#include <iostream> using namespace std; #define NoEdge 1000 //NoEdge表示无法连通 #define NIL -1 //表示无前驱 #define GRAY 0 //表示正在拓展的顶点 #define WHITE -1 //表示还未到达过的顶点 #define BLACK 1 //表示顶点的4周都已访问结束 typedef struct { char *vertices;//顶点信息 int **edge;//有向边,无法连通用NoEdge int num;//顶点数量 }Graph;//邻接矩阵表示图 //为了方便,将源点默认设置为第一个顶点,输入图时请注意第一个输入源点 class GraphOperation { private: Graph graph;//邻接矩阵图 int *minDist;//单源最短路径记录 int *parent;//单源最短路径顶点前驱记录 int *arrayList;//记录拓扑排序序列 int count;//用于拓扑排序插入记录下标 int *visited;//深度优先遍历时标记访问 public: //构造函数 GraphOperation(int num) { graph.num=num; graph.edge=new int* [num+1]; for(int i=0;i<=num;i++) { graph.edge[i]=new int [num+1]; } graph.vertices=new char [num+1]; minDist=new int [num+1]; parent=new int[num+1]; arrayList=new int[num+1]; count=num; visited=new int[num+1]; } //输入图的信息 void input() { for(int i=1;i<=graph.num;i++) { cout<<“输入第”<<i<<“个顶点的信息:”; cin>>graph.vertices[i]; } for(int i=1;i<=graph.num;i++) { for(int j=i+1;j<=graph.num;j++) { cout<<“输入”<<graph.vertices[i]<<“到”<<graph.vertices[j]<<“的权值:”; cin>>graph.edge[i][j]; cout<<“输入”<<graph.vertices[j]<<“到”<<graph.vertices[i]<<“的权值:”; cin>>graph.edge[j][i]; } } } //根据parent[i]回溯打印路径上的顶点 void printPath(int i) { if(i==NIL) { return; } printPath(parent[i]); cout<<graph.vertices[i]<<” “; } //打印所有顶点的单源最短路径 void printAll() { for(int i=1;i<=graph.num;i++) { printPath(i); cout<<“,路径长度”<<minDist[i]<<endl; } } //松弛初始化 void initiate() { //为所有顶点初始化 for(int i=1;i<=graph.num;i++) { minDist[i]=NoEdge; parent[i]=NIL; } minDist[1]=0;//为源点初始化 } //松弛技术 void relax(int i,int j) { if(minDist[i]+graph.edge[i][j]<minDist[j]) { minDist[j]=minDist[i]+graph.edge[i][j]; parent[j]=i; } } //拓扑排序核心算法 void topOlogicalSort(int i) { visited[i]=GRAY;//正在访问周边顶点 for(int j=1;j<=graph.num;j++)//深度优先遍历 { if(visited[j]==WHITE) { topOlogicalSort(j); } } visited[i]=BLACK;//该顶点周围都已遍历结束 arrayList[count–]=i;//插入到拓扑序列中 } //拓扑排序 void topSort() { for(int i=1;i<=graph.num;i++) { visited[i]=WHITE;//设置所有顶点为未访问 } for(int i=1;i<=graph.num;i++) { if(visited[i]==WHITE) { topOlogicalSort(i);//对每个顶点深度优先拓扑排序 } } } //有向无回路(dag图)拓扑排序后,对拓扑序列遍历一次计算出单源最短路径(也可以修改后求出关键路径(最长的)) void dagShortPaths() { initiate();//松弛初始化 topSort();//对图进行拓扑排序,结果存储在arrayList[]内 //遍历一次拓扑序列,完成单源最短路径问题 for(int i=1;i<=graph.num;i++) { for(int j=1;j<=graph.num;j++) { if(i!=j&&graph.edge[arrayList[i]][j]!=NoEdge) { relax(arrayList[i],j); } } } } //Bellman-Ford 算法,通用解决单源最短路径,图可以有回路,!!!可以有负权回路,算法都能判断出结果 bool bellmanFord() { initiate();//松弛初始化 //对整个图松弛num-1次(因为源点无需松弛),如果没有负权回路,则能计算出所有顶点的单源最短路径 for(int i=1;i<=graph.num-1;i++) { for(int j=1;j<=graph.num;j++) { for(int k=1;k<=graph.num;k++) { if(j!=k&&graph.edge[j][k]!=NoEdge) { relax(j,k); } } } } for(int i=1;i<=graph.num;i++)//检查是否已全部求的最短单源路径,如果有负权回路,则一定不满足下列检查,返回false { for(int j=1;j<=graph.num;j++) { if(i!=j&&graph.edge[i][j]!=NoEdge) { if(minDist[j]>minDist[i]+graph.edge[i][j]) { return false; } } } } return true; } //迪克拉斯算法(单源最短路径问题,要求没有负边,可以有回路) void dijkstra() { int min;//用于寻找到源点最近的顶点 int temp;//用于存储距源点最近的顶点的下标 //初始化松弛 initiate(); //初始化标记数组 for(int i=1;i<=graph.num;i++) { visited[i]=WHITE; } visited[1]=BLACK;//源点已经加入到最短路径中 //对源点的出边进行松弛 for(int i=1;i<=graph.num;i++) { if(visited[i]==WHITE&&graph.edge[1][i]!=NoEdge) { relax(1,i); } } //将出去源点的其他顶点加入到最短路径树中 for(int i=2;i<=graph.num;i++) { min=NoEdge; for(int j=2;j<=graph.num;j++) { if(visited[j]==WHITE&&minDist[j]<min) { min=minDist[j]; temp=j; } } if(min==NoEdge) { cout<<“无法生成所有顶点的最短路径,存在非连通分量”<<endl; return; } visited[temp]=BLACK; //松弛未加入到路径上的结点 for(int m=2;m<=graph.num;m++) { if(visited[m]==WHITE&&graph.edge[temp][m]!=NoEdge) { relax(temp,m); } } } } }; //注意,将源点第一个输入 void main() { //********Bellman-Ford算法测试:最通用的算法,任何图都可以解决 // GraphOperation test1(5);//5个顶点 // test1.input();//源点第一个输入 // test1.bellmanFord();//计算单源最短路径 // test1.printAll();//打印所有单源最短路径 //********有向无回路图的单源最短路径算法,利用拓扑排序+松弛技术 // GraphOperation test2(6);//dag图有6个顶点 // test2.input();//输入dag图的信息 // test2.dagShortPaths();//拓扑排序+按拓扑序列逐次松弛得出单源最短路径 // test2.printAll();//打印所有单源最短路径 //********迪克拉斯算法测试:使用于无负边权的图 // GraphOperation test3(5);//5个顶点 // test3.input();//输入图的信息 // test3.dijkstra();//迪克拉斯算法 // test3.printAll();//打印结果 } //Bellman-Ford算法(注意:允许负权边,允许回路,负权回路(能够判断)),测试用图的邻接矩阵如下:(1000为没有路径,这里用NO代替一下) // s t x y z //s 6 no 7 no //t no 5 8 -4 //x no -2 no no //y no no -3 9 //z 2 no 7 no //有向无回路的单源最短路径算法,注意应用,必须无回路,但可以有负权边(1000为没有路径,这里用no代替一下) // s r t x y z // s no 2 6 no no // r 5 3 no no no // t no no 7 4 2 // x no no no -1 1 // y no no no no -2 // z no no no no no
首先了解松弛技术,即给每个顶点设置到源点的最短距离,设置前驱. 在有限次的对边的松弛之后,可以将所有顶点的前驱与单源最短距离确定.
拓扑排序: 图的深度优先遍历的应用,对一个图进行深度优先的最后将该顶点头插到线性表中,那么线性表中只有自左至右的边,有边的两个的顶点的运行顺序一定是从左至右的,没有边相连的则无所谓. 这就给出了一个线性排序,它能够确定事情发展的先后次序. 从图上来看,就是一个单源的流程图,上部的事情肯定要比下部的事情先做,然后有些事情之间没有必要注意先后顺序 . 拓扑排序就是完成了一个事情发现先后顺序的排序,拓扑排序后对于松弛技术又有了进一步优化,只需要对拓扑序列做一趟松弛就完成了所有结点的单源最短路径问题,但是拓扑排序的适应范围仅限于有向的,没有回路的类似流程图的图,权可以为负,但不可以有回路。
Bellman-Ford算法是一种通用的单源最短路径做法 。 通过对所有的边松弛num-1次,就把除源点外的num-1个顶点的最短单源路径确定下来。 允许有回路,负权,负权回路(会自动判别,如果存在,则算法失败).
迪克拉斯算法就是对S集合与V-S集合之间操作,对每次选入S的顶点与V-S的顶点之间做松弛,n-1次后完成。
松弛算法对源点的minDist初始化为0,这也是为什么对边松弛有限次后有结果的原因.