الگوریتم کساراجو
الگوریتم کساراجو الگوریتمی است که به منظور پیدا کردن مولفههای قویا همبند در یک گراف جهت دار استفاده میشود. این الگوریتم توسط سامباسیوا کساراجو در سال ۱۹۷۸ در یک مقاله ارائه شد. کساراجو این الگوریتم را با استفاده از این نکته که مؤلفههای قویا همبند در یک گراف جهتدار با مؤلفههای قویا همبند در معکوس آن گراف جهتدار برابر هستند، ارائه کرد. الگوریتم دیگری که بر پایه همین الگوریتم است، الگوریتم تارجان است که در واقع نسخهٔ بهبودیافته الگوریتم کساراجو است. در حدود ۲۰ سال بعد یعنی در سال ۱۹۹۶ نسخهٔ بهبودیافتهٔ دیگری از این الگوریتم، توسط هارولد گابو منتشر شد.
رده | الگوریتم گراف |
---|---|
ساختمان داده | گراف و پشته |
کارایی بدترین حالت |
مفاهیم
- گراف قویا همبند
به گرافی گویند که هر جفت راس آن از یکدیگر قابل دسترس باشد. مانند شکل زیر:
- مؤلفه قویا همبند:
مولفه قویا همبند یک گراف به بیشینه زیرمجموعهای از رأسها گویند که قویا همبند هستند. یعنی از هر جفت راس در آن مجموعه به یکدیگر مسیر مستقیم وجود دارد. مانند شکل زیر:
- گراف معکوس:
گراف معکوس یا همان G که
در آن راس u به راس v یال دارد اگر و فقط اگر در گراف G راس v به راس u یال داشته باشد.
یعنی مجموعه یالهای آن به صورت زیر است:
{ E = { (u,v) | (v,u) ∈ E
مانند شکل زیر:
الگوریتم
این الگوریتم از یک نکتهٔ بسیار ساده استفاده میکند و آن این است که مؤلفههای قویا همبند یک گراف جهتدار با مؤلفههای قویا همبند معکوس آن گراف جهتدار یکی است، این نکته پایهٔ اصلی این الگوریتم است.
ابتدا یک پشته خالی در نظر میگیریم. گراف جهتدار داده شده را، G در نظر میگیریم.
این گراف را پیمایش میکنیم. پیمایش این گراف را از یک راس دلخواه به وسیلهٔ یکی از الگوریتمهای پیمایش گراف یعنی جستجوی عمق اول انجام میدهیم. وقتی الگوریتم جستجوی عمق اول
کار خود را با یک راس به اتمام رساند، آن را در داخل پشته قرار میدهیم. به همین ترتیب ادامه میدهیم تا کل گراف پیمایش شود.
در این مرحله تمامی رأسها در داخل پشته قرار دارند.
سپس گراف معکوس G یعنی G را به دست میآوریم. این بار گراف معکوس را با توجه به اولویت رأسها در پشته، پیمایش میکنیم. به این ترتیب که در ابتدا از راسی شروع میکنیم که در بالای پشته
قرار دارد. فرض کنید راس v در بالای پشته است. جستجوی عمق اول را از آن راس شروع میکنیم. فرض کنید در راسی به نام w به پایان برسد. تمام راسهایی که در این بین ملاقات شده جزو یک مؤلفههای قویا همبند هستند. تمام رأسهای ملاقات شده را از پشته خارج و همین الگوریتم را بر روی راسی که در بالای پشته قرار دارد، تکرار میکنیم، تا زمانی که پشته خالی شود.
مثال
برای درک بیشتر الگوریتم مثال زیر را در نظر بگیرید. فرض کنید میخواهیم مؤلفههای قویا همبند را در گراف شکل زیر پیدا کنیم.
ابتدا گراف مورد نظر را با استفاده از الگوریتم جستجوی عمق اول با شروع از راس A پیمایش میکنیم.
مطابق آنچه در توضیح الگوریتم گفتیم بعد از اجرای جستجوی عمق اول بر روی گراف از راس A، راس H وارد پشته میشود.
به این خاطر که جستجوی عمق اول در راس H متوقف میشود. بعد از آن، به ترتیب رأسهای F - E - C - G وارد پشته میشوند. سپس جستجوی عمق اول بار دیگر از A
ادامه پیدا کرده و در انتها، پشته به صورت زیر پر میشود:
سر پشته --> [H, G, C, E, F, D, B, A]
پس از آن معکوس گراف را محاسبه میکنیم. معکوس گراف مورد نظر مطابق شکل زیر است:
حال مطابق آنچه در توضیح الگوریتم گفته شد، جستجوی عمق اول را بر روی گراف معکوس، با شروع از راسی که در سر پشته است (یعنی A)، اجرا میکنیم. با پیمایش گراف از راس A، تنها دو راس B و D ملاقات میشوند؛ بنابراین ۳ راس A و B و D، تشکیل یک مؤلفهٔ قویا همبند میدهند. پس کافی است این ۳ راس را mark کرده (از پشته و گراف خارج کنیم) و الگوریتم را ادامه دهیم. راس F اکنون سر پشته است و قبلاً ملاقات (mark) نشدهاست. پس الگوریتم جستجوی عمق اول را بر روی آن اجرا میکنیم و F و E را به عنوان یک مؤلفهٔ قویا همبند دیگر، گزارش میکنیم. با ادامه الگوریتم H و G و C نیز که در پشته باقیماندهاند، به عنوان آخرین مؤلفهٔ قویا همبند گزارش میکنیم.
اثبات درستی الگوریتم
در نگاهی عمیقتر به الگوریتم، میتوان متوجه شد که هر چه یک راس در قسمت بالاتری از پشته قرار گیرد، به این معناست که در زمان دیرتری مراحل الگوریتم بر روی آن به پایان رسیدهاست. به بیان دیگر اگر (f(u زمان به پایان رسیدن مراحل جستجوی عمق اول بر روی راس u نظر گرفته شود، هر چه راس u در پشته بالاتر (به سر پشته نزدیکتر) باشد، دارای (f(u بزرگتری نیز است. برای به دست آوردن f مربوط به هر راس، کافی است الگوریتم جستجوی عمق اول را به صورت زیر تغییر دهیم:
Algorithm DFS(G) 1 for each vertex u ∈ V [G] 2 mark U as not visited 3 time ← 0 4 for each vertex u ∈ V [G] 5 do if u is unvisited 6 then DFS-Visit (u)
Algorithm DFS-Visit(u) 1 mark u as visited 2 d[u] ← time 3 time + 1 4 for each v ∈ Adj[u] 5 do if v is unvisited 6 then DFS-Visit (v) 7 f [u] ← time 8 time + 1
پس الگوریتم کساراجو در واقع به این صورت است که
- الگوریتم جستجوی عمق اول را بر روی گراف G اجرا میکند و (f(u را به ازای تمام رأسها بدست میآورد. (پشته در الگوریتم کساراجو نقش ترتیب نزولی (f(uها به ازای تمامی رأسها است)
- معکوس گراف G، یعنی G را محاسبه میکند.
- الگوریتم جستجوی عمق اول را بر روی G اجرا میکند ولی رأسها را با اولویت (f(u با ترتیب نزولی در نظر میگیرد (همان ترتیب رأسها در پشته)
- رأسها را در هر درخت حاصل از جستجوی عمق اول به عنوان یک مؤلفه قویا همبند (SCC) چاپ میکند.
تعریف
به ازای هر زیر مجموعهای از رأسها مانند U:
f(U) = max u∈U { f(u) }
لم
فرض کنید C1 و C2 دو مؤلفه متمایز قویا همبند در گراف G باشند و وجود دارد یک یالی از راس u به راس v بهطوریکه u ∈ C1 و v ∈ C2 در این صورت (f(C1 بزرگتر از (f(C2 است. اثبات: دو حالت وجود دارد:
- ابتدا C1 توسط الگوریتم جستجوی عمق اول ملاقات شود:
در ابتدا تمامی رأسهای موجود در C1 ملاقات شده و سپس از طریق یال u به v، رأسهای C2 ملاقات میشوند. چون C2 یالی به خارج از خود ندارد پس در ابتدا رأسهای آن به پایان میرسد و سپس رأسهای C1 به پایان میرسد.
- ابتدا C2 توسط الگوریتم جستجوی عمق اول ملاقات شود
در ابتدا تمامی رأسهای موجود در C2 ملاقات شده و چون C2 یالی به خارج از خود ندارد پس الگوریتم در همان مؤلفه به پایان میرسد. سپس با شروع از رأسهای C1 به کار خود ادامه میدهد.
بنابراین در هر دو حالت (f(C1 بزرگتر از (f(C2 خواهد بود.
نتیجه
یک نتیجه حاصل از این لم، این است که اگر در گراف G، داشته باشیم (f(C1 بزرگتر از (f(C2، در گراف معکوس یعنی G، برعکس خواهد بود و (f(C1 کوچکتر از (f(C2 خواهد بود.
چون مؤلفههای قویا همبند در G و G برابر هستند و تنها جهت یالها عوض خواهد شد.
بنابراین چون این الگوریتم در ابتدا (f(uها را محاسبه میکند و سپس در G، الگوریتم جستجوی عمق اول را از راسی که بیشینه (f(u را در گراف G دارد، اجرا میکند، این راس یعنی
u مطابق نتیجهای که در بالا گرفته شد، دارای کمینه (f(u است.
به این ترتیب، اگر مؤلفهای که u در آن وجود دارد را C3 بنامیم، الگوریتم جستجوی عمق اول در ابتدا تمامی همسایگان u را در C3 ملاقات میکند. حال ادعا میکنیم که جستجوی عمق اول در این مرحله به پایان میرسد. چون در غیر این صورت باید از C3 به یکی دیگر از مؤلفهها مانند Ci یال وجود داشته باشد. درحالیکه از C3 به هیچ مؤلفه دیگری مانند Ci، یال وجود ندارد. چون اگر یالی از یک راس در C3 به
یک راس در مؤلفه دیگر مانند Ci وجود داشت، طبق لم بالا، (f(C3 از (f(Ci بزرگتر بود که این با کمینه بودن (f(C3 تناقض است؛ بنابراین الگوریتم به درستی اولین مؤلفه قویا همبند را مشخص کرد. در مرحله بعد (f(Ci به گونهای انتخاب میشود که در گراف G، از (f(C مربوط به تمامی مؤلفهها به جز (f(C3 بزرگتر است.
به این ترتیب الگوریتم به درستی کار خود را به پایان میبرد.
تحلیل زمانی
همانطور که در توضیح الگوریتم گفته شد، الگوریتم شامل ۳ مرحله است:
- پیمایش گراف و پر کردن پشته
- معکوس کردن گراف
- پیمایش گراف با توجه به اولویت رأسها با استفاده از پشته
در صورتی که گراف به وسیله ماتریس مجاورت پیادهسازی شود، زمان اجرای این الگوریتم از (|O(|V خواهد بود.
از طرف دیگر میدانیم اگر گراف به وسیله لیست مجاورت پیادهسازی شود، الگوریتم جستجوی عمق اول به زمان (|O(|V|+|E نیاز دارد که در آن V تعداد راسها و E تعداد یال هاست.
برای معکوس کردن گراف نیز کافی است یک بار گراف را به وسیله جستجوی اول عمق پیمایش کرده و در گذر از هر یال جهت آن یال را عوض کنیم. پس آن نیز به (|O(|V|+|E
زمان نیاز دارد.
پس در کل ۳ بار جستجوی اول عمق نیاز داریم. میتوان مرحله اول و مرحله معکوس کردن را
در یک بار پیمایش گراف انجام دهیم به این ترتیب تنها به دو جستجوی اول عمق نیاز داریم
پس در کل زمان اجرا برابر(|O(|V|+|E است.
پیادهسازی
شبهکد الگوریتم کساراجو به صورت زیر است:
1 Algorithm Kosaraju(G,v) 2 Input: G=(V,E),) 3 Output: strongly connected component (SCC) 4 begin 5 while(stack does not contain all vertices) 6 Choose an arbitrary vertex v not in S 7 Depth First Search(G,V) 8 Each time that depth-first search finishes expanding a vertex u, push u onto S 9 compute G 10 While(stack is not empty) 11 pop vertex v form stack 12 Depth First Search(G,v) 13 Output set of visited vertices as a seprate SCC 14 mark this set of vertices form stack and graph 12 end
پیادهسازی الگوریتم با استفاده از جاوا:
import java.util.ArrayList;
public class Kosaraju {
private ArrayList<Node> stack;
public ArrayList<ArrayList<Node>> getSCC(Node root, AdjacencyList list){
stack = new ArrayList<Node>();
// search the graph (depth-first search), adding nodes to the stack
search(root, list, true);
// reverse all the edges in the graph
list.reverseGraph();
// search the graph again in the stack's order
ArrayList<ArrayList<Node>> SCC = new ArrayList<ArrayList<Node>>();
while(!stack.isEmpty()){
ArrayList<Node> component = new ArrayList<Node>();
search(stack.get(0), list, false);
// any components we visited are strongly connected
// remove them from the stack and add them to the component
for(Iterator<Node> it = stack.iterator(); it.hasNext(); ){
Node n = it.next();
if(!n.visited){
component.add(n);
it.remove();
}
}
// add the component to the result set
SCC.add(component);
}
return SCC;
}
private void search(Node root, AdjacencyList list, boolean forward){
root.visited = forward;
if(list.getAdjacent(root) == null){
if(forward) stack.add(0, root);
return;
}
for(Edge e : list.getAdjacent(root)){
if(e.to.visited != forward){
search(e.to, list, forward);
}
}
if(forward) stack.add(0, root);
}
}
کاربرد
پیدا کردن مؤلفههای قویا همبند از مهمترین مسایل در نظریه گراف است. با بدست آوردن گراف مؤلفههای قویا همبند، بسیاری از مسایل نظریه گراف به راحتی قابل حل است. مانند:
که همه این مسایل با داشتن گراف مؤلفههای قویا همبند، در (|O(|E قابل حل است.
علاوه بر این مسئله مؤلفههای قویا همبند در مخابرات و شبکه نیز بسیار کاربرد دارد.
جستارهای وابسته
منابع
- ↑ «Kosaraju's algorithm - Algowiki». بایگانیشده از اصلی در ۱۶ نوامبر ۲۰۱۰. دریافتشده در ۳۰ دسامبر ۲۰۱۳.