利用优先级队列(priority_queue)优化单源最短路径算法
2017-10-14 11:12
337 查看
单源最短路径,不用说一定是经典的
接下来展示通过“优先级队列(priority_queue)”优化时间复杂度的方法。
首先把图的类定义出来,我采用的是“class Edge”+”class Graph”的定义方案,将有向边和图的实现抽离,如有不妥,方请指正:
还需要一个
然后补充一下相关基础函数的操作:
接下来是正式的算法部分…图解的话…直接上书上的图片了:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201909/03/5755810a862345e6055465bc4dc25259)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201909/03/0198a53d7876246ce68517e5890760a9)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201909/03/32655bba84049a752f8df70453c99a8b)
实现如下:
这个版本的算法中,每次更新ssp[]时,都会有一个forloop对所有顶点都扫描一遍,再加上外层的while主循环,一张V个顶点的图,其时间复杂度为O(|V|^2),并不算很理想。倘若通过一个二叉堆来动态的维护这一个顶点的集合而不是每次暴力的遍历,那么其效率将会大大的提高。
优先级队列就是通过堆来实现的,用它来直接维护,操作会更方便。
下面给出优先级队列的版本:
算法的时间复杂度下降到了O( (|V|+|E|)*log|V| )。
dijkstra算法了。
dijkstra的原始版本做到了很好的解决问题,但是还有进一步优化的空间。
接下来展示通过“优先级队列(priority_queue)”优化时间复杂度的方法。
首先把图的类定义出来,我采用的是“class Edge”+”class Graph”的定义方案,将有向边和图的实现抽离,如有不妥,方请指正:
template<typename Type> class Edge { template<typename Type> friend class Graph; public: explicit Edge(const size_t& start, const size_t& end = 0, const Type& value = Type()) :start_idx(start), end_idx(end), value(value), flag(Flags::unvisited) {} size_t getEnd() const { return end_idx; } size_t getValue() const { return value; } ~Edge() = default; private: size_t start_idx; size_t end_idx; Type value; Flags flag; // search for next_edge unvisied edge. }; template<typename Type> class Graph { using Node = vector<Edge<Type>>; int INFTY = (1 << 20); public: Graph() : num_edges(0) {} explicit Graph(const size_t& size); ~Graph() = default; // some other functions. // return iterator points to the specific edge. decltype(auto) findEdge(const size_t& vertex_idx, const size_t& end) const; void addEdge(const size_t& vertex_idx, const size_t& end, const Type& value); void removeEdge(const size_t& vertex_idx, const size_t& end); void SingleSourcePath(); void optmzed_SingleSourcePath(); private: void init(); bool isConnected(const Node& node, const int& target); vector<Node> vertexs; size_t num_edges; };
还需要一个
enum结构来标记顶点或者有向边的状态:
enum class Flags { unvisited, be_visiting, visited, finish_visit };
然后补充一下相关基础函数的操作:
template<typename Type> inline void Graph<Type>::init() { for (auto& edges : vertexs) for (auto& edge : edges) { edge.flag = Flags::unvisited; } } template<typename Type> inline decltype(auto) Graph<Type>::findEdge(const size_t & vertex_idx, const size_t & end) const { assert(vertex_idx >= 0 && end >= 0); auto beg = vertexs[vertex_idx].begin(), ed = vertexs[vertex_idx].end(); for (; beg != ed; ++beg) { if (beg->end_idx == end) return beg; } return ed; } template<typename Type> inline void Graph<Type>::addEdge(const size_t & vertex_idx, const size_t & end, const Type & value) { assert(vertex_idx >= 0 && end >= 0 && vertex_idx != end); if (vertex_idx > vertexs.size() || end > vertexs.size()) { vertexs.resize(max(vertex_idx, end) + 1); } vertexs[vertex_idx].push_back(Edge<Type>(vertex_idx, end, value)); ++num_edges; } template<typename Type> inline void Graph<Type>::removeEdge(const size_t & vertex_idx, const size_t & end) { assert(vertex_idx < vertexs.size() && end < vertexs.size()); auto e = findEdge(vertex_idx, end); if (candi != vertexs[vertex_idx]) vertexs[vertex_idx].erase(e); }
接下来是正式的算法部分…图解的话…直接上书上的图片了:
实现如下:
template<typename Type> inline void Graph<Type>::SingleSourcePath() // dij 4000 kstra's algorithm. { init(); int size = vertexs.size(); vector<Flags> flags(size, Flags::unvisited); vector<int> ssp(size, INFTY); //initialize flags[0] = Flags::be_visiting; // start from root. ssp[0] = 0; int idx, minv; while (true) { minv = INFTY; idx = -1; for (int i = 0; i < size; ++i) { if (minv > ssp[i] && flags[i] != Flags::visited) { minv = ssp[i]; idx = i; } } if (idx == -1) // all vertexs have benn added. break; flags[idx] = Flags::visited; //update ssp[]. for (int i = 0; i < size; ++i) { if (flags[i] != Flags::visited && isConnected(vertexs[idx], i)) { int newv = findEdge(idx, i)->value; if (ssp[i] > ssp[idx]+newv) { ssp[i] = ssp[idx] + newv; flags[i] = Flags::be_visiting; } } } } for (int i = 0; i < size; ++i) { cout << i << " " << ssp[i] << endl; } }
这个版本的算法中,每次更新ssp[]时,都会有一个forloop对所有顶点都扫描一遍,再加上外层的while主循环,一张V个顶点的图,其时间复杂度为O(|V|^2),并不算很理想。倘若通过一个二叉堆来动态的维护这一个顶点的集合而不是每次暴力的遍历,那么其效率将会大大的提高。
优先级队列就是通过堆来实现的,用它来直接维护,操作会更方便。
下面给出优先级队列的版本:
template<typename Type> inline void Graph<Type>::optmzed_SingleSourcePath() { // optimized single source path algorithm . // maintain by priority queue [ binary heap ] init(); int size = vertexs.size(); vector<Flags> flags(size, Flags::unvisited); vector<int> ssp(size, INFTY); // pair: first -> second corresponding to graph: start -> end std::priority_queue<std::pair<int, int>> pq; //initialize flags[0] = Flags::be_visiting; // start from root. ssp[0] = 0; pq.push(make_pair(0, 0)); int idx; while (!pq.empty()) { pair<int, int> get = pq.top(); pq.pop(); idx = get.second; flags[idx] = Flags::visited; // get ssp[idx], if it's not the min, then skip if (ssp[idx] < get.first*(-1)) continue; // update ssp[]. int size_ = vertexs[idx].size(); for (int i = 0; i < size_; ++i) { int v = vertexs[idx][i].end_idx; if (flags[v] == Flags::visited) continue; if (ssp[v] > ssp[idx] + vertexs[idx][i].value) { ssp[v] = ssp[idx] + vertexs[idx][i].value; pq.push(make_pair(ssp[v] * (-1), v)); flags[v] = Flags::be_visiting; } } } for (int i = 0; i < size; ++i) { cout << i << " " << ssp[i] << endl; } }
算法的时间复杂度下降到了O( (|V|+|E|)*log|V| )。
相关文章推荐
- STL中的优先级队列(priority_queue)的自己实现priqueue
- STL之优先级队列priority_queue
- STL系列之五 priority_queue 优先级队列
- java中PriorityQueue优先级队列使用方法
- 优先级队列priority_queue基本操作
- STL中优先级队列的使用priority_queue
- 优先级队列priority_queue
- STL中的优先级队列(priority_queue)的自己实现priqueue
- STL——priority_queue 优先级队列
- 优先级队列Priority_queue
- C++最大堆实现priority_queue优先级队列(算法导论)
- 库函数优先级队列(priority_queue)
- C++ 优先级队列(priority_queue)
- PriorityQueue优先级队列源码分析
- STL笔记之priority_queue 优先级队列
- java中PriorityQueue优先级队列的使用
- 【CDP-云设计模式】第8章,2.优先级队列模式(Priority Queue Pattern)
- 优先级队列priority_queue
- 优先级队列(priority——queue)
- C++ 中"priority_queue" 优先级队列实例详解