خبرگاه المپیاد کامپیوتر
• منبع رسمی اخبار و اطلاعیه‌های کمیته‌ی المپیاد کامپیوتر در ایران
خبرگاه
آخرین خبر
اوّلین آزمون اینترنتی سایت acm.sharif.edu برگزار می‌شود.
پیوندها
تماس با کمیته
برای تماس با کمیته‌ی المپیاد کامپیوتر، نامه‌های الکترونیکی خود را به نشانی زیر ارسال نمائید:
سؤالات مفید در همین صفحه پاسخ داده خواهند شد.
 
Top درباره:
•  در این صفحه آخرین اخبار و اطلاعیه‌های مربوط به المپیاد کامپیوتر ایران نگاشته می‌شود.
•  کلیه‌ی نوشته‌جات این صفحه مورد تأیید کمیته‌ی ملی المپیاد کامپیوتر می‌باشد .
 
Top درس‌ها و اطلاعیه‌ها:
•  در این بخش دروس و اطلاعیه‌های مربوط به دانش‌پژوهان المپیاد کامپیوتر نگاشته می‌شود.
درس: Segment Tree


این درس شامل موارد زیر است:
مقدمه
در این مقاله میخواهیم ضمن حل چند مساله با یک Data Structure جدید آشنا شویم.
مساله ی اول
n عدد با نام های A1 … Anرا در نظر بگیرید که درابتدا همگی 0 هستند. هربار یکی از دو عمل زیر را روی دنباله میتوانیم انجام دهیم :
    1) Add(i , x) : این عمل به Ai ، به اندازه x تا اضافه میکند.
    2) Sum(i , j) : این عمل از شما می خواهد تا مجموع را در خروجی چاپ کنید.
خوب، یک الگوریتم بدیهی برای حل این مساله این است که یک آرایه به طول n (مثلن به نام a) در نظر بگیریم،
به ازای هر عمل Add(i , x) به a[i] ، x تا اضافه کنیم و برای هر سوال Sum(i , j) ، روی خانه های i ام تا j ام از آرایه for ببندیم و مجموع خواسته شده ( ) را در خروجی چاپ کنیم .
به این ترتیب هر عمل Add را در زمان O(1) و هر عمل Sum را در O(n) انجام میدهیم. و انجام m عمل روی دنباله از O(nm) طول خواهد کشید .
خوب ، پاسخ به m سوال در O(nm)) ... . به عنوان تمرین کمی فکر کنید و سعی کنید تا Order الگوریتم را کاهش دهید. (راهنمایی :آرایه a را به قسمت مساوی تقسیم کنید. به این ترتیب میتوانیم Order الگوریتم را به کاهش دهیم.)
با استفاده از Data Structure ای به نام Segment Tree میتوان Order الگوریتم را تا m log n کاهش داد :
یک درخت دودوای ریشه دار میسازیم و به هر راس آن یک زوج مرتب نسبت میدهیم .درخت را این گونه میسازیم :
به ریشه زوج [1, n] را نسبت میدهیم واز این به بعد این برای هر راس vبا زوج [i , j] داریم :
  • اگر i = j ، v هیچ بچه ای نخواهد داشت .
  • اگر i != j ، v یک بچه ی چپ با زوج مرتب [ i , (i+j)/2 ] و یک بچه راست با زوج مرتب [ (i+j)/2 +1 , j] خواهد داشت.
  • برای مثال اگر n=4 ، درخت شرح داده شده در شکل شماره 1 آمده است

حال برگردیم به مساله ی اول :
اگر چنین Data Stcuture ای داشته باشیم ، جواب دادن سوال Sum(i , j) با O(log n) امکان پذیر است :
یک آرایه به نام s در نظر میگیریم که برای هرراس v در درخت با زوج [i , j]، s[v] برابراست با .
(برای راحتی کار ، از این به بعد ، مولفه ی اولِ زوج مرتبِ راس دلخواهِ v از درخت را با v.left و مولفه ی دوم آن را با v.right نشان میدهیم.)
if ( i <= v.left && v.right <= j) return s[v] return find_sum(v.left , i, j) + find_sum(v.right, i, j);

int find_sum(v, i, j)

    if (v.right < i || j < v.left)

        return 0
    if (i <= v.left && v.right <= j)

        return s[v]
    return find_sum(v.left, i, j) + find_sum(v.right, i, j)

حال، برای جواب دادن به سوال Sum(i , j) کافیست تا find_sum(root, i, j) را در خروجی چاپ کنیم .
ادعا میکنیم که find_sum(v, i, j) که یک الگوریتم بازگشتی است، مجموع Ak هایی را بر میگرداند که


اثبات درستی الگوریتم :
find_sum(v, i, j) برای پیدا کردن این گونه عمل میکند :
اگر بازه ی [v.left , v.right] زیر بازه ی [i , j] بود ، s[v] را به عنوان جواب بر میگردانیم، وگرنه بازه ی [v.left , v.right] را به دو بازه ی جدا از هم Aleft=[v.left , (v.left+v.right)/2] و Aright=[(v.left+v.right)/2 +1 , v.right] تقسیم میکنیم، واضح است که پس :

find_sum(v,i,j) = find_sum(v.left,i,j) + find_sum(v.right,i,j)

پس find_sum(root, i, j) ، را بر میگرداند.
تمرین : ثابت کنید که زمان اجرایfind_sum(root, i, j) از O(log n) است.
خوب، تا به حال ثابت کرده ایم که اگر برای هر v ، s[v] نشان دهنده باشد،
find_sum(root, i, j) sigma Ai را بر میگرداند ، برای اینکه الگوریتممان کامل شود، کافیست طوری عمل کنیم تا بعد از هر بار عمل Add ، s[v] نشان دهنده Simgma باشد.
در ابتدا Ai ها 0 اند، پس برای هر v باید داشته باشیم s[v] = 0 . بعد از Add(i , x)، s را اینگونه update میکنیم :
void add(v, i, j)

    s[v] += x
    if (v.left == v.right )

        return
    if (v.left <= i && i <= (v.left+v.right)/2)

        add(v.left, i, x)
    else

        add(v.right, i, x)
به این ترتیب واضح است که برای انجام دستور add(i, x) و update کردن s ، کافیست تا add(root, i, x) را صدا بزنیم.
با توجه به اینکه ارتفاع درخت از O(log n) است (چرا؟) زمان اجرای add(root, i, x) هم از O(log n) است.
به این ترتیب ما در هر عمل Add با O(log n) ، s را update کردیم و به هر سوال Sum هم با O(log n) جواب دادیم، در نتیجه الگوریتممان از O(m log n) خواهد بود.

مساله ی دوم
n عدد با نام های A1 … Anرا در نظر بگیرید که درابتدا همگی 0 هستند. هربار یکی از دو عمل زیر را روی دنباله میتوانیم انجام دهیم :
    1) Add(i , x) : این عمل Ai را مساوی Ai+x قرار میدهد. ( x مقداری مثبت است )
    2) MaxOf(i , j) : این عمل از شما می خواهد تا عدد ماکسیمم بین Ai , Ai+1 , … , Aj را در خروجی چاپ کنید.
باز هم از Segment Tree استفاده میکنیم، درخت شرح داده شده در مساله ی اول را تشکیل میدهیم، یک آرایه به اسم max هم در نظر میگیریم به طوری که برای هر
راس v ، max[v] برابر با ماکسیمم عدد در بین Av.left , Av.left+1 , … , Av.right باشد.
همانند مساله ی اول عمل خواهیم کرد، فرض کنیم که آرایه max خاصیت گفته شده را دارد، در این صورت به هر سوال MaxOf(i , j) اینگونه پاسخ میدهیم :
int maxof(v, i, j)

    if (v.right < i  ||  j < v.left)

        return 0
    if (i <= v.left && v.right <= j)

        return max[v]
    return maximum ( maxof(v.left, i, j) , maxof(v.right, i, j) )

حال برای پاسخ دادن به سوال MaxOf(i , j) کافیست تا MaxOf(root, i , j) را در خروجی چاپ کنیم. در ضمن با توجه به مساله ی یک بدیهیست که زمان اجرای MaxOf(root, i , j) از O(log n) است. با توجه به اینکه همه Ai ها در ابتدا 0 اند، پس در آغاز کار برای هر راس v داریم max[v] = 0 . اکنون تنها چیزی که باقی میماند این است که max را پس از هر
بار عمل Add ، update کنیم :
void add(v, i, j)
    max[v] = maximum(max[v], A_i + x)

    if (v.right == v.left)
        /* here v.left is equal to i */

        A_i += x
        return
    if (v.left <= i && i <= (v.left+v.right)/2)

        add(v.left, i, x)
    else

        add(v.right, i, x)
پس به این ترتیب، باز هم هز سوال را در O(log n) جواب دادیم و در نتیجه الگوریتم از O(m log n) میباشد.
مساله ی سوم
همان مساله ی اول با این تفاوت که عمل Add را اینگونه تغییر میدهیم :
Add(i, j, x) : برای هر k، (i <= k <=j) به Ak ، x تا اضافه میکنیم.
برای حل این مساله ، همانند مساله ی اول عمل میکنیم با این تفاوت که یک آرایه دیگر به نام all نگه میداریم که all[v] برابر است با مقداری که قرار است به
همه ی Av.right Av.left… افزوده شود :
int find_sum(v, i, j){
    if (v.right < i || j < v.left)  
        return 0

    if ( i <= v.left  &&  v.right <= j)

        return s[v]
    int eshterak = minimum(v.right, j) - maximum(v.left, i) + 1

    /* length of the interval [v.left, v.right] ^ [i, j] */
    return  find_sum(v.left , i, j) 
                 + find_sum(v.right, i, j) + all[v] * eshterak

}

void add(v, i, j, x){

    if (v.right < i || j < v.left)  
        return 
    if ( i <= v.left &&  v.right <= j)

        all[v] += x
    else {
        add(v.left, i, j, x)

        add(v.right, i, j, x)
    }

    s[v] = s[v.left] + s[v.right] + all[v]*(v.right - v.left + 1)
}

اثبات درستی این الگوریتم و محاسبه ی زمان اجرای آن ( که O(m log n) است) به خواننده واگذار میشود.
تمرین
  1. مساله ی 2 با این تفاوت که در تابع Add ، x میتواند منفی هم باشد.
  2. مساله ی 311 از سایت http://acm.sgu.ru
  3. مساله ی 325 از سایت http://acm.sgu.ru
منبع
برای مطالعه بیشتر در این زمینه می توانید به کتاب ورودی به الگوریتم نوشته CLRS و ورودی به الگوریتم با نگرشی خلاقانه مراجعه کنید.
 
درس: شارش بیشینه در شبکه


این درس شامل موارد زیر است:
تعاریف
  • شبکه جریان: شبکه هاي جريان مانند شبکه هاي توزيع آب, حمل و نقل, جريان برق و ... را مي توان مانند خيلي از موارد با گراف مدل سازي کرد. يک شبکه جريان شامل تعدادي گره (رئوس گراف) و ارتباط هاي بين بعضي از آنها (يالهاي گراف) است.
    اين ارتباط ها مانند لوله هاي آب مي توانند جريان را از يک رأس به رأس ديگر منتقل کنند. براي اين يالها محدوديتي وجود دارد که نشان دهنده حداکثر توان آن يال براي عبور جريان است. حداکثر جرياني که از آن يال مي گذرد را با اين محدوديت که آن را ظرفيت (capacity) مي ناميم نشان مي دهيم.
    در يک شبکه جريان به وضوح جريان در رأسي از بين نمي رود. يعني ميزان جريان ورودي يک رأس با جريان خروجي همان رأس برابر است. به اين خاصيت براي يک رأس بقاي جريان در آن رأس مي گويند.
  • جريان در يک شبکه: يک شبکه را با يک گراف جهت دار که هر يال آن وزني دارد نشان داديم. منظور از يک جريان در يک شبکه, نسبت دادن اعدادي نامنفي به يالها به عنوان ميزان جريان عبوري از آن يال است که داراي خواصي که گفته شد باشند. يعني عددي که ما نسبت مي دهيم از ظرفيت يال بيشتر نشود و در رئوسي که مولد يا مصرف کننده جريان نيستند بقاي جريان را داشته باشيم.
    براي بيان دقيقتر مي توان اينطور گفت :
    گراف جهتدار G داراي مجموعه رئوس V ومجموعه يالهاي E است و تابع c روي مجموع يالها تعريف شده که c(e) نشان دهنده ظرفيت آن يال است.
    تابع u را در نظر بگيريد که u(e,v) نمايش دهنده وضعيت يال e و رأس v است. u(e,v) مي تواند سه مقدار 0 و 1 و -1 را به ترتيب در حالتهاي زير بگيرد :
      1. اگر v هيچ يک از دو سر e نباشد u(e,v) را 0 تعريف مي کنيم.
      2. اگر e از رأس v خارج شده باشد u(e,v) را 1 تعريف مي کنيم.
      3. اگر e به رأس v داخل شده باشد u(e,v) را -1 تعريف مي کنيم.
    در اين حالت يک جريان, تابع f روي يالها است که براي هر يال e داشته باشيم و براي هر رأس v که مولد يا مصرف کننده نيست داشته باشيم (حاصل جمع دقيقاً برابر ميزان جريان خروجي منهاي ميزان جريان ورودي است)
    براي مثال شکل زير جرياني در يک شبکه را نشان مي دهد. رئوس مشخص شده مولد يا مصرف کننده هستند و قانون بقاي جريان براي آنها صادق نيست. اعداد کمرنگ روي يالها نمايش دهنده جريان عبوري از آن يال و اعداد پررنگ نمايش دهنده ظرفيت آن يال است.


مسئله بيشينه جريان
شبکه جرياني را در نظر بگيريد که در آن فقط دو رأس استثناء يعني رئوسي که مولد يا مصرف کننده هستند وجود دارند. اين دو رأس را با s و t نشان مي دهيم. s به عنوان منبع توليد جريان (چشمه) و t به عنوان مصرف کننده (چاه) در نظر گرفته مي شوند.
مسئله ما شامل پيدا کردن جرياني در شبکه است که بيشترين ميزان ممکن جريان را از s به t منتقل کند. يعني جرياني که ميزان خروجي s منهاي ميزان ورودي s در آن بيشينه باشد.
ديدگاه فيزيکي زير به ما براي حل اين مسئله کمک مي کند :
    در يک جوي آب حداکثر جريان عبوري از جوي با کمترين سطح مقطع جوي متناسب است.
در يک شبکه جريان نيز کمابيش وضعيت مانند بالاست.
برش ها در شبکه جريان
يک برش (cut), افراز رئوس گراف به دو مجوعه S و T است که منبع در مجموعه S و چاه در مجموعه T قرار بگيرند. تعبير برش در مثال جوي آب, در نظر گرفتن دو تکه در دو سمت سطح مقطع جوي است.
مثال جوي آب را در نظر بگيريد، اگر دو سطح مقطع در جوي را در نظر بگيريد آب در تکه جوي که بين اين دو سطح مقطع قرار مي گيرد نمي تواند انباشته يا توليد شود, يا به عبارت ديگر جريان عبوري از يک سطح مقطع با ديگري برابر است. اين حکم در مورد شبکه هاي جريان هم معادل خود را دارد. جريان عبوري از يک برش را به شکل زير تعريف مي کنيم :


مي خواهيم ثابت کنيم که اين حاصل جمع از برش مستقل است. در واقع


تساوي آخر در بالا به اين خاطر است که اگر دو سر e در T باشند همه جملات u(e,v)f(e) برابر 0 مي شوند, اگر e از T به S باشد هم جمع جملات u(e,v) برابر -f(e) مي شود و اگر هم از S به T باشد اين حاصل جمع برابر f(e) مي شود, اگر هم از S به S باشد مثلاً e=(a,b) اين حاصل جمع برابر u(e,a)f(e)+u(e,b)f(e) يا f(e)-f(e)=0f(e)-f(e)=0 مي شود. پس تساوي بالا برقرار است. اما


تساوي آخر به اين علت برقرار است که جمله حاصل جمع داخلي براي x هايي که در آنها قانون بقاي جريان برقرار است بنابر تعريف 0 مي شود و فقط براي s از بين نمي رود.
اما سمت راست تساوي به برش ما ربطي ندارد و با نگاه دقيقتر مي بينيم که برابر جريان خارج شده از s است, همان چيزي که ما مي خواهيم بيشينه اش کنيم.
حالا مي توانيم يک طرف قضيه اي را که مي خواستيم، ثابت کنيم. يعني اينکه جريان خارج شده از s از ظرفيت هر برشي کمتر يا مساوي است. منظور از ظرفيت يک برش است. چون مقدار جريان برابر است با


براي اينکه نشان دهيم براي يک برش تساوي رخ مي دهد, از الگوريتمي که در ادامه مي آيد استفاده مي کنيم.
الگوريتم پيدا کردن جريان بيشينه
براي راحتي کار فرض کنيد ظرفيت يالهايي که در گراف نيستند را 0 در نظر مي گيريم (در تساوي هاي قبل به طور ضمني از اين موضوع استفاده کرده بوديم)
از روي يک جريان مانند f مي توان تابع F را ساخت که . F در حقيقت جريان واقعي بين دو رأس را نشان مي دهد و مي تواند منفي هم بشود. براي F بايد رابطه برقرار باشد. در ضمن شرط بقاي جريان براي F هم به صورت مي شود. از روي هر تابع F با اين خواص نيز مي توان تابع f را به صورت ساخت. به عنوان تمرين مي توانيد موارد زير را ثابت کنيد.
  • f اي که به اين صورت ساخته مي شود يک جريان است.
  • جريان خروجي ازs در f برابر است
  • اگر (S,T) يک برش باشد آنوقت
به وضوح يک جريان براي هر شبکه وجود دارد, و آن, جريان همه جا 0 است. در ابتدا تابع F را همه جا برابر 0 قرار دهيد.
اگر مسيري از s به t وجود داشته باشد که ظرفيت يالهاي آن ناصفر باشند مي توان به F يالهاي اين مسير مقداري اضافه کرد و از F يالهاي مسير معکوس همان مقدار را کم کرد. در واقع مقدار اضافه شده بايد از تمام ظرفيت هاي يالهاي اين مسير کمتر باشد, پس به اندازه کمترين ظرفيت يالهاي اين مسير به F تمام يالها اضافه مي کنيم و همين مقدار را از يالهاي مسير معکوس کم مي کنيم. با اينکار جرياني با اندازه بيشتر به دست مي آوريم. بعد از اين عمل ظرفيت يالها کمي تغيير مي کند. در واقع بايد از ظرفيت تمام يالهاي اين مسير مقدار اضافه شده به F را کم کرد و به ظرفيت يالهاي مسير معکوس اين مقدار را اضافه کرد.
با انجام عمل بالا جرياني با اندازه خروجي از s بيشتري به وجود مي آيد. پس عمل بالا را مي توان تا جاي ممکن انجام داد. در پايان گرافي به وجود مي آيد که در آن هيچ مسيري با ظرفيت يالهاي مثبت از s به t وجود ندارد. (ممکن است الگوريتم پايان نپذيرد ولي در تمرينات مي بينيم که براي حالتي که وزن يالها صحيح باشند حتماً الگوريتم پايان مي پذيرد)
در گراف حاصل مي توان برشي را به صورت زير تعريف کرد :
    S را تمام رئوسي قرار دهيد که از s به آنها مسيري با يالهاي داراي ظرفيت مثبت وجود دارد. T را هم بقيه رئوس بگيريد.
در تمرين ها مي بينيد که در الگوريتم بالا F(x,y)+c(x,y) براي دو رأس x و y همواره ثابت است. در برش ما اگر x در S و y در T باشند چون c(x,y)=0 پس F(x,y) برابر مقدار اوليه c(x,y) است. پس به راحتي ديده مي شود که براي اين برش پس جرياني پيدا کرديم که برابر يکي از برش هاست (از نظر مقداري) ولي چون هر جرياني از هر برشي کمتر يا مساوي است پس اين جريان بيشينه است.
کاربردهاي الگوريتم جريان بيشينه
يکي از مثالهاي کاربرد اين الگوريتم مسئله تطابق بيشينه است. در گراف دو بخشي (X,Y) مي خواهيم بيشترين تعداد يالهاي ممکن که با هم رأس مشترک ندارند را پيدا کنيم.
تمام يالهاي گراف دو بخشي را از بخش X به بخش Y جهت دار کنيد و به گراف دو رأس s و t را اضافه کنيد.
از s به تمام رئوس X و از تمام رئوس Y به t يال بگذاريد. وزن تمامي يالها را نيز 1 تعريف کنيد


طبق الگوريتم ما در گرافي که وزن تمام يالهاي آن صحيح هستند مي توان جرياني پيدا کرد که در آن جريان عبوري از هر يلل يک عدد صحيح شود (چون در هر مرحله از الگوريتم ما مقدار صحيحي جريان هر يال را تغيير مي دهيم) پس در جريان حاصل جريان هر يال 0 يا 1 است.
برشي را در نظر بگيريد که S آن شامل s و رئوس X است و T شامل t و رئوس Y. طبق لمي که داشتيم


و مقدار سمت راست برابر تعداد يالهاي داراي جريان بين X و Y است. از طرفي هيچ دو تا از اين يالها نمي توانند در يک رأس مشترک باشند چون جريان ورودي به رئوس X و جريان خروجي از رئوس Y حداکثر 1 هستند و در نتيجه دو يال جريان دار نمي توانند از يک رأس X خارج يا به يک رأس Y وارد شوند. به راحتي مي توان ديد به ازاي هر تطابق نيز يک جريان وجود دارد. از طرفي چون تعداد يالها برابر مقدار جريان است پس تطابق وقتي بيشينه است که جريان متناظرش بيشينه باشد.
در تمرين ها کاربردهاي بيشتري از اين مسئله را مي بينيم.
پياده سازي الگوريتم
در زير شبه کد اين الگوريتم آمده است
maxflow(Graph G, Cost c, source s, sink t)

    for each edge(x, y) in E(G) do

        F[x, y] := 0
        F[y, x] := 0

    while there is a positive capacity path P in G from s to t do
        k := { min c(e) | for every edge e in P }

        for each edge(x, y) in P do
            F[x, y] := F[x, y] + k
            F[y, x] := F[y, x] - k
            F[x, y] := F[x, y] - k
            F[y, x] := F[y, x] + k


در شبه کد بالا براي پيدا کردن مسير مي توان از الگوريتم هايي مانند DFS و BFS استفاده کرد. البته اگر از BFS استفاده شود مي توان ثابت کرد که الگوريتم در پايان مي پذيرد. از اينرو الگوريتم پيدا کردن جريان بيشينه در رده الگوريتم هایي با زمان اجراي چندجمله اي جاي مي گيرد. (رجوع شود به [CLRS])
تمرين ها
در تمرين هاي زير تعداد ستاره ها مشخص کننده سختي است و تمرين هاي بدون ستاره براي ياد آوري و يا اثبات احکام جا مانده در متن است.
  1. ثابت کنيد در مراحل انجام الگوريتم مقدار F(x,y)+c(x,y) براي دو رأس x و y تغير نمي کند.
  2. ثابت کنيد اگر ظرفيت ها اعدادي صحيح باشند الگوريتم در |f*| مرحله که f* جريان بيشينه است پايان مي پذيرد. با استفاده از اين موضوع و يافتن کران خوبي براي |f*| (از روي برشي که در آن S فقط تک رأس s را دارد) نشان دهيد که الگوريتم براي تطابق بيشينه در پايان مي پذيرد.
  3. * فرض کنيد ظرفيت يالها بتواند بينهايت شود, نشان دهيد اگر جريان بيشينه بينهايت نشود همچنان الگوريتم پايان پذير است. از اين موضوع استفاده کنيد و الگوريتم را طوري تغيير دهيد که در صورتي که جريان بيشينه بينهايت شود در متناهي مرحله به ما اطلاع دهد که جريان بيشينه بينهايت است و در غير اينصورت به ما جريان بيشينه را برگرداند.
  4. * نشان دهيد وقتي شبکه ما چند يال از يک رأس به رأس ديگر دارد با حالتي که دقيقاً يک يال از هر رأس به رأس ديگر وجود دارد معادل است و از آن نتيجه بگيريد که مي توان الگوريتم را در زمان اجراي پياده سازي کرد.
  5. * ثابت کنيد که مسأله پيدا کردن جريان بيشينه در حالتي که چند رأس چشمه و چند رأس چاه وجود دارند با حالتي که فقط يک چاه و يک چشمه وجود دارند معادل است (راهنمايي : يک چشمه را به بقيه وصل کنيد و ...)
  6. * نشان دهيد که مسأله وقتي که براي جريان عبوري از هر رأس نيز ظرفيت دارد با حالتي عادي معادل است. (راهنمايي : هر رأس را دو تا کنيد, يکي رأس ورودي و ديگري رأس خروجي)
  7. ** نشان دهيد که عدد همبندي يالي يک گراف را مي توان با چند بار استفاده از maximum flow حل کرد (راهنمايي : تعداد يالهاي لازم براي حذف کردن براي اينکه از رأس ثابت u به رأسي ديگر مسير پيدا نشود را با استفاده از الگوريتم روي گراف به اضافه يک رأس مجازي t پيدا کنيد)
  8. اگر تعداد يالها زياد باشد بايد آنها در ليست مجاورت نگهداري کنيم. در اين حالت بگوييد بايد به چه نکاتي در پياده سازي توجه کرد. (راهنمايي : يالهاي مسير معکوس را چگونه پيدا مي کنيد؟)
  9. ثابت کنيد اگر در شبکه اي ظرفيت هر يال 0 يا 1 باشد, جريان بيشينه اي وجود دارد که جمع تعدادي مسير مجزاي يالي است.
  10. * نشان دهيد کمينه تعداد يالهاي لازم که اگر آنها را حذف کنيم از رأس x به رأس y در گراف مسيري وجود نداشته باشد با حداکثر تعداد مسيرهاي مجزاي يالي بين x و y برابر است.
  11. *** يک پوشش مسيري از گراف G تعدادي مسير مجزاي رأسي در G است که تمام رئوس G در آنها ظاهر شده اند. ثابت کنيد که پوشش مسيري با تعداد مسيرهاي کمينه را مي توان در صورتي که گراف يک DAG باشد را با maximum flow پيدا کرد. (راهنمايي : گراف دو بخشي بسازيد که هر بخش آن رئوس G باشند و يالهاي آن از x در بخش بالا به y در بخش پايين است اگر در گراف G از x به y يال وجود داشته باشد.) نشان دهيد که اين الگوريتم براي گراف هاي داراي دور کار نمي کند.
  12. **** فرض کنيد روي جريان عبوري از يک يال يک کران پايين نيز قرار دهيم. با ديدن تعريف F از روي f و رابطه نشان دهيد که چطور شبکه اي با اين خاصيت بسازيم (راهنمايي: از ظرفيت هاي منفي استفاده کنيد). مي خواهيم اين مسأله را که جرياني در شبکه با ظرفيت هاي منفي پيدا کنيم را حل کنيم. (در اين مسأله s و t اي وجود ندارند) نشان دهيد که مسأله پيدا کردن جريان بيشينه با استفاده مکرر از مسأله بالا قابل حل است (راهنمايي : از t به s يک يال با ظرفيت بينهايت قرار دهيد و روي ميزان جريان جستجوي دودويي را انجام دهيد) فرض کنيد گراف مسأله جديد G باشد. گراف H را اينطور بسازيد که و ظرفيت يالهاي H را که با d نمايش مي دهيم را اينطور تعريف مي کنيم که براي هر u و v در V(G) و و که در آن منظور از c(X,Y) که X و Y دو زيرمجموعه V(G) هستند برابر تعريف مي شود. نشان دهيد که يک جريان در G وجود دارد اگر و فقط اگر تمامي يالهاي H نامنفي باشند و در جريان بيشينه H تمام يالهاي ورودي t اشباع شده باشند (يعني جريان عبوري از آنها با ظرفيتشان يکي باشد) در ضمن از روي اين جريان براي H جرياني براي G بسازيد. نشان دهيد که اگر G خود داراي چشمه و چاه باشد مي توان با دو بار انجام maximum flow جريان بيشينه را در صورت وجود در G پيدا کرد (با فرض اينکه يالها منفي هم مي توانند باشند)
منبع
برای مطالعه بیشتر در این زمینه می توانید به کتاب های زیر مراجعه کنید.
 
درس: تطابق ماکسیمم در گراف دوبخشی


این درس شامل موارد زیر است:
تعاریف
  • گراف دوبخشی: گرافی است که راس‌هایش را بتوان به دو مجموعه‌ افراز کرد، به طوری که برای هر یالِ u، uv و v در دو مجموعه‌ی متفاوت باشند.
  • تطابق: یک تطابق در گراف بدون جهت G عبارت است از مجموعه‌ای از یال‌های گراف به طوری که هر رأس حداکثر به یکی از یال‌های مجموعه متصل باشد. رأسی که به یکی از یال‌های تطابق متصل باشد را «آلوده» می‌نامیم. اگر یک تطابق تمام رئوس گراف را آلوده کند، آن تطابق را یک «تطابق کامل» می‌نامیم.
  • تطابق ماکسیمم: تطابقی است که بیشترین تعداد یال را در گراف داشته باشد. هم‌چنین تطابق ماکسیمال تطابقی است که نتوان یالی از یال‌های گراف را به آن اضافه کرد به طوری که یک تطابق بزرگتر بوجود بیاید. بدیهی است که یک تطابق ماکسیمال ممکن است ماکسیمم نباشد.
  • مسیر متناوب: مسیری است که یال‌هایش یکی در میان در تطابق قرار داشته باشند.
  • مسیر افزایشی: مسیری متناوب که رئوس اول و آخر آن در تطابق نباشند را یک مسیر افزایشی می گویند.
مقدمه
در مجموعه‌ای از اشیا هر شی می‌تواند با برخی از اشیای دیگر سازگار باشد. مثلاً در مجموعه‌ای از آدم ها افراد می‌توانند با یکدیگر دوست باشند در نتیجه می توان دوستی را یک رابطه سازگاری تعریف کرد. حال فرض کنید بخواهیم این گروه را به مجموعه‌هایی دوتایی تقسیم کنیم که هر فرد حداکثر در یک گروه بیاید و افراد هر گروه با یکدیگر دوست باشند. در واقع مسائلی از این قبیل را می‌توان به دید گراف و پیدا کردن یک تطابق ماکسیمم در این گراف نگاه کرد.
با افزایش سؤالاتی از این قبیل دغدغه‌ای برای پیدا کردن یک الگوریتم مناسب برای حل این گونه مسائل در افراد بوجود آمد. از اولین قضایا در این مجموعه که برای پیدا کردن یک الگوریتم مناسب پرکاربرد است، می‌توان به قضیه زیر اشاره کرد:
قضیه ۱
یک تطابق در یک گراف ماکسمیم است، اگر و فقط اگر گراف دارای هیچ مسیر افزایشی‌ای نباشد.
در واقع با شرط لازم بودن این شرط به ما کمک می‌کند برای پیدا کردن یک تطابق ماکسیمم از یک تطابق M که در ابتدا خالی است شروع کرده و با پیدا کردن مسیرهای افزایشی تطابقمان را بزرگ کنیم. به این ترتیب که یک مسیر افزایشی پیدا کرده و یال‌هایی از آن را که در M هستند از M خارج کنیم و یال‌هایی از آن را که در M نیستند به آن اضافه کنیم.
به این ترتیب با وجود این‌که شرط تطابق بودن M از بین نرفته یکی به تعداد یال‌های M اضافه شده است. می توان این کار را آن‌قدر انجام داد تا هیچ مسیر افزایشی دیگری پیدا نشود که در این صورت طبق قضیه 1 می‌توان نتیجه گرفت که M ماکسیمم است.
تنها چیزی که باقی می ماند پیدا کردن مسیر افزایشی در یک گراف G می باشد که ما در اینجا الگوریتمی برای پیدا کردن این مسیرها در گراف دوبخشی می‌دهیم
الگوریتم
الگوریتم پیدا کردن مسیر افزایشی در گراف دوبخشی:
  • ورودی: یک گراف دوبخشی با افراز X و Y از رأس‌ها و یک تطابق M در G و مجموعه‌ی U از همه رأس‌های آلوده نشده توسط M در X.
  • ارزش‌دهی آغازی: S = U و T = Ø.
  • تکرار: برای هر راس در داخل S تمام مسیرهای افزایشی که از راس مورد نظر شروع میشوند را در نظر می گیریم. اگر راسی در بخش X در یکی از این مسیرهای افزایشی قرار داشت آن راس را به مجموعه S اضافه می کنیم. همچنین اگر راسی در بخش Y در یکی از این مسیرهای افزایشی قرار داشته باشد آن راس را نیز به مجموعه T اضافه می کنیم. برای راس های S آنقدر این کار را انجام می دهیم تا هیچ راسی را نتوان به S یا T اضافه کرد.
    حال اگر در مجموعه T راسی باشد که آلوده نشده است در این صورت یک مسیر افزایشی پیدا کرده ایم. زیرا از یکی از راس های غیر آلوده که در مجموعه U قرار داشت به یک راس غیر آلوده در بخش دیگر مسیری متناوب پیدا شده است. چون ابتدا و انتهای این مسیر راس هایی غیر آلوده هستند پس این مسیر یک مسیر افزایشی است.


این کار را آنقدر انجام میدهیم تا زمانی که دیگر مسیر افزایشی در گراف وجود نداشته باشد. توجه داشته باشید که اگر مسیری افزایشی در گراف وجود داشته باشد، اگلوریتم ما آن را پیدا می کند. زیرا الگوریتم ما همه مسیرهای متناوب که از راسی غیر آلوده از مجموعه X شروع می شود را جستجو می کند.
تحلیل
در واقع در این الگوریتم S مجموعه رئوسی در X بودند که با مسیری متناوب از U به آن‌ها رسیدیم و T مجموعه رئوسی در Y بودند که با مسیری متناوب از U به آن‌ها رسیدیم. در واقع پیاده سازی این الگوریتم بوسیله الگوریتم DFS به سادگی قابل انجام است با ادامه دادن این الگوریتم بعد از پیدا کردن هر مسیر افزایشی تا وقتی که دیگر هیچ مسیر افزایشی دیگری در گراف باقی نماند می توان یک تطابق ماکسیمم را در G پیدا کرد.
این الگوریتم در واقع از مرتبه‌ی O(ne) می باشد.
الگوریتم‌های بهتری نیز برای پیدا کردن تطابق دوبخشی ماکسیمم وجود دارد. مثلاً یون و تارجان الگوریتمی دادند که این مسئله را در O(√ne)حل می کند.
مجموعه مستقل
مجموعه مستقل در گراف G به مجموعه ای از راس ها گفته می شود که بین آنها یالی نباشد. یک مجموعه مستقل ماکسیمم مجموعه مستقلی با بیشترین تعداد راس ممکن است. طبق چیزی که در تمرین شماره 4 ثابت می شود می دانیم که اندازه بزرگترین مجموعه مستقل در گراف دوبخشی برابر n ( تعداد راس های گراف ) منها اندازه بزرگترین تطابق است. پس اگر ما موفق به پیدا کردن مجموعه مستقلی به این اندازه شویم مجموعه مستقل ماکسیمم را پیدا کرده ایم.
برای این کار فرض کنید تطابق ماکسیمم را پیدار کرده ایم و این تطابق مجموعه S از رئوس بخش X و T از رئوس بخش Y را آلوده کرده باشد. حال تمام مسیرهای متناوب که از رئوس مجموعه X-S شروع می شوند را در نظر بگیرید. رئوسی از بخش X را که عضو حداقل یکی از این مسیرها هستند را مجموعه P و رئوسی از بخش Y را که عضو حداقل یکی از این مسیرها هستند را Q می نامیم. حال مجموعه P+(Y-Q) یکی مجموعه مستقل با اندازه خواسته شده است ( تمرین 5 ).
کاربرد
الگوریتم پیدا کردن تطابق ماکسیمم در حل بسیاری از مسائل می تواند تأثیرگذار باشد. مثلاً می‌توان بزرگترین مجموعه‌ی مستقل (مجموعه‌ی از رئوس که هیچ دوتایی به یکدیگر یال ندارند) در گراف دوبخشی را نیز پیدا کرد یا کوچکترین پوشش رأسی (مجموعه ی از راس ها که به ازای هر یال یک سر آن در این مجموعه باشد)؛ (حل این مسائل به خواننده توصیه می شود). پیدا کردن راه حلی برای این مسائل باعث حل مسائل زیاد دیگری در نظریه گراف شد که تأثیر بسزایی در حل مسائل الگوریتمی دارد. پس داشتن یک برنامه مناسب و دقیق برای پیدا کردن تطابق می تواند بسیار مفید باشد.
تمرین
  1. ثابت کنید در گراف G با n راس که دارای هیچ راس تنهایی نمی باشد اندازه تطابق ماکسیمم بعلاوه اندازه پوشش یالی مینیمم برابر n است.
  2. ثابت کنید در هر گراف دوبخشی G اندازه بزرگترین تطابق برابر اندازه کوچکترین پوشش راسی می باشد.
  3. ثابت کنید در هر گراف دوبخشی G اندازه بزرگترین مجموعه مستقل برابر اندازه کوچکترین پوشش یالی می باشد.
  4. ثابت کنید اندازه کوچکترین پوشش راسی بعلاوه اندازه کوچکترین پوشش یالی برابر n می باشد.
  5. ثابت کنید اندازه بزرگترین مجموعه مستقل در گراف دوبخشی برابر n ( تعداد رئوس گراف ) منها اندازه تطابق ماکسیمم در گراف می باشد.
  6. ثابت کنید روشی که برای پیدا کردن بزرگترین مجموعه مستقل در گراف دوبخشی داده شده است این مجموعه را پیدا می کند.
  7. ( مسئله ازدواج پایدار ) فرض کنید n مرد و n زن می خواهند ازدواج کنند به صورتی که هر مرد لیستی اولویت بندی شده از زن هایی دارد که می خواهد با آنها ازدواج کند ( می توانید فرض کنید هر مرد به زن ها اعداد 1 تا n را که نشانه اولویت آنهاست نسبت می دهد ). همچنین زن ها نیز لیسیت از اولویت های خود مانند زن ها دارند. حال می خواهیم ترتیبی از ازدواج را بین این n مرد و n زن برقرار کنیم به طوری که هیچ زن و مردی نباشند که همدیگر را به همسرهای کنونی خود ترجیح دهند. الگوریتمی برای انجام این کار بدست آورید.
منبع
برای مطالعه بیشتر در این زمینه می توانید به فصل سوم کتاب آشنایی با نظریه گراف نوشته وست مراجعه کنید.
 
درس: مؤلفه‌های دوهمبند (رأسی)


این درس شامل موارد زیر است:
تعاریف
  • گراف دوهمبند(راسي): گراف جهت‌داری که بین هر دو راس u و v از آن، یک مسیر جهت‌دار از u به v و یک مسیر جهت‌دار از v به u وجود داشته باشد.
  • مؤلفه‌ی دوهمبند(راسي): یک زیرگراف القایی ماکسیمال که قویا همبند است.
  • راس برشی: یا همان GT که در آن راس u به راس v یال دارد اگر و فقط اگر در گراف G راس v به راس u یال داشته باشد.
  • گراف دوهمبند يالي: گراف بدون جهتي كه با حذف يك يال دلخواه، همچنان همبند باقي بماند.
مقدمه
تجزیه‌ی گراف به مؤلفه‌های دوهمبند آن یکی دیگر از روش‌های مفید در حل الگوریتم‌های گراف است. چرا که با صرف زمانِ از مرتبه‌ی رشدِ ورودی، براحتی می‌توان در بسیاری مسائل فرض کرد که گراف‌های ورودی دوهمبند هستند. البته در بسیاری موارد توجه به این نکته که با جایگذاری مؤلفه‌های دوهمبند با یک راس، و وصل كردن دو راس در صورت وجود اشتراك بين دو مؤلفه‌ي نظير، به یک درخت می‌رسیم؛ مي‌تواند در درک و حل مسئله‌ها به ما کمک ‌کند.
قضیه
    1) دو راس u و v در یک مؤلفه‌ی دوهمبند قرار دارند اگر و فقط اگر داخل یک دور قرار داشته باشند.
    2) یال‌های یک گراف (و نه راس‌های آن) بصورت یکتا به مؤلفه‌های دوهمبند افراز می‌شوند.
    x اثبات این دو قضیه به‌عنوان تمرین در نظر گرفته می‌شود.
اثبات این دو قضیه به‌عنوان تمرین در نظر گرفته می‌شود.
الگوریتم
براي راحتي كار مي‌توان فرض كرد كه گراف همبند است، چراكه در غير اين صورت مي‌توانيم همين الگوريتم را براي هر مؤلفه‌ي همبندي تكرار كنيم. دوهمبند نبودن يك مؤلفه، به معناي وجود راس برشي در آن است. بنابراين بايد به نحوي بتوان رئوس برشي را تشخيص داد. فرض كنيد راس u يك راس برشي با k يال باشد. برشي بودن u به اين معناست كه دو دسته از اين k شاخه وجود دارند به نحوي كه هيچ يالي بين اين دو دسته وجود نخواهد داشت. حال اگر كمي دقيق‌تر بررسي كنيم، متوجه مي‌شويم كه اگر دوشاخه به هم يال داشته باشند، در حقيقت مي‌توان آن دو را يك شاخه در نظر گرفت. براي همين برشي بودن يك راس در حقيقت به معناي چند شاخه بودن آن است. بنابراين اگر بخواهيم تعداد شاخه‌هاي واقعي راس u را پيدا كنيم، بهترين كار اينست كه از يكي از يال‌ها شروع كرده و همه‌ي راس‌هاي در آن شاخه را پيدا كنيم و بعد اگر هنوز يالي به راس جديدي وجود دارد، اين كار را در آن شاخه انجام دهيم. در حقيقت بنظر مي‌رسد كه اين كار بسيار شبيه عمل الگوريتم DFS است. به همين ترتيب با كمي تأمل الگوريتم زير بدست مي‌آيد:
از يك رأس دلخواه r شروع به DFS زدن مي‌كنيم. الگوريتم DFS ما همانند يك DFS عادي عمل مي‌كند، با اين دو تفاوت كه اولاً به رئوس نسبت به r ارتفاع نسبت مي‌دهد(يعني h[r]=1 و ارتفاع بقيه‌ي رئوس نيز برابر يكي بيشتر از فاصله‌ي آن‌ها تا راس r در درخت DFS ‌است) و ثانياً هنگام خروج از يك راس، كوچكترين ارتفاعي را برمي‌گرداند كه خود آن راس يا رئوس زير درختش به آن يال دارند. بطور مثال در گراف زير:


در اجراي الگوريتم DFS اين گراف بصورت زير درمي‌آيد و بطور مثال راس J عدد 4 را برمي‌گرداند و راس E‌ عدد 1.


واضح است كه در اين الگوريتم يك راس u براي يكي از شاخه‌هاي زيرينش برشي است، اگر و فقط اگر جواب DFS‌ آن شاخه، برابر ارتفاع خودش شود. چراكه مي‌دانيم در درخت DFS همه‌ي يال‌هاي غير درخت، بين يك راس و يكي از اجداد آن راس است. بنابراين در طي الگوريتم، در صورتي كه يكي از شاخه‌هاي راس u جوابي برابر ارتفاع u برگرداند، ما مؤلفه‌ي شامل u و راس‌هاي باقيمانده در آن شاخه را بعنوان يك مؤلفه‌ي دوهمبند در نظر مي‌گيريم و رئوس آن شاخه را (كه u جزء آن‌ها نيست) حذف مي‌كنيم.
حال مي‌خواهيم ثابت كنيم كه مؤلفه‌هاي در نظر گرفته شده واقعا مؤلفه‌هاي دوهمبندي گراف هستند. براي اين كار فرض كنيد كه در راس u جواب شاخه‌ي وابسته به راس v برابر ارتفاع u شده‌است. همچنين فرض كنيد كه تا بحال الگوريتم ما مؤلفه‌هاي دوهمبندي را به درستي پيدا كرده است. واضح است كه راس u و v و بقيه‌ي راس‌هاي باقيمانده در آن شاخه، بايد تشكيل يك يا چند مؤلفه‌ي دوهمبندي بدهند و بدليل فرض درست بودن الگوريتم تا به اينجا، و همچنين قضيه‌ي 1، هيچ راس ديگري نيز داخل اين مؤلفه‌ها نخواهد بود. حال اگر راس v‌ تنها باشد كه مسلما مؤلفه‌ي uv همان مؤلفه‌ي مورد نظر خواهد بود. وگرنه مثلا راس x مجاور v در درخت را در نظر بگيريد. چون راس x حدف نشده، اين نشان مي‌دهد يا x، يا يكي از بچه‌هاي x كه هنوز حذف نشده به راس u يال دارد و لذا راس u و x در يك دور قرار دارند. بطور كلي براي همه‌ي رئوس باقيمانده، به روش مشابه اما كمي پيچيده‌تر ثابت مي‌شود كه با هم و با u در يك دور قرار دارند و لذا اين مؤلفه را نيز درست تشخيص خواهيم داد. كه جزئيات اين اثبات به عهده‌ي خواننده خواهد بود.
براي مثال در صورت اجراي الگوريتم در شكل بالا، مؤلفه‌ها به ترتيب زير بدست خواهند آمد: J-K و L-J-M و H-I و G-H و A-F-E-D-G-C و بالاخره A-B.
البته براي پياده سازي ساده‌ي اين الگوريتم، بهتر است كه رئوس را به هنگام بازديد در انتهاي پشته قرار دهيم و در صورت احتياج به معرقي يك مؤلفه، همه‌ي رئوس در پشته را كه آن شاخه قرار داده خارج مي‌كنيم و بعنوان مؤلفه‌ي دوهمبند اعلام مي‌كنيم.
و روشن است كه اين الگوريتم تنها يك DFS‌ تغيير يافته است و لذا همچنان از O(n+e) است.
تمرین
  1. روشي براي پيدا كردن رأس‌ها برشي در الگوريتم بالا ارائه دهيد. چه زماني رأس r برشي خواهد بود؟
  2. الگوريتمي براي پيدا كردن مؤلفه‌هاي دوهمبند يالي ارائه دهيد.
  3. الگوريتم گفته شده و الگوريتم سوال1 و سوال 2 را پياده‌سازي كنيد.
  4. سوالات مربوط، در فصل 7 كتاب Creative.
منبع
برای مطالعه بیشتر در این زمینه می توانید به کتاب ورودی به الگوریتم نوشته CLRS و ورودی به الگوریتم با نگرشی خلاقانه مراجعه کنید.
 
درس: مؤلفه‌هاي قوياً همبند


این درس شامل موارد زیر است:
تعاریف
  • گراف قویاً همبند: گراف جهت‌داری که بین هر دو راس u و v از آن، یک مسیر جهت‌دار از u به v و یک مسیر جهت‌دار از v به u وجود داشته باشد.
  • مؤلفه‌ی قویاً همبند: یک زیرگراف القایی ماکسیمال که قویا همبند است.
  • ترانهاده‌ی گراف G: یا همان GT که در آن راس u به راس v یال دارد اگر و فقط اگر در گراف G راس v به راس u یال داشته باشد.
مقدمه
يكي از استفاده‌هاي الگوريتم DFS، در پيدا كردن مؤلفه‌هاي قوياً همبند گراف‌هاي جهت‌دار است. در بسیاری مواقع لازم است که ابتدا گراف را به مؤلفه‌های قویا همبند تقسیم کنیم و بعد هر مؤلفه را جداگانه بررسی کنیم.
نکته‌ای که بسیاری اوقات مورد استفاده قرار می‌گیرد، اینست که اگر به جای هر مؤلفه یک راس بگذاریم، گراف حاصل یک DAG خواهد بود. و بطور معمول کار کردن با مؤلفه‌های قویاً همبند و یا یک DAG بسیار راحت‌تر خواهد بود.
قضیه
    1) دو راس u و v در یک مؤلفه قرار می‌گیرند اگرو فقط اگر در یک گشت بسته قرار داشته باشند.
    2) رئوس هر گراف جهت‌دار ( و نه یال‌های آن ) بصورت یکتا به مولفه‌های قویا همبند افراز می‌شوند.
اثبات این دو قضیه به‌عنوان تمرین در نظر گرفته می‌شود.
الگوریتم 1
هدف اینست که گراف جهت‌دار داده شده‌ی G را به مؤلفه‌های قویاً همبند آن تجزیه کنیم. برای مثال در شکل زیر گراف G دارای 3 مؤلفه‌ی قویاً همبند است.


یکی از الگوریتم‌های مناسب برای پیدا کردن مؤلفه‌های قویاً همبند، استفاده از دو DFS، یکی روی G و دیگری روی GT است. بنابر این ابتدا روی گراف G الگوریتم ِDFS را اجرا می‌کنیم و همانند الگوریتم مرتب سازی توپولوژیکال، به رئوس عدد DFS خروجی نسبت می‌دهیم. بطور مثال اجرای این الگوریتم روی راس b و بعد روی راس d در شکل بالا، ترتیب زیر را به رئوس می‌دهد:
d , h , c , b , e , a , f , g
حال به ترتیب این عدد گذاری (در حقیقت آخرین راسی که الگوریتم DFS از آن خارج شده، اولین راس است)، در گراف GT الگوریتم DFS را اجرا می‌کنیم. البته هر بار که DFS روی یک راس مثلا راس u تمام می‌شود، همه‌ی رئوسی که توسط این DFS بازدید شده‌اند را بعنوان مؤلفه‌ی قویا همبند راسu در نظر گرفته و این رئوس را حذف می‌کنیم. بطور مثال در شکل بالا ابتدا مؤلفه‌ی d , h , c بدست آمده و بعد مؤلفه‌ی b , a , e و در آخر مؤلفه‌ی f , g بدست می‌آید.
برای اثبات این الگوریتم باید ثابت کنیم که در هنگام اجرای DFS دوم از راس u، بعد از اتمام DFS، اولا همه‌ی رئوس مؤلفه‌ی قویا همبند u بازدید خواهند شد و ثانیاً هیچ راس دیگری بازدید نخواهد شد.
در اثبات شرط دوم، واضح است که همه‌ی رئوسی که مورد بازدید قرار می‌گیرند، در G به راس u مسیر دارند. کافی است ثابت کنیم راس u نیز به این رئوس مسیر داشته باشد. فرض کنید که یکی از این رئوس مانند v وجود داشته باشد که u به v مسیر ندارد. از آن جا که u به v مسیر ندارد نتیجه می‌دهد که در مرحله‌ی اول الگوریتم، DFS از راس u، راس v را بازدید نخواهد کرد. بنابراین چون جایگاه u در دنباله قبل از جایگاه v است، لذا در مرحله‌ی اول، DFS قبل از u، بر روی v اجرا شده است. اما چون v به u مسیر دارد، DFS نمی‌تواند قبل از تمام شدن در u، در v تمام شود و لذا جایگاه v باید قبل از جایگاه u در دنباله باشد که این تناقض است.
برای اثبات شرط اول، کافی است ثابت کنیم همه‌ی رئوسی که تا کنون توسط الگوریتم حذف شده‌اند، جایگاهشان قبل از u بوده‌است (در حقیقت همواره u راس ابتدای دنباله است). که این هم همانند حالت قبل، با توجه به این نکته که هیچ یالی در گراف G از راست به چپ در دنباله وجود ندارد، اثبات می‌شود.
لذا با اجرای یک الگوریتم DFS رو G، سپس بدست آوردن GT، و در آخر یک DFS روی GT می‌توانیم گراف را به مؤلفه‌های قویا همبندش تقسیم کنیم. و واضح است که هر 3 قسمت از O(n+e) هستند.
الگوریتم 2
شايد الگوريتم1 بدليل داشتن چند آرايه‌ي اضافي و پیچیدگی نسبی، چندان رضايت‌بخش نباشد. به همين منظور راه ساده‌تر و تاحدي قدرتمندتر براي اين الگوريتم، استفاده از DFS است. براي اين منظور كافي است تا به ترتيب دلخواهي رئوس را چك كنيم؛ اینگونه كه اگر تا به حال روی یک راس DFS اجرا نشده، DFS را برای آن راس اجرا می کنیم. البته تنها خاصيت اين DFS اينست كه در هنگام خروج از يك رأس (يعني هنگامي كه همه‌ي مجاوران آن رأس چک شده‌اند)، به آن رأس عدد dfs_num را نسبت مي‌دهد. عدد dfs_num در ابتداي الگوريتم به n مقداردهي مي‌شود و هر بار بعد از شماره‌گذاري يك رأس، مقدار آن يكي كاهش ميابد. حال ادعا مي‌كنيم كه شماره‌هاي نسبت داده شده به رئوس، همان شماره‌گذاري مطلوب است.
براي اثبات اين اگوريتم بايد ساختار درختي را -كه الگوريتم DFS مي‌سازد- بررسي كنيم. مي‌دانيم كه در الگوريتم DFS روي گراف‌هاي جهت‌دار، علاوه بر يال‌هاي يك رأس به زيردرخت خود و اجداد خود، بعضي يال‌هاي از راست به چپ نيز وجود دارند. يعني يال‌هايي از يك رأس، به رئوسي كه قبلاً مورد پيمايش قرار گرفته‌اند. اما هيچگاه يالي از چپ به راست وجود نخواهد داشت. همچنين در مورد مسئله‌ي ما بدليل بدون دور بودن گراف‌ها، يالي از يك رأس به اجداد آن نيز وجود نخواهد داشت. بنابراين هنگام خروج از راس u، همه‌ي يال‌هاي خروجي u يا:
    1) به زيردرخت رأس u هستند؛ كه در اين صورت، از رأس‌هاي زيردرخت u قبل از u خارج شده‌ايم و بنابراين شماره‌ي آن‌هاي بزرگتر از شماره‌ي u است؛ و يا
    2) به راس‌هايي غير از اجداد u كه قبلاً پيمايش شده‌اند؛ كه در اين صورت نيز شماره‌ي آن رأس‌ها بزرگتر از شماره‌ي u خواهد بود.
لذا همه‌ی مجاوران راس u قبل از u نام‌گذاری شده‌اند یعنی شماره‌ی بیشتری دارند. بنابراین با يك DFS از O(N+E) نيز مي‌توان الگوريتم مرتب‌سازي توپولوژيكال را پياده‌سازي كرد.
تمرین
    1. الگوريتم بالا را پياده‌سازي كنيد. 2. تمرينات مربوط، در بخش22.4 و 22 كتاب CLRS. 3. تمرينات مربوط، در فصل 7 كتاب Creative
منبع
برای مطالعه بیشتر در این زمینه می توانید به کتاب ورودی به الگوریتم نوشته CLRS و ورودی به الگوریتم با نگرشی خلاقانه مراجعه کنید.
 
درس: مرتب‌سازي توپولوژيكال


این درس شامل موارد زیر است:
تعاریف
  • DAG: گراف جهت‌دار که هیچ دور جهتداری ندارد به اختصار DAG می‌نامند.
مقدمه
در بسياري از مواقع مسئله‌ي ما شامل تعدادي كار، همراه با نيازمندي آن كارهاست و هدف رسيدگي به همه‌ي آن كارهاست. در نوع خاصي از اين گونه مسائل، نيازمندي‌هاي كارها، كارهايي از همان نوع است. بطور مثال در هنگام تعريف يك داده‌ي جديد در برنامه، بايد همه‌ي داده‌هاي استفاده شده در تعريف، قبلا تعريف شده باشند.
در چنين مسائلي، داشتن ترتيبي براي رسيدگي به كارها – كه رعايت نيازمندي‌ها را تضمين كند – بسيار مهم است. به چنين مرتب‌سازي كارها، مرتب‌سازي توپولوژيكال مي‌گويند. البته آنچنان كه از تعريف نيازمندي‌ها برمي‌آيد، روابط نيازمندي نمي‌توانند تشكيل يك دور بدهند چراكه در اين صورت تعدادي ازكارها هرگز نمي‌توانند شروع شوند.
قضیه
در یک DAG حداقل یک راس با درجه‌ی خروجی صفر وجود دارد.
اثبات تقریبا واضح است و به‌عنوان تمرین در نظر گرفته می‌شود.
الگوریتم 1
مسئله‌ي ما به اين صورت تعريف مي‌شود:
    در یک DAG، رأس‌ها را از 1 تا n طوري شماره‌گذاري كنيد كه همه‌ي رئوسي كه به رأس با شماره‌ي i يال دارند، شماره‌اي كوچكتر از i داشته باشند.
در حقيقت چنين گرافي، با گذاشتن يك رأس به جاي هر كار و گذاشتن يك يال جهت‌دار ازi به j در صورتي كه كار نظير j نيازمند كار نظير i باشد، بدست مي‌آيد و در چنین چینشی همه‌ی یال‌ها از چپ به راست خواهند بود. همچنین واضح است كه با داشتن چنين چينشي، مي‌توان كارها را به ترتيب شماره‌ها انجام داد بطوريكه همواره قبل از شروع يك كار، كارهاي نيازمند آن انجام شده‌باشند. حال سعي مي‌كنيم الگوريتمي بدهيم كه با داشتن يك DAG، شماره‌گذاري مطلوبي براي رأس‌هاي آن پيدا كند.
اولين راه، استفاده از استقراست. از آن‌جا كه حذف يك رأس از DAG، باز هم خاصيت بدون دور بودن گراف را حفظ مي‌كند؛ لذا با استقرا بر روي تعداد رئوس DAG، مي‌توان با حذف هر كدام از رئوس به حالت فرض استقرا رسيد (البته حالت پايه‌ي استقرا نيز كه بديهي است). بنابراين بايد بدنبال رأسي باشيم كه بعد از حذف آن، براحتي بتوان آن را به دنباله‌ي بدست آمده از فرض استقرا اضافه كرد. مسلما رأسي با درجه‌ي خروجی صفر انتخاب مناسبي است. چرا كه ميتواند به راحتي در انتهای چینش رئوس قرار گيرد. همچنين بنابر قضیه‌ی 1 همواره چنین راسی را می‌توان پیداکرد. پس الگوريتم ما تا رسيدن به گراف تهي اين كار را تكرار مي‌كند كه هر بار يك رأس با درجه‌ي خروجی صفر را به ابتدای دنباله‌ اضافه مي‌كند و پس از حذف آن راس، الگوریتم را در بقیه‌ی گراف اجرا می‌کند.
    نكته: همه‌ي اين الگوريتم را مي‌توان بطور مشابه، با حذف رئوس با درجه‌ي ورودی صفر اجرا كرد.
تقريبا واضح است كه با در نظر گرفتن روشي مناسب براي پيدا كردن راس‌هاي با خروجي صفر، پيچيدگي زمان اجراي اين الگوريتم از O(N+E) است و پیاده‌سازی آن بعنوان تمرین درنظر گرفته می‌شود.
الگوریتم 2
شايد الگوريتم1 بدليل داشتن چند آرايه‌ي اضافي و پیچیدگی نسبی، چندان رضايت‌بخش نباشد. به همين منظور راه ساده‌تر و تاحدي قدرتمندتر براي اين الگوريتم، استفاده از DFS است. براي اين منظور كافي است تا به ترتيب دلخواهي رئوس را چك كنيم؛ اینگونه كه اگر تا به حال روی یک راس DFS اجرا نشده، DFS را برای آن راس اجرا می کنیم. البته تنها خاصيت اين DFS اينست كه در هنگام خروج از يك رأس (يعني هنگامي كه همه‌ي مجاوران آن رأس چک شده‌اند)، به آن رأس عدد dfs_num را نسبت مي‌دهد. عدد dfs_num در ابتداي الگوريتم به n مقداردهي مي‌شود و هر بار بعد از شماره‌گذاري يك رأس، مقدار آن يكي كاهش ميابد. حال ادعا مي‌كنيم كه شماره‌هاي نسبت داده شده به رئوس، همان شماره‌گذاري مطلوب است.
براي اثبات اين اگوريتم بايد ساختار درختي را -كه الگوريتم DFS مي‌سازد- بررسي كنيم. مي‌دانيم كه در الگوريتم DFS روي گراف‌هاي جهت‌دار، علاوه بر يال‌هاي يك رأس به زيردرخت خود و اجداد خود، بعضي يال‌هاي از راست به چپ نيز وجود دارند. يعني يال‌هايي از يك رأس، به رئوسي كه قبلاً مورد پيمايش قرار گرفته‌اند. اما هيچگاه يالي از چپ به راست وجود نخواهد داشت. همچنين در مورد مسئله‌ي ما بدليل بدون دور بودن گراف‌ها، يالي از يك رأس به اجداد آن نيز وجود نخواهد داشت. بنابراين هنگام خروج از راس u، همه‌ي يال‌هاي خروجي u يا:
  1. به زيردرخت رأس u هستند؛ كه در اين صورت، از رأس‌هاي زيردرخت u قبل از u خارج شده‌ايم و بنابراين شماره‌ي آن‌هاي بزرگتر از شماره‌ي u است؛ و يا
  2. به راس‌هايي غير از اجداد u كه قبلاً پيمايش شده‌اند؛ كه در اين صورت نيز شماره‌ي آن رأس‌ها بزرگتر از شماره‌ي u خواهد بود.
لذا همه‌ی مجاوران راس u قبل از u نام‌گذاری شده‌اند یعنی شماره‌ی بیشتری دارند. بنابراین با يك DFS از O(N+E) نيز مي‌توان الگوريتم مرتب‌سازي توپولوژيكال را پياده‌سازي كرد.
تمرین
  1. الگوريتم بالا را پياده‌سازي كنيد.
  2. تمرينات مربوط، در بخش22.4 و 22 كتاب CLRS.
  3. تمرينات مربوط، در فصل 7 كتاب Creative
منبع
برای مطالعه بیشتر در این زمینه می توانید به کتاب ورودی به الگوریتم نوشته CLRS و ورودی به الگوریتم با نگرشی خلاقانه مراجعه کنید.
 
 
Top پرسش و پاسخ:
•  در این بخش پاسخ پرسش‌های پرسیده‌شده نگاشته می‌شود.
  • پرسش: نتایج مرحله‌ی اوّل شانزدهمین دوره (سال ۱۳۸۵) چه زمانی و چگونه اعلام می‌شود؟
  • پاسخ: نیمه‌ی اسفندماه و از طریق سایت باشگاه و احتمالاً یکی از روزنامه‌های کثیرالانتشار.
  • پرسش: حداقل امتیاز لازم برای قبولی در مرحله‌ی اوّل چند نمره است؟
  • پاسخ: بسته به عمل‌کرد سایر شرکت‌کنندگان و درجه‌ی سختی سؤالات این حدنصاب متغیر بوده و میزان از پیش تعیین شده‌ای نمی‌باشد. با این حال، پیش‌بینی می‌شود این میزان کمتر از نصف حداکثر امتیاز قابل اکتساب باشد.
  • پرسش: در مورد دوره‌ی نوروزی: لطفاً مسائی واکنشی (دسته‌ی دوّم) را بیشتر توضیح دهید و اگر ممکن است یک مثال بزنید.
  • پاسخ: به عنوان مثال این سوال را که در المپیاد جهانی ۲۰۰۰ مطرح شده است، در نظر بگیرید: به شما یک فایل از نوع median.o و یک فایل از نوع median.h داده می‌شود که این توابع را دارند:
    int get_n();
    int median(int i, int j, int k);
    
    void report(int m);
    
    مسئله به این صورت است که ما یک آرایه‌ی حداکثر ۱۵۰۰ عضوی داریم که شما باید اندیس عنصر میانه‌ی آن را پیدا کنید. برای این کار اگر تابع اول را صدا کنید اندازه‌ی آرایه را دریافت خواهید کرد؛ با اجرای تابع دوم اندیس عنصر میانه ی سه عنصری که اندیس آن را به تابع داده اید را دریافت خواهید کرد (یکی از سه پارامتر تابع)؛ و با اجرای تابع سوم اندیس جواب را اعلام خواهید کرد و برنامه ی شما متوقف خواهد شد. بدیهی است که اجرای هر یک از سه تابع با پارامترهای بی معنا باعث گرفتن نمره‌ی ۰ از تست مورد نظر خواهد شد، در ضمن تعداد اجراهای تابع دوم نباید از ۷۰۰ بار بیشتر باشد.
    شما باید با فرض این‌که این توابع در median.o (که یک object file است)، پیاده‌سازی شده‌اند (و البته کد آن‌ها در دسترس نیست)، فایل median.h را (که یک header file است) در ابتدای برنامه‌ی خودتان include کرده و به هنگام اجرا نیز با لینک‌کردن median.o در دستور کامپایل، برنامه‌ی خود را تست کنید.


Copyright © 2005, 2006. All rights reserved for committee of olympiad in informatics.
Designed by Aideen NasiriShargh