求最短路径的方法:
1.深度优先或广度优先搜索算法
从起点开始访问所有深度遍历路径或广度优先路径,则到达终点节点的路径有多条,取其中路径权值最短的一条则为最短路径。
void dfs(int cur,int dst){
if(minpath<dst) return;//当前走过的路径大雨之前的最短路径,没有必要再走下去了
if(cur==en){//临界条件,当走到终点n
if(minpath>dst){
minpath=dst;
return;
}
}
for(int i=1;i<=n;i++){
if(mark[i]==0&&edge[cur][i]!=inf&&edge[cur][i]!=0){
mark[i]=1;
dfs(i,dst+edge[cur][i]);
mark[i]=0;//需要在深度遍历返回时将访问标志置0
}
}
return;
}
2.Dijkstra算法(迪杰斯特拉算法)
假设存在G=<V,E>,源顶点为V0,U={V0},dist[i]记录V0到i的最短距离,path[i]记录从V0到i路径上的i前面的一个顶点。
1)从V-U中选择使dist[i]值最小的顶点i,将i加入到U中;
2)更新与i直接相邻顶点的dist值。dist[j]=min{dist[j],dist[i]+matrix[i][j]}
3)直到U=V,算法停止。
int dijkstra(int n)
{
//初始化 v[0]到v[i]的距离
for(int i=1;i<=n;i++)dis[i]=w[0][i];
vis[0]=1;//标记v[0]点
for(int i=1;i<=n;i++)
{
//查找最近点
int min=INF,k=0;
for(int j=0;j<=n;j++)
if(!vis[w]&&dis[j]<min)
min=dis[w],k=j;
vis[k]=1;//标记查找到的最近点
//判断是直接v[0]链接v[j]段,还是经过v[k]连接v[j]更短
for(int j=1;j<=n;j++)
if(!vis[j]&&min+w[k][j]<dis[j])
d[j]=min+w[k][j];
}
return dis[j];
}
dijkstra算法是处理单源最短路径的有效算法,但局限于边的权值非负的情况,若图中出现权值为负的边,dijkstra算法就会失效。并且图中不能出现负有向圈。
3.bellman-ford
(1)创建源顶点v到图中所有顶点的距离的集合Distant,且指定一个距离值,源顶点到其本身的距离为0.Distant[i]记录从源点到顶点i的路劲长度
(2)计算最短路径,并进行n-1次遍历(n为顶点数);对于每一条边e=uv,如果Distant[u]+w(uv)<DIstant[v]则Distant[v]=Distant[u]+w(uv),w(uv)为边uv的权值。
(3)如果第(2)步没有对数组集合Distant更新,则说明已经是最短路径,或者部分点不可达。直接执行下一次循环
(4)判断负权回路,若在n-1次遍历之后,仍能对数组更新,则图中存在负环路
核心代码:
for(k = 1; k <= n - 1; k++)
for(i = 1; i <= m; i++)
if(dis[v[i]] > dis[u[i]] + w[i])
dis[v[i]] = dis[u[i]] + w[i];
4.SPFA
SPFA是对bellman-ford的优化,能省去一些冗长的部分,优化时间复杂度。
(1)建立一个队列,初始时队列里只有起始点,再建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。
(2)然后只要队列不为空就取出队首进行松弛;松弛成功后将被松弛的节点(如果不在队列中)加入队列。
松弛操作即为:用该点去刷新起始点到其他点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。
(3)重复执行直到队列为空。
void SPFA(int s)
{
for(int i=0;i<N;i++) vis[i]=0,d[i]=inf;
d[s]=0; vis[s]=1;
queue<int> q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i!=-1;i=G[i].next){
int v=E[i].to;
int w=E[i].w;
if(d[v]>d[u]+w){
d[v]=d[u]+w;
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
}
}
bellman-ford与SPFA的区别:
5.floyd算法(佛洛依德算法)
从任意节点i到任意节点j的最短路径不外乎2种可能,一是直接从i到j,二是从i经过若干个节点k到j。我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。
用dist[v][w]记录每一对顶点的最短距离。dist为距离数组,path为路径数组
//Floyd算法(多源最短路径算法)
bool Floyd(){
for(int k = 1 ; k < this->Nv+1 ; k++){ //k代表中间顶点
for(int i = 1 ; i < this->Nv+1 ; i++){//i代表起始顶点
for(int j = 1 ; j < this->Nv+1 ; j++){//j代表终点
if(this->dist[i][k] + this->dist[k][j] < this->dist[i][j]){
this->dist[i][j] = this->dist[i][k] + this->dist[k][j];
if(i == j && this->dist[i][j] < 0){//发现了负值圈
return false;
}
this->path[i][j] = k;
}
}
}
}
return true;
}
上述函数在一个图类中进行定义,dist和path均为私有变量,dist为距离数组,path为路径数组。