(1)概述:该算法可以计算正权图的单源最短路,即从一个单个源点出发,到所有结点的最短路。伪代码如下:
清除所有点的标号
设d[0]=0,其他d[i]=INF
循环n次
{
在所有未标号的结点中,选出d值最小的结点x
给结点x标记
对于从x出发的所有边(x,y),更新d[y]=min{d[y],d[x]+w(x,y)}
}
下面给出优化到O(M*logN)的代码
#define N 10000
#define INF 1000000
struct Edge
{
int from,to,dist;
Edge(int u, int v, int d) :from(u), to(v), dist(d){}
};
struct HeapNode//将d值,编号捆绑为一个结构体
{
int d, u;
HeapNode(int dd, int uu) :d(dd), u(uu){};
bool operator <(const HeapNode&rhs)const
{
return d>rhs.d;
}
};
struct Dijkstra
{
int n, m;
vectoredges;
vectorG[N];
bool done[N];//是否已经永久标记
int d[N]; //s到各个点的距离
int p[N]; //最短路中的上一条边
void init()//初始化
{
this->n = n;
for (int i = 0; i < n; i++)
G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int dist)//添加两个端点,距离
{
edges.push_back(Edge(from, to, dist));
m = edges.size();
G[from].push_back(m - 1);//这条边的序号是m-1
}
void dijkstra(int s)//s是源点,注意s表示的是边序号
{
priority_queueQ;
for (int i = 0; i < n; i++)
d[i] = INF;
d[s] = 0;//源点的d值为0
memset(done, 0, sizeof(done));
Q.push(HeapNode(0,s));
while (!Q.empty())//利用BFS的思路
{
HeapNode x = Q.top(); Q.pop();
int u = x.u;//边的u端点
if (done[u])continue;//已经取过,扔掉
done[u] = true;
for (int i = 0; i < G[u].size(); i++)//考虑从u出发的所有边
{
Edge& e = edges[G[u][i]];//e是第i条和u相连的边
if (d[e.to]>d[u] + e.dist)//松弛操作
{
d[e.to] = d[u] + e.dist;
p[e.to] = G[u][i];//记录e.to这个端点的父边
Q.push(HeapNode(d[e.to], e.to));
}
}
}
}
};
(1)概述:当负权存在时,最短路不一定存在。但有这样一个事实:如果最短路存在,一定存在一个不含环的最短路。(分零环,正环,负环三种讨论即可证明)。
既然如此,那么最短路最多只经过(起点不算)n-1个结点,可以通过n-1次松弛操作得到。下面利用了FIFO队列代替这n-1次循环检查。因为这样一个结点可以多次进入队列。这样的好处是最坏情况下时间复杂度才是O(N*M)。
#define N 10000
#define INF 1000000
struct Edge
{
int from, to, dist;//dist可正可负
Edge(int u, int v, int d) :from(u), to(v), dist(d){}
};
struct HeapNode//将d值,编号捆绑为一个结构体
{
int d, u;
HeapNode(int dd, int uu) :d(dd), u(uu){};
bool operator <(const HeapNode&rhs)const
{
return d>rhs.d;
}
};
struct Bellman_Ford
{
int n, m;
vectoredges;
vectorG[N];
bool done[N];//是否已经永久标记
int d[N]; //s到各个点的距离
int p[N]; //最短路中的上一条边
int inq[N],cnt[N];
void init()//初始化
{
this->n = n;
for (int i = 0; i < n; i++)
G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int dist)//添加两个端点,距离
{
edges.push_back(Edge(from, to, dist));
m = edges.size();
G[from].push_back(m - 1);//这条边的序号是m-1
}
bool bellman_ford(int s)
{
queueQ;
memset(inq, 0, sizeof(inq));//inq数组标记是否进入队列
memset(cnt, 0, sizeof(cnt));//cnt数组标记到结点入队列的次数
for (int i = 0; i < n; i++)
d[i] = INF;
d[s] = 0;
inq[s] = true;
Q.push(s);
while (!Q.empty())
{
int u = Q.front(); Q.pop();
inq[u] = false;
for (int i = 0; i < G[u].size(); i++)
{
Edge&e = edges[G[u][i]];
if (d[u]d[u] + e.dist)//松弛操作
{
d[e.to] = d[u] + e.dist;
p[e.to] = G[u][i];
if (!inq[e.to])
{
Q.push(e.to);
inq[e.to] = true;
if (++cnt[e.to] > n)//入队列次数超过n次,说明有负圈存在,及时退出
return false;
}
}
}
}
return true;
}
};
(2)简易版本的Bellman-Ford算法(只适用于不含负圈的图)
#define N 1000
#define INF 100000000
struct Edge{ int from, to, cost; };
Edge es[N];
int d[N]; //最短距离
int V, E;
void shortest_path(int s)
{
for (int i = 0; i < V; i++)//初始化为无穷大
d[i] = INF;
d[s] = 0;
while (1)//循环次数不超过V-1次
{
bool update = false;
for (int i = 0; i < E; i++)//考察每一条边
{
Edge e = es[i];
if (d[e.from] != INF&&d[e.to]>d[e.from] + e.cost)
{
d[e.to] = d[e.from] + e.cost;
update = true;
}
}
if (!update)break;//只要不含有负圈,更新的次数就是有限的
}
}
(1)概述:如果需要求每两个点之间的最短路(权值可正可负),可以用下面的Floyd-Warshall算法。
(i)适用于带权图
#define N 1000
#define INF 10000001 //预先估计一下最短路上限
int d[N][N];//记录u,v之间的权值
int n;
void Floyd()//带权图
{
for (int k = 0; k < n;k++)
for (int i = 0; i < n;i++)
for (int j = 0; j < n; j++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
(ii)适用于有向无权图(DAG)
#define N 1000
int d[N][N];//记录u,v之间的权值
int n;
void Floyd()//适用于DAG,只判断是否连通
{
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
d[i][j] = d[i][j] || (d[i][k] && d[k][j]);
}