Dijkstra算法用於解決單源最短路徑問題,經過逐個收錄頂點來確保已收錄頂點的路徑長度爲最短。ios
圖片來自陳越姥姥的數據結構課程:https://mooc.study.163.com/learn/1000033001?tid=1000044001#/learn/content?type=detail&id=1000112011&cid=1000126096算法
Dijkstra算法的時間複雜度,取決於「V=未收錄頂點中dist最小者」的算法。這一步能夠用線性查找實現,也能夠用最小堆實現。數據結構
線性查找的算法就不用多說了。最小堆的算法有一個問題:最小堆是以未收錄頂點的dist做爲key來創建的,可是每一輪循環都會把部分頂點的dist值改變,也就會破壞最小堆的有序性,怎麼解決?函數
顯然應該在每一輪循環中把最小堆從新調整成有序。如今問題又來了:測試
1. 複雜度還合算嗎?優化
建堆的時間複雜度是O(N),pop一個元素的時間複雜度是O(logN);線性查找的時間複雜度也是O(N)。建堆還額外使用了O(N)的空間。spa
看似一點都不合算。但我又想到每一輪循環中的建堆操做,極可能只須要調整少許元素,而對於其餘元素,只須要進行訪問。然而線性查找連調整都不須要,只有交換。再然而,循環過程當中堆會變小,使建堆的時間複雜度中的常數變小。至於到底哪一個更快,還得實踐出真知。.net
因此只能從消除建堆操做入手。這樣又是另外一種算法了,參考資料[1]給出了詳細說明,這種算法中每一輪的時間複雜度爲O(logN),總時間複雜度爲O(|E|log|V|)(V爲頂點,E爲邊)。3d
2. 如何利用STL進行堆操做?code
STL <algorithm> 頭文件提供了 std::is_heap 、 std::is_heap_until (這兩個須要C++11)、 std::make_heap 、 std::push_heap 、 std::pop_heap 和 std::sort_heap 等函數模板用於堆操做。
現有一道單源最短路徑的題:https://pintia.cn/problem-sets/994805342720868352/problems/994805523835109376,Dijkstra算法的變形而已。
如下爲實現代碼。三種算法用宏定義選擇,已選擇優先隊列算法。
1 #include <iostream> 2 #include <limits> 3 #include <vector> 4 #include <queue> 5 #include <algorithm> 6 #include <utility> 7 #include <functional> 8 9 //#define LINEAR 10 //#define HEAP 11 #define QUEUE 12 13 struct Path 14 { 15 Path() = default; 16 Path(int _city, int _dist) 17 : city(_city), dist(_dist) 18 { 19 ; 20 } 21 int city; 22 int dist; 23 bool operator<(const Path& _rhs) const 24 { 25 return dist < _rhs.dist; 26 } 27 bool operator>(const Path& _rhs) const 28 { 29 return dist > _rhs.dist; 30 } 31 }; 32 33 struct City 34 { 35 std::vector<Path> paths; 36 int team; 37 int dist = std::numeric_limits<int>::max(); 38 bool collected = false; 39 int team_max = 0; 40 int dist_count = 0; 41 }; 42 43 #ifdef HEAP 44 class Comparator 45 { 46 public: 47 Comparator(std::vector<City>& _cities) 48 : cities_(&_cities) 49 { 50 ; 51 } 52 bool operator()(int _lhs, int _rhs) 53 { 54 return (*cities_)[_lhs].dist > (*cities_)[_rhs].dist; 55 } 56 private: 57 std::vector<City>* cities_; 58 }; 59 #endif 60 61 int main() 62 { 63 int n, m, src, dst; 64 std::cin >> n >> m >> src >> dst; 65 std::vector<City> cities(n); 66 for (auto& city : cities) 67 std::cin >> city.team; 68 for (int cnt = 0; cnt != m; ++cnt) 69 { 70 int src, dst, dist; 71 std::cin >> src >> dst >> dist; 72 cities[src].paths.emplace_back(dst, dist); 73 cities[dst].paths.emplace_back(src, dist); 74 } 75 76 { 77 auto& city = cities[src]; 78 cities[src].collected = true; 79 cities[src].dist = 0; 80 cities[src].dist_count = 1; 81 cities[src].team_max = cities[src].team; 82 } 83 #ifdef QUEUE 84 std::priority_queue<Path, std::vector<Path>, std::greater<Path>> queue; 85 #endif 86 for (const auto& path : cities[src].paths) 87 { 88 cities[path.city].dist = path.dist; 89 cities[path.city].dist_count = 1; 90 cities[path.city].team_max = cities[src].team + cities[path.city].team; 91 #ifdef QUEUE 92 queue.emplace(path.city, path.dist); 93 #endif 94 } 95 96 #ifdef HEAP 97 std::vector<int> heap; 98 heap.reserve(n - 1); 99 for (int i = 0; i != n; ++i) 100 if (i != src) 101 heap.push_back(i); 102 Comparator comp(cities); 103 std::make_heap(heap.begin(), heap.end(), comp); 104 #endif 105 106 while (1) 107 { 108 #ifdef LINEAR 109 int min_dist = std::numeric_limits<int>::max(); 110 int index = -1; 111 for (int i = 0; i != n; ++i) 112 if (!cities[i].collected && cities[i].dist < min_dist) 113 min_dist = cities[i].dist, index = i; 114 if (index == -1) 115 break; 116 auto& city = cities[index]; 117 #endif 118 #ifdef HEAP 119 if (heap.empty()) 120 break; 121 auto& city = cities[heap[0]]; 122 #endif 123 #ifdef QUEUE 124 if (queue.empty()) 125 break; 126 Path temp; 127 while (1) 128 { 129 temp = queue.top(); 130 queue.pop(); 131 if (!cities[temp.city].collected) 132 break; 133 } 134 auto& city = cities[temp.city]; 135 #endif 136 city.collected = true; 137 for (const auto& path : city.paths) 138 { 139 if (!cities[path.city].collected) 140 { 141 auto& dest = cities[path.city]; 142 if (city.dist + path.dist < cities[path.city].dist) 143 { 144 dest.dist = city.dist + path.dist; 145 dest.dist_count = city.dist_count; 146 dest.team_max = city.team_max + dest.team; 147 } 148 else if (city.dist + path.dist == cities[path.city].dist) 149 { 150 dest.dist = city.dist + path.dist; 151 dest.dist_count += city.dist_count; 152 if (city.team_max + dest.team > dest.team_max) 153 dest.team_max = city.team_max + dest.team; 154 } 155 #ifdef QUEUE 156 queue.emplace(path.city, dest.dist); 157 #endif 158 } 159 } 160 #ifdef LINEAR 161 if (index == dst) 162 break; 163 #endif 164 #ifdef HEAP 165 if (heap[0] == dst) 166 break; 167 std::pop_heap(heap.begin(), heap.end(), comp); 168 heap.pop_back(); 169 std::make_heap(heap.begin(), heap.end(), comp); 170 #endif 171 #ifdef QUEUE 172 if (temp.city == dst) 173 break; 174 #endif 175 } 176 177 { 178 auto& city = cities[dst]; 179 std::cout << cities[dst].dist_count << ' ' << cities[dst].team_max; 180 } 181 182 return 0; 183 }
測試結果:
線性查找版
最小堆版
優先隊列版
平臺顯示線性查找版的時間6ms,內存512KB;最小堆版的時間5ms,內存512KB;優先隊列版的時間3ms,內存424KB。我認爲時間都過短了,數據量不夠大,不足以說明問題。
若是僅從理論上分析的話,我認爲優先隊列的算法是最優的。
參考資料:
[1] dijkstra + heap 優化 https://blog.csdn.net/sentimental_dog/article/details/51955765