主要介绍最短路径问题和松弛操做、Dijkstra算法的思想、负权边和Bellman-Ford算法等算法
一个节点到另外一个节点最短的路径,路径规划问题。数组
对于无权图进行广度优先遍历就是求出了一个最短路径(求出的是一颗最短路径树)微信
从起始点到其余节点路径最短的树数据结构
无权图的最短路径。函数
松弛操做是指对于每一个顶点v∈V,都设置一个属性d[v],用来描述从源点s到v的最短路径上权值的上界,称为最短路径估计(shortest-path estimate)。测试
最小索引堆this
松弛操做spa
// Dijkstra算法求最短路径
template<typename Graph, typename Weight>
class Dijkstra{
private:
Graph &G; // 图的引用
int s; // 起始点
Weight *distTo; // distTo[i]存储从起始点s到i的最短路径长度
bool *marked; // 标记数组, 在算法运行过程当中标记节点i是否被访问
vector<Edge<Weight>*> from; // from[i]记录最短路径中, 到达i点的边是哪一条
// 能够用来恢复整个最短路径
public:
// 构造函数, 使用Dijkstra算法求最短路径
Dijkstra(Graph &graph, int s):G(graph){
// 算法初始化
assert( s >= 0 && s < G.V() );
this->s = s;
distTo = new Weight[G.V()];
marked = new bool[G.V()];
for( int i = 0 ; i < G.V() ; i ++ ){
distTo[i] = Weight();
marked[i] = false;
from.push_back(NULL);
}
// 使用索引堆记录当前找到的到达每一个顶点的最短距离
IndexMinHeap<Weight> ipq(G.V());
// 对于其实点s进行初始化
distTo[s] = Weight();
from[s] = new Edge<Weight>(s, s, 0);
ipq.insert(s, distTo[s] );
marked[s] = true;
while( !ipq.isEmpty() ){
int v = ipq.extractMinIndex();
// distTo[v]就是s到v的最短距离
marked[v] = true;
// 对v的全部相邻节点进行更新
typename Graph::adjIterator adj(G, v);
for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ){
int w = e->other(v);
// 若是从s点到w点的最短路径尚未找到
if( !marked[w] ){
// 若是w点之前没有访问过,
// 或者访问过, 可是经过当前的v点到w点距离更短, 则进行更新
if( from[w] == NULL || distTo[v] + e->wt() < distTo[w] ){
distTo[w] = distTo[v] + e->wt();
from[w] = e;
if( ipq.contain(w) )
ipq.change(w, distTo[w] );//最小索引堆支持change操做
else
ipq.insert(w, distTo[w] );
}
}
}
}
}
// 析构函数
~Dijkstra(){
delete[] distTo;
delete[] marked;
delete from[0];
}
// 返回从s点到w点的最短路径长度
Weight shortestPathTo( int w ){
assert( w >= 0 && w < G.V() );
assert( hasPathTo(w) );
return distTo[w];
}
// 判断从s点到w点是否联通
bool hasPathTo( int w ){
assert( w >= 0 && w < G.V() );
return marked[w];
}
// 寻找从s到w的最短路径, 将整个路径通过的边存放在vec中
void shortestPath( int w, vector<Edge<Weight>> &vec ){
assert( w >= 0 && w < G.V() );
assert( hasPathTo(w) );
// 经过from数组逆向查找到从s到w的路径, 存放到栈中
stack<Edge<Weight>*> s;
Edge<Weight> *e = from[w];
while( e->v() != this->s ){
s.push(e);
e = from[e->v()];
}
s.push(e);
// 从栈中依次取出元素, 得到顺序的从s到w的路径
while( !s.empty() ){
e = s.top();
vec.push_back( *e );
s.pop();
}
}
// 打印出从s点到w点的路径
void showPath(int w){
assert( w >= 0 && w < G.V() );
assert( hasPathTo(w) );
vector<Edge<Weight>> vec;
shortestPath(w, vec);
for( int i = 0 ; i < vec.size() ; i ++ ){
cout<<vec[i].v()<<" -> ";
if( i == vec.size()-1 )
cout<<vec[i].w()<<endl;
}
}
};
复制代码
main.cpp:3d
// 测试咱们的Dijkstra最短路径算法
int main() {
string filename = "testG1.txt";
int V = 5;
SparseGraph<int> g = SparseGraph<int>(V, true);
// Dijkstra最短路径算法一样适用于有向图
//SparseGraph<int> g = SparseGraph<int>(V, false);
ReadGraph<SparseGraph<int>, int> readGraph(g, filename);
cout<<"Test Dijkstra:"<<endl<<endl;
Dijkstra<SparseGraph<int>, int> dij(g,0);
for( int i = 0 ; i < V ; i ++ ){
if(dij.hasPathTo(i)){
cout<<"Shortest Path to "<<i<<" : "<<dij.shortestPathTo(i)<<endl;
dij.showPath(i);
}
else
cout<<"No Path to "<<i<<endl;
cout<<"----------"<<endl;
}
return 0;
}
复制代码
dijkstra算法不能处理负权边code
拥有负权环的不存在最短路径
两边造成负权环
松弛操做的核心是咱们找到了一条边的路径,咱们看一下有没有两条边的路径比他权值小。
// 使用BellmanFord算法求最短路径
template <typename Graph, typename Weight>
class BellmanFord{
private:
Graph &G; // 图的引用
int s; // 起始点
Weight* distTo; // distTo[i]存储从起始点s到i的最短路径长度
vector<Edge<Weight>*> from; // from[i]记录最短路径中, 到达i点的边是哪一条
// 能够用来恢复整个最短路径
bool hasNegativeCycle; // 标记图中是否有负权环
// 判断图中是否有负权环
bool detectNegativeCycle(){
for( int i = 0 ; i < G.V() ; i ++ ){
typename Graph::adjIterator adj(G,i);
for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() )
if( from[e->v()] && distTo[e->v()] + e->wt() < distTo[e->w()] )
return true;
}
return false;
}
public:
// 构造函数, 使用BellmanFord算法求最短路径
BellmanFord(Graph &graph, int s):G(graph){
this->s = s;
distTo = new Weight[G.V()];
// 初始化全部的节点s都不可达, 由from数组来表示
for( int i = 0 ; i < G.V() ; i ++ )
from.push_back(NULL);
// 设置distTo[s] = 0, 而且让from[s]不为NULL, 表示初始s节点可达且距离为0
distTo[s] = Weight();
from[s] = new Edge<Weight>(s, s, 0); // 这里咱们from[s]的内容是new出来的, 注意要在析构函数里delete掉
// Bellman-Ford的过程
// 进行V-1次循环, 每一次循环求出从起点到其他全部点, 最多使用pass步可到达的最短距离
for( int pass = 1 ; pass < G.V() ; pass ++ ){
// 每次循环中对全部的边进行一遍松弛操做
// 遍历全部边的方式是先遍历全部的顶点, 而后遍历和全部顶点相邻的全部边
for( int i = 0 ; i < G.V() ; i ++ ){
// 使用咱们实现的邻边迭代器遍历和全部顶点相邻的全部边
typename Graph::adjIterator adj(G,i);
for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() )
// 对于每个边首先判断e->v()可达
// 以后看若是e->w()之前没有到达过, 显然咱们能够更新distTo[e->w()]
// 或者e->w()之前虽然到达过, 可是经过这个e咱们能够得到一个更短的距离, 便可以进行一次松弛操做, 咱们也能够更新distTo[e->w()]
if( from[e->v()] && (!from[e->w()] || distTo[e->v()] + e->wt() < distTo[e->w()]) ){
distTo[e->w()] = distTo[e->v()] + e->wt();
from[e->w()] = e;
}
}
}
hasNegativeCycle = detectNegativeCycle();
}
// 析构函数
~BellmanFord(){
delete[] distTo;
delete from[s];
}
// 返回图中是否有负权环
bool negativeCycle(){
return hasNegativeCycle;
}
// 返回从s点到w点的最短路径长度
Weight shortestPathTo( int w ){
assert( w >= 0 && w < G.V() );
assert( !hasNegativeCycle );
assert( hasPathTo(w) );
return distTo[w];
}
// 判断从s点到w点是否联通
bool hasPathTo( int w ){
assert( w >= 0 && w < G.V() );
return from[w] != NULL;
}
// 寻找从s到w的最短路径, 将整个路径通过的边存放在vec中
void shortestPath( int w, vector<Edge<Weight>> &vec ){
assert( w >= 0 && w < G.V() );
assert( !hasNegativeCycle );
assert( hasPathTo(w) );
// 经过from数组逆向查找到从s到w的路径, 存放到栈中
stack<Edge<Weight>*> s;
Edge<Weight> *e = from[w];
while( e->v() != this->s ){
s.push(e);
e = from[e->v()];
}
s.push(e);
// 从栈中依次取出元素, 得到顺序的从s到w的路径
while( !s.empty() ){
e = s.top();
vec.push_back( *e );
s.pop();
}
}
// 打印出从s点到w点的路径
void showPath(int w){
assert( w >= 0 && w < G.V() );
assert( !hasNegativeCycle );
assert( hasPathTo(w) );
vector<Edge<Weight>> vec;
shortestPath(w, vec);
for( int i = 0 ; i < vec.size() ; i ++ ){
cout<<vec[i].v()<<" -> ";
if( i == vec.size()-1 )
cout<<vec[i].w()<<endl;
}
}
};
复制代码
main.cpp:
// 测试Bellman-Ford算法
int main() {
string filename = "testG2.txt";
//string filename = "testG_negative_circle.txt";
int V = 5;
SparseGraph<int> g = SparseGraph<int>(V, true);
ReadGraph<SparseGraph<int>, int> readGraph(g, filename);
cout<<"Test Bellman-Ford:"<<endl<<endl;
BellmanFord<SparseGraph<int>, int> bellmanFord(g,0);
if( bellmanFord.negativeCycle() )
cout<<"The graph contain negative cycle!"<<endl;
else
for( int i = 1 ; i < V ; i ++ ) {
if (bellmanFord.hasPathTo(i)) {
cout << "Shortest Path to " << i << " : " << bellmanFord.shortestPathTo(i) << endl;
bellmanFord.showPath(i);
}
else
cout << "No Path to " << i << endl;
cout << "----------" << endl;
}
return 0;
}
复制代码
用于有向图,由于无向图中一条负权边就等价于两个方向都有,会造成环。
具体实现,distTo[i] 初始化为“正无穷”
if( from[e->v()] && (!from[e->w()] || distTo[e->v()] + e->wt() < distTo[e->w()]) ){
distTo[e->w()] = distTo[e->v()] + e->wt();
from[e->w()] = e;
}
复制代码
利用队列数据结构
queue-based bellman-ford算法
-------------------------华丽的分割线--------------------
看完的朋友能够点个喜欢/关注,您的支持是对我最大的鼓励。
想了解更多,欢迎关注个人微信公众号:番茄技术小栈