贝尔曼-福特算法(Bellman-Ford)
是求解单源最短路径的一种算法。它的原理是对图进行V-1次松弛操作,得到所有可能的最短路径。其优于迪杰斯特拉的方面是边的权值可以为负数,缺点是时间复杂度过高,高达O(VE)。一般用于实现通过m次迭代求出从起点到终点不超过m条边构成的最短路径。
算法原理:松弛操作
对于一条从 顶点u指向 顶点v的边 u-->v来说,如果满足 d[u]+w(u,v)<d[v],就更新 d[v],使得 d[v]=d[u]+w(u,v);这就是对 边uv的一次放松操作;(其中,w(u,v)表示边的权重,d(u)表示顶点u到达源s的最短距离(目前已知))。
算法思路:两层循环,内层循环所有边,外层循环就是循环所有边的次数
算法实现:
public double maxProbability(int n, int[][] edges, double[] succProb, int start, int end)
{ //创建并初始化最优解数组
double dp[] = new double[n];//初始化最优解数组,由于初始化为0,所以不需要循环赋值
//for (int i = 0; i < n; i++) {
// dp[i] = 0;
//}
//初始化起点
dp[start] = 1;//遍历节点列表进行松弛操作
int count = 1;
while (count < n) {//至多循环n-1次,如果超过次数返回失败值
boolean k = false;
for (int j = 0; j < edges.length; j++) {
//判断并更新对应dp值,求最大值
if (dp[edges[j][0]] * succProb[j] > dp[edges[j][1]]) {
dp[edges[j][1]] = dp[edges[j][0]] * succProb[j];
k = true;
}
//因为是无向图,所以再反向遍历
if (dp[edges[j][1]] * succProb[j] > dp[edges[j][0]]) {
dp[edges[j][0]] = dp[edges[j][1]] * succProb[j];
k = true;
}
}
if (!k) break;//循环一遍未修改,则表示已完成松弛操作
count++;
}
if (count >= n) {
//如果循环超过n-1次返回失败值
return 0;
} else {
return dp[end];
}
}
注意:如果图中有负权回路的话,最短路不一定存在。
SPFA算法
是Bellman-Ford算法的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。SPFA 最坏情况下时间复杂度和朴素Bellman-Ford 相同,为 O(VE)。
算法原理:
动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止
算法实现:
#include<iostream>
#include<vector>
#include<list>
using namespace std;
struct Edge
{
int to,len;
};
bool spfa(const int &beg,//出发点
const vector<list<Edge> > &adjlist,//邻接表,通过传引用避免拷贝
vector<int> &dist,//出发点到各点的最短路径长度
vector<int> &path)//路径上到达该点的前一个点
//没有负权回路返回0
//福利:这个函数没有调用任何全局变量,可以直接复制!
{
const int INF=0x7FFFFFFF,NODE=adjlist.size();//用邻接表的大小传递顶点个数,减少参数传递
dist.assign(NODE,INF);//初始化距离为无穷大
path.assign(NODE,-1);//初始化路径为未知
list<int> que(1,beg);//处理队列
vector<int> cnt(NODE,0);//记录各点入队次数,用于判断负权回路
vector<bool> flag(NODE,0);//标志数组,判断是否在队列中
dist[beg]=0;//出发点到自身路径长度为0
cnt[beg]=flag[beg]=1;//入队并开始计数
while(!que.empty())
{
const int now=que.front();
que.pop_front();
flag[now]=0;//将当前处理的点出队
for(list<Edge>::const_iterator//用常量迭代器遍历邻接表
i=adjlist[now].begin(); i!=adjlist[now].end(); ++i)
if(dist[i->to]>dist[now]+i->len)//不满足三角不等式
{
dist[i->to]=dist[now]+i->len;//更新
path[i->to]=now;//记录路径
if(!flag[i->to])//若未在处理队列中
{
if(NODE==++cnt[i->to])return 1;//计数后出现负权回路
if(!que.empty()&&dist[i->to]<dist[que.front()])//队列非空且优于队首(SLF)
que.push_front(i->to);//放在队首
else que.push_back(i->to);//否则放在队尾
flag[i->to]=1;//入队
}
}
}
return 0;
}
int main()
{
int n_num,e_num,beg;//含义见下
cout<<"输入点数、边数、出发点:";
cin>>n_num>>e_num>>beg;
vector<list<Edge> > adjlist(n_num,list<Edge>());//默认初始化邻接表
for(int i=0,p; i!=e_num; ++i)
{
Edge tmp;
cout<<"输入第"<<i+1<<"条边的起点、终点、长度:";
cin>>p>>tmp.to>>tmp.len;
adjlist[p].push_back(tmp);
}
vector<int> dist,path;//用于接收最短路径长度及路径各点
if(spfa(beg,adjlist,dist,path))cout<<"图中存在负权回路\n";
else for(int i=0; i!=n_num; ++i)
{
cout<<beg<<"到"<<i<<"的最短距离为"<<dist[i]<<",反向打印路径:";
for(int w=i; path[w]>=0; w=path[w])cout<<w<<"<-";
cout<<beg<<'\n';
}
}
比较:
SPAF与BFS算法比较,复杂度相对稳定。但在稠密图中复杂度比Dijkstra算法差。