مثال یافتن کوتاه ترین مسیر را در نظر بگیرید. شبکه ای از بزرگراه ها که مناطق شهر را به هم وصل می کند داده شده است. برخی از جاده ها یک طرفه هستند. کوتاه ترین مسیرها را از مرکز شهر تا هر شهر در منطقه پیدا کنید.

برای حل این مشکل می توانید استفاده کنید الگوریتم دایکسترا- الگوریتمی بر روی نمودارها که توسط دانشمند هلندی E. Dijkstra در سال 1959 اختراع شد. کمترین فاصله را از یکی از رئوس نمودار تا بقیه را پیدا می کند. فقط برای نمودارهای بدون لبه وزن منفی کار می کند.

اجازه دهید لازم باشد کوتاهترین فواصل را از راس اول تا بقیه را پیدا کنید.

دایره ها راس ها را نشان می دهند، خطوط مسیرهای بین آنها (لبه های نمودار) را نشان می دهند. تعداد رئوس در دایره ها نشان داده شده است، وزن آنها در بالای لبه ها نشان داده شده است - طول مسیر. در کنار هر راس، یک برچسب قرمز مشخص شده است - طول کوتاه ترین مسیر به این راس از راس 1.

برچسب خود راس 1 0 در نظر گرفته می شود، برچسب های راس های باقی مانده غیرقابل دسترسی هستند. عدد بزرگ(در حالت ایده آل - بی نهایت). این نشان می دهد که فاصله راس 1 تا رئوس دیگر هنوز مشخص نیست. تمام رئوس نمودار به عنوان بازدید نشده مشخص می شوند.

گام اول

رئوس 1 دارای حداقل برچسب است راس های 2 و 3 و 6 همسایه های آن هستند و به نوبه خود همسایه های راس را دور می زنیم.

اولین همسایه راس 1 راس 2 است زیرا طول مسیر به آن حداقل است. طول مسیر به آن از طریق راس 1 برابر است با مجموع کوتاه ترین فاصله تا راس 1، مقدار برچسب آن و طول یال که از 1 به 2 می رود، یعنی 0 + 7 = 7. این کمتر از برچسب فعلی راس 2 (10000) است، بنابراین برچسب جدید راس دوم 7 است.


به طور مشابه، طول مسیر را برای همه همسایگان دیگر (راس 3 و 6) پیدا می کنیم.

همه همسایگان گره 1 بررسی می شوند. حداقل فاصله فعلی تا قله 1 نهایی در نظر گرفته شده و قابل بازنگری نیست. راس 1 به عنوان بازدید شده علامت گذاری شده است.

مرحله دوم

مرحله 1 الگوریتم تکرار می شود. دوباره "نزدیک ترین" رئوس بازدید نشده را پیدا می کنیم. این راس 2 با برچسب 7 است.

مجدداً سعی می کنیم برچسب های همسایه های راس انتخاب شده را کاهش دهیم و سعی می کنیم آنها را از راس دوم عبور دهیم. همسایگان راس 2 رئوس 1، 3 و 4 هستند.

Vertex 1 قبلاً بازدید شده است. همسایه بعدی راس 2، راس 3 است، زیرا دارای حداقل برچسب رئوس است که به عنوان بازدید نشده مشخص شده است. اگر از طریق 2 به آن بروید، طول چنین مسیری برابر با 17 خواهد بود (7 + 10 = 17). اما برچسب فعلی راس سوم 9 و 9 است< 17, поэтому метка не меняется.


همسایه دیگر راس 2، راس 4 است. اگر از طریق دوم به آن برویم، طول چنین مسیری برابر با 22 خواهد بود (7 + 15 = 22). از 22<10000, устанавливаем метку вершины 4 равной 22.

همه همسایگان راس 2 مشاهده شده اند، بنابراین ما آن را به عنوان بازدید شده علامت گذاری می کنیم.

مرحله سوم

مرحله الگوریتم را با انتخاب راس 3 تکرار می کنیم. پس از پردازش آن، نتایج زیر را به دست می آوریم.

مرحله چهارم

مرحله پنجم

قدم ششم


بنابراین، کوتاه ترین مسیر از راس 1 تا راس 5، مسیر عبور از رئوس خواهد بود 1 — 3 — 6 — 5 ، زیرا به این ترتیب حداقل 20 وزن اضافه می کنیم.

بیایید نگاهی به کوتاه ترین مسیر بیندازیم. طول مسیر هر راس را می دانیم و حالا رئوس را از انتهای آن در نظر می گیریم. راس نهایی را در نظر بگیرید (در این مورد، راس 5 ) و برای تمام رئوس هایی که با آنها مرتبط است، با کم کردن وزن یال مربوطه از طول مسیر راس نهایی، طول مسیر را پیدا می کنیم.
بله، بالا 5 طول مسیر دارد 20 . او به بالا متصل است 6 و 4 .
برای بالا 6 وزن را بدست آورید 20 - 9 = 11 (مطابقت).
برای بالا 4 وزن را بدست آورید 20 - 6 = 14 (تطابق نداشت).
اگر در نتیجه مقداری به دست آوریم که با طول مسیر راس مورد نظر مطابقت داشته باشد (در این مورد، راس 6 ، سپس از آن بود که انتقال به راس نهایی انجام شد. این راس را در مسیر مورد نظر علامت گذاری می کنیم.
در مرحله بعد، لبه ای را که از طریق آن به راس رسیدیم تعیین می کنیم 6 . و به همین ترتیب تا زمانی که به ابتدا برسیم.
اگر در نتیجه چنین پیمایشی، در مرحله ای مقادیر یکسانی برای چندین راس داشته باشیم، می توانیم هر یک از آنها را انتخاب کنیم - چندین مسیر دارای طول یکسان خواهند بود.

پیاده سازی الگوریتم دایکسترا

برای ذخیره وزن نمودار از ماتریس مربع استفاده می شود. رئوس نمودار در سرفصل سطرها و ستون ها قرار دارند. و وزن کمان های نمودار در خانه های داخلی جدول قرار می گیرند. نمودار حاوی حلقه نیست، بنابراین مورب اصلی ماتریس حاوی مقادیر صفر است.

1 2 3 4 5 6
1 0 7 9 0 0 14
2 7 0 10 15 0 0
3 9 10 0 11 0 2
4 0 15 11 0 6 0
5 0 0 0 6 0 9
6 14 0 2 0 9 0

پیاده سازی در C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

#define _CRT_SECURE_NO_WARNINGS
#عبارتند از
#عبارتند از
#SIZE 6 را تعریف کنید
int main()
{
int a; // ماتریس اتصالات
int d; // حداقل فاصله
در تلویزیون؛ // رئوس بازدید شده
int temp, miniindex, min;
int begin_index = 0;
system("chcp 1251");
system("cls");
// اولیه سازی ماتریس رابطه
برای (int i = 0; i {
a[i][i] = 0;
برای (int j = i + 1; j printf( "فاصله %d - %d را وارد کنید:", i + 1, j + 1);
scanf("%d" , &temp);
a[i][j] = دما;
a[j][i] = دما;
}
}
// نمایش ماتریس رابطه
برای (int i = 0; i {
برای (int j = 0; j printf("%5d " , a[i][j]);
printf("\n");
}
//راس ها و فواصل را راه اندازی کنید
برای (int i = 0; i {
d[i] = 10000;
v[i] = 1;
}
d=0;
// مرحله الگوریتم
انجام دادن(
حداقل شاخص = 10000;
حداقل = 10000;
برای (int i = 0; i { // اگر راس هنوز بازدید نشده باشد و وزن آن کمتر از min باشد
اگر ((v[i] == 1) && (d[i] { // تخصیص مجدد مقادیر
min = d[i];
miniindex=i;
}
}
// حداقل وزن پیدا شده را اضافه کنید
// به وزن راس فعلی
// و با حداقل وزن راس فعلی مقایسه کنید
اگر (minindex != 10000)
{
برای (int i = 0; i {
اگر (a[i] > 0)
{
temp = min + a[i];
اگر (دمای< d[i])
{
d[i] = دما;
}
}
}
v = 0;
}
) در حالی که (minindex< 10000);
// نمایش کوتاه ترین فواصل تا رئوس
printf( "\nکوتاهترین فاصله تا رئوس: \n");
برای (int i = 0; i printf("%5d " , d[i]);

// بازیابی مسیر
درونی؛ // آرایه از رئوس بازدید شده
int end = 4; // شاخص راس انتهایی = 5 - 1
ver = پایان + 1; // عنصر شروع - راس پایان
int k = 1; // شاخص پیک قبلی
وزن int = d; // وزن راس انتهایی

در حالی که (پایان != begin_index) // تا زمانی که به راس شروع برسیم
{
برای (int i = 0; i // به همه رئوس نگاه کنید
اگر (a[i] != 0) // اگر ارتباطی وجود دارد
{
int temp = وزن - a[i]; // وزن مسیر را از راس قبلی تعیین کنید
اگر (دمای == d[i]) // اگر وزن با وزن محاسبه شده مطابقت داشته باشد
{ // یعنی انتقالی از این رأس وجود داشته است
وزن = دما // وزن جدید را ذخیره کنید
پایان = من // راس قبلی را ذخیره کنید
ver[k] = i + 1; // و آن را در یک آرایه بنویسید
k++;
}
}
}
// نمایش مسیر (راس شروع در انتهای آرایه k عناصر است)
printf( "\nخروجی کوتاهترین مسیر\n");
برای (int i = k - 1; i >= 0; i--)
printf("%3d " , ver[i]);
getchar(); getchar();
بازگشت 0;
}


نتیجه اجرا


بازگشت:

الگوریتم Dijkstra یک الگوریتم نموداری است که توسط دانشمند هلندی E. Dijkstra در سال 1959 اختراع شد. این الگوریتم کوتاهترین فاصله را از یکی از رئوس نمودار تا بقیه را پیدا می کند. الگوریتم فقط برای نمودارهایی بدون یال وزن منفی کار می کند. به طور گسترده در برنامه نویسی استفاده می شود و فناوری هایی مانند OSPF از آن برای حذف مسیرهای حلقه بک استفاده می کند که به عنوان کوتاه ترین مسیر اول نیز شناخته می شود.

الگوریتم دایکسترا مسئله کوتاه ترین مسیر تک راس را برای یک گراف جهت دار وزن دار G = (V, E) با راس اولیه s حل می کند که در آن وزن تمام یال ها غیرمنفی است ((u, v) ? 0 برای همه (u) ، v) E). در مواردی که لبه های نمودار برابر نیستند، توصیه می شود از این الگوریتم استفاده کنید.

فرمول وظیفه.یک نمودار وجود دارد. برخی از رئوس آن به عنوان راس 1 تعیین می شوند. لازم است حداقل مسیرهای راس 1 به هر یک از رئوس نمودار را پیدا کنید. حداقل مسیر، مسیری است که حداقل مجموع قیمت ها در طول مسیر وجود دارد. قیمت یک عدد غیر منفی است که وزن لبه است.

ایده الگوریتم.این ایده بر اساس عبارت واضح زیر است: اجازه دهید مسیر حداقل از راس آبه راس B. و اجازه دهید راس B به تعدادی از رئوس i متصل شود. هزینه مسیر از راس B تا راس i را با C i نشان دهید. بیایید حداقل مقدار را از C i انتخاب کنیم. سپس حداقل ادامه مسیر از نقطه B از مقدار انتخاب شده عبور می کند.

این گفته واقعاً نیازی به اثبات ندارد. و عواقبی بسیار جدی از آن به دنبال دارد. بگذارید مجموعه ای از رئوس وجود داشته باشد که حداقل مسیرها قبلاً از آن عبور می کنند. وجود چنین مجموعه ای تضمین شده است، این راس 1 است. عبارت فرموله شده در بالا این امکان را فراهم می کند که یک راس دیگر را به مجموعه رئوس موجود اضافه کنید (در ادامه آنها را انتخاب شده می نامیم)، و از آنجایی که تعداد رئوس در نمودار وجود دارد. متناهی است، سپس در تعداد محدودی از مراحل تمام رئوس نمودار انتخاب می شود و این راه حل خواهد بود.

ماهیت الگوریتم Dijkstra رویه اضافه کردن یک راس دیگر به مجموعه راس های انتخاب شده است. این روش شامل دو مرحله است:

1. مجموعه‌ای از رئوس را می‌سازیم که با رئوس انتخاب‌شده برخورد می‌کنند و از بین آن‌ها راس با کمترین قیمت را پیدا می‌کنیم. راس یافت شده به مجموعه موارد انتخاب شده اضافه می شود.

2. مجموعه ای از رئوس برخورد با راس انتخاب شده را می سازیم و قیمت های جدیدی را برای آنها تعیین می کنیم. قیمت جدید یک راس حداقل هزینه مسیر از مجموعه رئوس انتخاب شده تا راس داده شده است. قیمت جدید به این صورت ساخته شده است:

آ. برای یک راس انتخاب نشده در مجموعه رئوس انتخاب شده، زیر مجموعه ای از رئوس که به این راس برخورد می کنند تعیین می شود.

ب برای هر رأس زیرمجموعه انتخاب شده، هزینه مسیر به یک مورد داده شده تعیین می شود.

ج. حداقل قیمت تعیین می شود. این قیمت تبدیل به بالاترین قیمت می شود.

این الگوریتم با دو نوع قیمت کار می کند: قیمت لبه و قیمت بالا. قیمت لبه ثابت است. قیمت پیک ها دائماً مجدداً محاسبه می شود. معنای این قیمت ها متفاوت است. هزینه یک یال هزینه جابجایی از یک راس به یک راس متصل به آن یال است. و قیمت تاپ قیمت حداقل مسیر است. نکته مهم دیگر مربوط به محاسبه مجدد قیمت های اولیه است. در واقع، محاسبه مجدد قیمت های اولیه فقط برای آن رئوس منطقی است که در آخرین مرحله با راس اضافه شده به مجموعه موارد انتخاب شده مرتبط هستند، زیرا هیچ دلیلی برای تغییر قیمت اولیه برای سایر رئوس وجود ندارد.

مشخص است که تمام قیمت ها (مثلاً راه اندازی یا گذر) غیر منفی است. مسیر کمترین هزینه 1->i را برای همه i=1 پیدا کنید. n در زمان O(n2).

در طول عملکرد الگوریتم، برخی از شهرها انتخاب می شوند (در ابتدا - فقط شهر 1، در پایان - همه). که در آن:

برای هر شهر انتخاب شده i، کمترین هزینه مسیر 1->i ذخیره می شود. در عین حال، مشخص است که در مسیری که فقط از شهرهای منتخب عبور می کند به حداقل رسیده است.

برای هر شهر تخصیص نیافته i کمترین هزینه مسیر 1->i ذخیره می شود که در آن فقط شهرهای انتخاب شده به عنوان میانی استفاده می شود.

مجموعه شهرهای تخصیص یافته بر اساس این نکته گسترش می یابد: اگر در بین همه شهرهای تخصیص نیافته، شهری را انتخاب کنیم که تعداد ذخیره شده برای آن حداقل است، این تعداد کمترین هزینه واقعی است. در واقع، اجازه دهید راه کوتاه تری وجود داشته باشد. اولین شهر انتخاب نشده در این مسیر را در نظر بگیرید - مسیر رسیدن به آن در حال حاضر طولانی تر است! (منفی بودن قیمت ها در اینجا ضروری است.)

با افزودن شهر انتخابی به انتخاب شده، باید اطلاعات ذخیره شده برای شهرهای انتخاب نشده را اصلاح کنیم. در این مورد، کافی است تنها مسیرهایی را در نظر بگیریم که شهر جدید آخرین نقطه انتقال است و این کار آسان است، زیرا ما از قبل حداقل هزینه مسیر به شهر جدید را می دانیم.

به عبارت دیگر، هر راس از V با یک برچسب مرتبط است - حداقل فاصله شناخته شده از این راس تا a. الگوریتم گام به گام کار می کند - در هر مرحله از یک راس "بازدید" می کند و سعی می کند برچسب ها را کاهش دهد. این الگوریتم زمانی خاتمه می یابد که همه رئوس بازدید شوند.

مقداردهی اولیه. برچسب بالا آقرار است برابر باشد 0 ، برچسب های رئوس باقی مانده بی نهایت هستند. این نشان دهنده فاصله ها از آبه قله های دیگر هنوز ناشناخته است. تمام رئوس نمودار به عنوان بازدید نشده مشخص می شوند.

مرحله الگوریتم اگر همه رئوس بازدید شده باشد، الگوریتم خاتمه می یابد. در غیر این صورت، یک راس از رئوس هایی که هنوز بازدید نشده اند انتخاب می شود توبا حداقل برچسب. ما تمام مسیرهای ممکن را در نظر می گیریم که در آن تونقطه ماقبل آخر است رئوس متصل به یک راس تولبه ها، همسایه های این راس را صدا می زنیم. برای هر همسایه، طول مسیر جدیدی برابر با مجموع برچسب فعلی در نظر بگیرید توو طول لبه اتصال توبا این همسایه اگر طول حاصل از برچسب همسایه کمتر است، برچسب را با این طول جایگزین کنید. با در نظر گرفتن همه همسایگان، راس را علامت گذاری می کنیم توهمانطور که بازدید کرده اید و مرحله را تکرار کنید.

از آنجایی که الگوریتم دایکسترا هر بار انتخاب می‌کند که راس را با کوچک‌ترین تخمین کوتاه‌ترین مسیر پردازش کند، می‌توان گفت که متعلق به الگوریتم‌های حریص است.

اجازه دهید نحوه عملکرد الگوریتم دایکسترا را با جزئیات بیشتری شرح دهیم.

الگوریتم از سه آرایه N (= تعداد رئوس شبکه) اعداد استفاده می کند. اولین آرایه A حاوی برچسب هایی با دو مقدار است: 0 (راس هنوز در نظر گرفته نشده است) و 1 (راس قبلاً در نظر گرفته شده است). آرایه دوم B شامل فواصل است - کوتاهترین فواصل فعلی از تا راس مربوطه. آرایه سوم c شامل اعداد رئوس است - عنصر k-امین C [k] تعداد راس ماقبل آخر در کوتاهترین مسیر فعلی از Vi تا Vk است. ماتریس فاصله D طول قوس D را مشخص می کند. اگر چنین کمانی وجود نداشته باشد، به D یک عدد بزرگ B، برابر با "بی نهایت ماشین" اختصاص داده می شود.

اکنون می توانیم شرح دهیم:

1. (ابتدایی). در یک چرخه از 1 تا N، آرایه A را با صفر پر کنید. آرایه C را با عدد i پر کنید. ردیف i-امین ماتریس D را به آرایه B منتقل کنید، A [i]: =1; C [i]: = 0 (i - شماره راس شروع)

2. (مرحله عمومی). حداقل را در بین موارد بدون علامت (یعنی آنهایی که k برای آنها A [k] = 0) بیابید. بگذارید حداقل در شاخص j به دست آید، یعنی. B[j]<=B [k] Затем выполняются следующие операции: A [j]: =1; если B [k] >B [j] +D، سپس (B [k]: =B [j] +D ؛ C [k]: =j) (شرط به این معنی است که مسیر Vi. Vk طولانی تر از مسیر Vi. Vj Vk است) . (اگر همه A [k] علامت گذاری شده باشند، طول مسیر از Vi تا Vk برابر با B [k] است. حال باید رئوس موجود در کوتاه ترین مسیر را برشماریم).

3. (صدور پاسخ). (مسیر Vi به Vk به ترتیب معکوس با روش زیر برگردانده می شود:)

2. مسأله ز;

3. z:=C[z]. اگر z = 0 است، پایان دهید، در غیر این صورت به 3.2 بروید.

برای اجرای الگوریتم، لازم است آرایه B از N عنصر N بار اسکن شود، یعنی. الگوریتم Dijkstra دارای پیچیدگی درجه دوم است: O(n2).

در زیر یک بلوک دیاگرام از الگوریتم Dijkstra آمده است (شکل 2 را ببینید).

شکل 2. فلوچارت الگوریتم دایکسترا

در ابتدای الگوریتم، فاصله راس اولیه روی صفر تنظیم می شود و تمام فواصل دیگر با یک عدد مثبت بزرگ (بزرگتر از حداکثر مسیر ممکن در نمودار) پر می شوند. آرایه پرچم ها با صفر پر شده است. سپس حلقه اصلی شروع می شود.

در هر مرحله از حلقه، ما به دنبال یک راس با حداقل فاصله و یک پرچم برابر با صفر هستیم. سپس پرچم را روی 1 قرار می دهیم و تمام رئوس مجاور آن را بررسی می کنیم. اگر فاصله در آن از مجموع فاصله تا راس جاری و طول یال بیشتر باشد، آن را کاهش می دهیم. حلقه زمانی به پایان می رسد که پرچم تمام رئوس برابر با 1 شود.

به هر شهر منطقه (اگر فقط می توانید در جاده ها حرکت کنید).

گزینه 2.تعدادی پرواز بین شهرهای دنیا وجود دارد که هزینه هر کدام مشخص است. هزینه پرواز از A به B ممکن است با هزینه پرواز از B به A برابر نباشد. مسیر حداقل هزینه (احتمالاً با ترانسفر) از کپنهاگ به بارنائول را پیدا کنید.

تعریف رسمی

مثال

اجرای الگوریتم را در مثال نمودار نشان داده شده در شکل در نظر بگیرید.

اجازه دهید لازم باشد کوتاهترین فواصل را از راس اول تا بقیه را پیدا کنید.

پیاده سازی در زبان های برنامه نویسی

اجرا به زبان C (C).

#define SIZE 6 #define INF 1000000000 int a [ SIZE ][ SIZE ] = (( INF , 5 , INF , INF , INF , INF ),( 1 , 2 , 3 , 4 , 5 // matrix ( 1 , 2 , 3 , 4 , 5 , 6 ), ( 1 , 2 , 3 , 4 , 5 , 6 ) // شاخص های افقی از نقطه { 1 , 2 , 3 , 4 , 5 , 6 },{ 1 , 2 , 3 , 4 , 5 , 6 }}; // عمودی به یک نقطه، مقدار - وزن int d[ SIZE ]; // آرایه ای از کوتاه ترین مسیرها، شاخص ها - رئوس نمودار void deikstra () (int v [SIZE]؛ // آرایه برچسب‌ها int temp, i؛ int miniindex, min؛ برای (i = 0؛ i< SIZE ; i ++ ) { d [ i ] = INF ; // آرایه ای از مسیرها که تا بی نهایت مقدار دهی اولیه شده اند v[i] = 1; ) d [ 0 ] = 0 ; انجام دادن( // اجرای الگوریتم minindex = INF ; حداقل = INF ; برای (i = 0؛ i< SIZE ; i ++ ) { if ((v [ i ] == 1 ) && (d [ i ] < min )) { min = d [ i ]; minindex = i ; } } if (minindex != INF ) { for (i = 0 ; i < SIZE ; i ++ ) { if (a [ minindex ][ i ] >0 ) ( دما = حداقل + a [ miniindex ][ i ]؛ اگر (دمای< d [ i ]) d [ i ] = temp ; } } v [ minindex ] = 0 ; } } while (minindex < INF ); }

یک نمودار وزن دار جهت دار یا غیر جهت دار با رئوس و یال ها داده می شود. وزن تمام لبه ها غیر منفی است. مقداری راس شروع مشخص شده است. لازم است طول کوتاه‌ترین مسیرها از یک راس تا همه راس‌های دیگر را پیدا کنید، و همچنین راهی برای خروجی کوتاه‌ترین مسیرها ارائه کنید.

این مشکل کوتاهترین مسیرهای تک منبعی نامیده می شود.

الگوریتم

این الگوریتم ارائه شده توسط محقق هلندی را توصیف می کند دایکسترا(Dijkstra) در سال 1959

بیایید آرایه ای بگیریم که در آن برای هر رأس، طول فعلی کوتاه ترین مسیر از تا را ذخیره می کنیم. در ابتدا و برای همه رئوس دیگر، این طول برابر با بی‌نهایت است (زمانی که در رایانه پیاده‌سازی می‌شود، معمولاً تعداد به اندازه کافی بزرگ به‌عنوان بی‌نهایت انتخاب می‌شود که آشکارا بیشتر از طول مسیر ممکن است):

علاوه بر این، برای هر رأس ذخیره می کنیم که آیا هنوز برچسب زده شده است یا نه، یعنی. بیایید یک آرایه بولی بگیریم. در ابتدا، همه رئوس بدون برچسب هستند، یعنی.

خود الگوریتم دایکسترا شامل تکرارها. در تکرار بعدی، راس با کمترین مقدار در بین مواردی که هنوز برچسب گذاری نشده اند انتخاب می شود، یعنی:

(مشخص است که در اولین تکرار، راس شروع انتخاب خواهد شد.)

راس انتخاب شده به این صورت علامت گذاری شده است. علاوه بر این، در تکرار فعلی، از راس، آرامش: تمام یال های خروجی از راس از طریق بررسی می شوند و برای هر یک از این رئوس الگوریتم سعی می کند مقدار . بگذارید طول لبه فعلی باشد، سپس در قالب یک کد، آرامش به نظر می رسد:

اینجا جایی است که تکرار فعلی به پایان می رسد، الگوریتم به تکرار بعدی ادامه می دهد (دوباره، راس با کوچکترین مقدار انتخاب می شود، آرامش ها از آن انجام می شود و غیره). در این صورت در نهایت پس از تکرارها، تمام رئوس نمودار برچسب دار می شوند و الگوریتم کار خود را کامل می کند. ادعا می شود که مقادیر یافت شده طول های مورد نیاز کوتاه ترین مسیرها از تا هستند.

شایان ذکر است که اگر همه رئوس نمودار از راس قابل دسترسی نباشند، مقادیر برای آنها بی نهایت باقی خواهد ماند. واضح است که چند تکرار آخر الگوریتم فقط این رئوس را انتخاب می کند، اما این تکرارها هیچ کار مفیدی ایجاد نمی کنند (زیرا یک فاصله نامتناهی نمی تواند سایر فواصل، حتی بی نهایت) را کاهش دهد. بنابراین، به محض اینکه یک راس با فاصله بی نهایت به عنوان راس انتخاب شده در نظر گرفته شد، می توان بلافاصله الگوریتم را متوقف کرد.

ترمیم مسیر. البته معمولاً نه تنها طول کوتاه ترین مسیرها، بلکه خود مسیرها را نیز باید دانست. اجازه دهید نحوه ذخیره اطلاعات کافی برای بازسازی بعدی کوتاهترین مسیر از هر راس را نشان دهیم. برای این، به اصطلاح آرایه اجدادی: آرایه ای که برای هر راس، تعداد راس ماقبل آخر را در کوتاه ترین مسیر به راس نگه می دارد. این از این واقعیت استفاده می کند که اگر ما کوتاه ترین مسیر را به یک راس انتخاب کنیم، و سپس آخرین راس را از این مسیر حذف کنیم، مسیری به دست می آید که به یک راس ختم می شود، و این مسیر برای راس کوتاه ترین خواهد بود. بنابراین، اگر ما این آرایه از اجداد را داشته باشیم، می‌توان کوتاه‌ترین مسیر را از آن بازیابی کرد، به سادگی هر بار جد را از راس فعلی گرفته تا به راس شروع برسیم - به این ترتیب کوتاه‌ترین مسیر مورد نظر را به دست می‌آوریم، اما نوشته شده در به صورت برعکس. بنابراین کوتاه ترین مسیر برای رسیدن به قله این است:

باید بدانیم که چگونه این آرایه از اجداد را بسازیم. با این حال، این بسیار ساده انجام می شود: با هر آرامش موفقیت آمیز، یعنی. هنگامی که از راس انتخاب شده بهبودی در فاصله تا یک راس وجود دارد، می نویسیم که جد راس همان راس است:

اثبات

بیانیه اصلی، که درستی الگوریتم دایکسترا بر اساس آن استوار است به شرح زیر است. بیان شده است که پس از علامت گذاری هر راس، فاصله فعلی تا آن از قبل کوتاه ترین است و بر این اساس، دیگر تغییر نخواهد کرد.

اثباتما با القاء تولید خواهیم کرد. برای تکرار اول، اعتبار آن واضح است - برای راس ما، که طول کوتاه ترین مسیر به آن است. حال اجازه دهید این ادعا برای همه تکرارهای قبلی، یعنی. همه رئوس از قبل علامت گذاری شده اند. اجازه دهید ثابت کنیم که پس از اجرای تکرار فعلی نقض نشده است. بگذارید راس انتخاب شده در تکرار فعلی باشد، یعنی. راسی که الگوریتم قرار است برچسب گذاری کند. اجازه دهید ثابت کنیم که واقعاً برابر است با طول کوتاه ترین مسیر به آن (این طول را با نشان می دهیم).

کوتاه ترین مسیر به سمت بالا را در نظر بگیرید. واضح است که این مسیر را می توان به دو مسیر تقسیم کرد: که فقط از رئوس علامت گذاری شده تشکیل شده است (حداقل راس شروع در این مسیر خواهد بود) و بقیه مسیر (می تواند شامل رئوس علامت گذاری شده نیز باشد، اما همیشه با یک شروع می شود. بدون علامت). با راس اول مسیر و با راس آخر مسیر مشخص کنید.

اجازه دهید ابتدا ادعای خود را برای راس ثابت کنیم، یعنی: بیایید برابری را ثابت کنیم با این حال، این تقریباً واضح است: پس از همه، در یکی از تکرارهای قبلی، ما یک راس را انتخاب کردیم و از آن آرامش را انجام دادیم. از آنجایی که (به دلیل انتخاب راس ) کوتاهترین مسیر به مساوی است با کوتاهترین مسیر به علاوه لبه، پس زمانی که شل کردن از انجام می شود، مقدار در واقع به مقدار لازم تنظیم می شود.

به دلیل منفی نبودن هزینه‌های لبه‌ها، طول کوتاه‌ترین مسیر (که طبق آنچه اخیراً ثابت شد برابر است با ) از طول کوتاه‌ترین مسیر تا راس بیشتر نمی‌شود. با توجه به این که (به هر حال، الگوریتم دایکسترا نتوانست مسیری کوتاه تر از حد معمول پیدا کند)، در نتیجه، روابط زیر را به دست می آوریم:

از طرف دیگر، از آنجایی که هر دو و و رئوس بدون برچسب هستند، از آنجایی که این راس بود که در تکرار فعلی انتخاب شد و نه رأس، نابرابری دیگری به دست می‌آوریم:

از این دو نابرابری، برابری را نتیجه می گیریم و سپس از روابطی که قبلاً یافت شد، به دست می آوریم و:

Q.E.D.

پیاده سازی

بنابراین، الگوریتم Dijkstra شامل تکرارهایی است که در هر یک از آنها یک راس بدون برچسب با کوچکترین مقدار انتخاب می شود، این راس برچسب گذاری می شود و سپس تمام یال های خروجی از این راس بررسی می شوند و در امتداد هر یال تلاش برای بهبود مقدار در انتهای دیگر لبه.

زمان اجرای الگوریتم حاصل جمع موارد زیر است:

با ساده ترین اجرای این عملیات، عملیات برای یافتن یک راس، و عملیات روی یک آرامش و نهایی صرف می شود. مجانبیالگوریتم این است:

پیاده سازی:

const int INF = 1000000000 ; int main() ( int n؛ ... خواندن n ... بردار< vector < pair< int ,int >> > g(n); ... خواندن نمودار ... int s = ...; // راس شروعبردار< int >d(n، INF)، p(n) ; d[s] = 0; بردار< char >u(n); برای (int i= 0 ; i< n; ++ i) { int v = - 1 ; for (int j= 0 ; j< n; ++ j) if (! u[ j] && (v == - 1 || d[ j] < d[ v] ) ) v = j; if (d[ v] == INF) break ; u[ v] = true ; for (size_t j= 0 ; j< g[ v] .size () ; ++ j) { int to = g[ v] [ j] .first , len = g[ v] [ j] .second ; if (d[ v] + len < d[ to] ) { d[ to] = d[ v] + len; p[ to] = v; } } } }

در اینجا نمودار به شکل لیست های مجاورت ذخیره می شود: برای هر رأس، لیست حاوی لیستی از یال های ناشی از این راس است، به عنوان مثال. فهرستی از جفت‌ها >، که در آن اولین عنصر جفت، راسی است که لبه به آن منتهی می‌شود، و عنصر دوم وزن یال است.

) در زمان O(m + n \ln n) اجرا می شود و به طور مجانبی سریعترین الگوریتم ترتیبی شناخته شده برای این دسته از مسائل است.

1.2 توصیف ریاضی الگوریتم

اجازه دهید یک نمودار G = (V, E) با وزن یال f(e) و راس منبع متمایز u داده شود. کوتاه ترین فاصله از منبع u تا راس v را با d(v) نشان دهید.

بگذارید همه فواصل که از یک عدد خاص r تجاوز نمی کنند قبلاً محاسبه شده باشند، یعنی فواصل تا رئوس مجموعه V_r = \( v \ در V \ mid d(v) \le r \). اجازه دهید

(v, w) \in \arg\min \(d(v) + f(e) \mid v \in V, e = (v, w) \in E \).

سپس d(w) = d(v) + f(e)و v در کوتاه ترین مسیر از u تا w قرار دارد.

مقادیر d^+(w) = d(v) + f(e)، جایی که v \در V_r , e = (v, w) \در E , فراخوانی می شوند فاصله های تخمینیو یک کران بالایی برای فواصل واقعی هستند: d(w) \le d^+(w) .

الگوریتم Dijkstra در هر مرحله، راس با کمترین فاصله تخمینی را پیدا می‌کند، آن را به‌عنوان بازدید شده علامت‌گذاری می‌کند، و فواصل تخمینی را برای تمام نقاط انتهایی یال‌های ناشی از آن به‌روزرسانی می‌کند.

1.3 هسته محاسباتی الگوریتم

محاسبات اصلی مربوط به عملیات در صف اولویت است:

  • استخراج حداقل عنصر (delete_min)؛
  • کاهش اولویت یک عنصر (decrease_key).

1.4 ساختار کلان الگوریتم

شبه کد الگوریتم:

داده های ورودی: نمودار با رئوس V، لبه ها Eبا ترازو f(ه) گره منبع تو. خروجی: فاصله ها د(v) به هر رأس vVاز بالا تو. س := جدیدصف اولویت برای هر vV: اگر v = تو سپس د(v) := 0 دیگر د(v) := ∞ س.insert( v, د(v)) در حالی که س ≠ ∅: v := س.delete_min() برای هر ه = (v, w) ∈ E: اگر د(w) > د(v) + f(ه): د(w) := د(v) + f(ه) س.decrease_key( w, د(w))

1.5 طرح پیاده سازی الگوریتم ترتیبی

اجرای خاص الگوریتم Dijkstra با انتخاب الگوریتم صف اولویت استفاده شده تعیین می شود. در ساده‌ترین حالت، این می‌تواند یک آرایه یا یک لیست باشد، جستجوی حداقلی که در آن مستلزم مشاهده تمام رئوس است. کارآمدتر استفاده از پشته است. بهترین تخمین پیچیدگی شناخته شده برای گونه ای است که از پشته فیبوناچی استفاده می کند.

یک گزینه پیاده سازی زمانی امکان پذیر است که رئوس نه در مرحله اولیه، بلکه در زمان اولین بازدید به صف اضافه شوند.

1.6 پیچیدگی متوالی الگوریتم

پیچیدگی ترتیبی الگوریتم O(C_1 m + C_2n) است، که در آن

  • C_1 - تعداد عملیات برای کاهش فاصله تا بالا؛
  • C_2 تعداد عملیات برای محاسبه حداقل است.

الگوریتم اصلی Dijkstra از لیست ها به عنوان یک ساختار داده داخلی استفاده می کرد که برای آنها C_1 = O(1) , C_2 = O(n) , بنابراین پیچیدگی کلی O(n^2) بود.

هنگام استفاده از پشته فیبوناچی، حداقل زمان محاسبه به C_2 = O(\ln n) کاهش می یابد، بنابراین پیچیدگی کل O(m + n \ln n) است که مجانبی بهترین نتیجه شناخته شده برای این دسته از مسائل است.

1.7 نمودار اطلاعات

یک نمودار الگوریتم برای پیاده سازی اساسی الگوریتم دایکسترا در لیست ها یا آرایه ها ارائه شده است.

شکل 1. نمودار الگوریتم بدون نمایش داده های ورودی و خروجی. n=3. عملیات مقایسه با رنگ زرد، عملیات تغییر برچسب‌های راس با رنگ سبز و برچسب‌گذاری راس با رنگ آبی مشخص شده‌اند.

1.8 منبع موازی الگوریتم

الگوریتم Dijkstra امکان موازی سازی کارآمد، میانگین زمان اجرای O(n^(1/3)\ln n) با حجم محاسبات O(n \ln n + m) را فراهم می کند.

تخمین اول بر اساس مشخصه daps است که تعداد دسترسی های حافظه (خواندن و نوشتن) انجام شده در هر ثانیه را تخمین می زند. این مشخصه مشابه تخمین فلاپ ها در رابطه با کار با حافظه است و بیشتر یک تخمین عملکرد تعامل حافظه است تا تخمین محلی. با این حال، به عنوان یک منبع اطلاعات خوب عمل می کند، از جمله برای مقایسه با نتایج مربوط به ویژگی cvg زیر.

شکل 6 مقادیر daps را برای پیاده سازی الگوریتم های رایج نشان می دهد که به ترتیب صعودی مرتب شده اند (هر چه daps بیشتر باشد به طور کلی عملکرد بهتری دارد). می بینید که عملکرد حافظه بسیار پایین است. این تعجب آور نیست، زیرا پیاده سازی الگوریتم ها بر روی نمودارها تقریباً همیشه به دلیل بی نظمی دسترسی به داده ها، که در هنگام تجزیه و تحلیل نمایه دسترسی مشاهده کردیم، کارایی پایینی دارند.

شکل 6. مقایسه مقادیر نمره daps

مشخصه دوم، cvg، برای ارائه تخمین محلی مستقل تر از ماشین در نظر گرفته شده است. تعیین می کند که برنامه چقدر باید داده ها را به حافظه کش بکشد. بر این اساس، هر چه مقدار cvg کوچکتر باشد، هرچه کمتر باید انجام شود، محل بهتری است.

شکل 7 مقادیر cvg را برای مجموعه مشابهی از پیاده سازی ها نشان می دهد که به ترتیب نزولی مرتب شده اند (هر چه cvg کوچکتر باشد، به طور کلی محل بالاتر است). مشاهده می شود که در این مورد مقدار cvg به خوبی با نمره عملکرد همبستگی دارد و محلی بودن پایین را منعکس می کند که با یافته های ارزیابی محلی کیفی مطابقت دارد.

شکل 7. مقایسه مقادیر امتیاز cvg

2.3 روش ها و ویژگی های احتمالی اجرای موازی الگوریتم

2.4 مقیاس پذیری الگوریتم و پیاده سازی آن

2.4.1 مقیاس پذیری الگوریتم

2.4.2 مقیاس پذیری اجرای الگوریتم

بیایید مقیاس پذیری اجرای موازی الگوریتم را با توجه به روش بررسی کنیم. این مطالعه بر روی ابر رایانه لومونوسوف مجتمع ابر رایانه دانشگاه مسکو انجام شد. مجموعه و مرزهای مقادیر پارامترهای متغیر برای راه اندازی اجرای الگوریتم:

  • تعداد پردازنده با مقادیر مربع عدد صحیح؛
  • اندازه نمودار با گام 16000.

شکل زیر نموداری از عملکرد پیاده سازی انتخاب شده الگوریتم را بسته به پارامترهای راه اندازی قابل تغییر نشان می دهد.

شکل 8. اجرای موازی الگوریتم. تغییر در عملکرد بسته به تعداد پردازنده ها و اندازه منطقه.

با توجه به ویژگی های اجرای موازی الگوریتم، عملکرد کلی بسیار پایین است و با رشد تعداد فرآیندها به آرامی افزایش می یابد و با نزدیک شدن به تعداد فرآیندهای 128، شروع به کاهش می کند. این با استفاده از عملیات جمعی در هر تکرار الگوریتم و با این واقعیت توضیح داده می شود که هزینه مبادلات ارتباطی با افزایش تعداد فرآیندهای مورد استفاده به طور قابل توجهی افزایش می یابد. محاسبات در هر فرآیند بسیار سریع است و بنابراین تجزیه نمودار به طور ضعیف اثر هزینه های مبادلات ارتباطی را جبران می کند.

2.5 عملکرد پویا و کارایی اجرای الگوریتم

اجرای الگوریتم Dijkstra برای آزمایش ها استفاده شد. تمام نتایج در ابر رایانه لومونوسوف به دست آمد. ما از پردازنده های Intel Xeon X5570 با حداکثر عملکرد 94 Gflops و همچنین یک کامپایلر Intel 13.1.0 استفاده کردیم. شکل ها کارایی اجرای الگوریتم دایکسترا را بر روی 32 فرآیند نشان می دهد.

شکل 9. نمودار بار CPU هنگام اجرای الگوریتم Dijkstra

نمودار بار پردازنده نشان می دهد که تقریباً در تمام مدت اجرای برنامه، سطح بار حدود 50٪ است. این نشان‌دهنده بار یکنواخت روی پردازنده‌ها، هنگام استفاده از 8 پردازش در هر گره محاسباتی و بدون استفاده از Hyper Threading است.

شکل 10. نمودار عملیات ممیز شناور در ثانیه هنگام اجرای الگوریتم Dijkstra

شکل 10 نموداری از عملیات ممیز شناور در ثانیه را نشان می دهد. نمودار عملکرد کلی بسیار ضعیف حدود 250 کیلوفلاپ در اوج و حدود 150 کیلوفلاپ به طور متوسط ​​در تمام گره ها را نشان می دهد. این نشان می دهد که تقریباً تمام محاسبات در برنامه با اعداد صحیح انجام می شود.

نمودار نوشتن به حافظه، الگوی مشابهی از ناهمواری محاسباتی را نشان می‌دهد که تنها چند فرآیند به طور همزمان به طور فعال نوشته می‌شوند. این با سایر برنامه های اجرایی مرتبط است. شایان ذکر است تعداد نسبتاً کم دسترسی به نوشتن حافظه است. این نشان دهنده سازماندهی خوب محاسبات و کار نسبتاً کارآمد با حافظه است.

شکل 15. نمودار نرخ انتقال از طریق شبکه Infiniband بر حسب بایت در ثانیه زمانی که الگوریتم Dijkstra در حال اجرا است.

در نمودار نرخ انتقال داده شبکه Infiniband، نرخ انتقال داده نسبتاً بالایی بر حسب بایت در ثانیه وجود دارد. این نشان می دهد که فرآیندها به شدت و احتمالاً بخش های نسبتاً کمی از داده ها را با یکدیگر مبادله می کنند، زیرا عملکرد محاسباتی بالا است. شایان ذکر است که نرخ انتقال بین فرآیندها متفاوت است که نشان دهنده عدم تعادل محاسباتی است.

شکل 16. نمودار نرخ انتقال از طریق شبکه Infiniband در بسته در ثانیه هنگام اجرای الگوریتم Dijkstra

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

شکل 17. نمودار تعداد فرآیندهای منتظر ورود به مرحله شمارش (Loadavg) زمانی که الگوریتم Dijkstra در حال اجرا است.

در نمودار تعداد فرآیندهای منتظر ورود به مرحله شمارش (Loadavg) مشاهده می شود که مقدار این پارامتر در کل عملیات برنامه ثابت است و تقریباً برابر با 8 است. این نشان دهنده عملکرد پایدار برنامه با بارگذاری است. محاسبات توسط همه گره ها این نشان دهنده بارگیری بسیار منطقی و ایستا منابع سخت افزاری توسط فرآیندها است. و کارایی نسبتا خوبی از اجرای در حال انجام را نشان می دهد. به طور کلی، با توجه به مانیتورینگ سیستمی برنامه، می توان نتیجه گرفت که برنامه کاملاً کارآمد و پایدار کار کرده است. استفاده از حافظه بسیار فشرده است و استفاده از رسانه ارتباطی بسیار فشرده است، در حالی که حجم انتقال داده زیاد نیست. این ماهیت دقیق تأخیر محیط ارتباطی بخش الگوریتمی برنامه را نشان می دهد. به نظر می رسد راندمان پایین با حجم نسبتاً بالایی از انتقال در هر فرآیند و با مبادلات فشرده پیام همراه باشد.