Dijkstra算法與堆(C++)

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

相關文章
相關標籤/搜索