Dijkstra(迪杰斯特拉) 算法
迪杰斯特拉算法中有几个数组:s[ i ], dist[ i ](假设起始点是u,终点为v)
- s[i] :在s数组的意思是已经找到从u开始的最短路
- dist[i] : 表示在目前为止计算到从u到v 的最短的距离
迪杰斯特拉算法三步骤:
- 在dist[i]数组中找到S[i]为0的点,并且dist[i]最小的点u
- 将u加如集合S[i],即把S[u]标为1
- 修改与u链接的点的最短路径dist[i]和上一节点path[i]
迪杰斯特拉算法就是通过dist【v】最小的开始确定,从v到其他点继续确定,应该是一个贪心算法
struct edge{ //邻接表存图
int v;
int w;
int next;
}e[maxn];
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;
// 优先队列从小到大排序
void Dijkstra(int u)//u为源点
{
s[u] = 0;
dis[u] = 0; // u到自己本身的距离是0
q.push(make_pair(dis[u], u)); //入队
while(!q.empty())
{
int u = q.top()->second; //将当前dis最小的结点取出
q.pop();
if(s[u]) continue;
s[u] = 1; //加入到s集合中
for(int i = head[u]; ~i; i = e[i].next)
{
dis[edge[i].v] = min(dis[u]+edge[v].w, dis[edge[i].v]);
q.push(make_pair<dis[edge[i].v], edge[i].v>);
}
}
}
说白了Dijkstra算法就是通过访问dis最小的点来更新他周围的结点
Floyd算法
floyd算法用来解决多源点,但是不能够带有负权值的边
void floyd(){
int i, j, k;
for( k = 0; k < n ; i++)//经过k点
for(j = 0; j < n; j++)//从j点开始
for(i = 0; i < n; i++)//终点为i
{
edge[i][j] = min(edge[i][k]+ege[k][j], edge[i][j]);
}
}
Bellman-Ford算法(贝尔曼-福特算法)
贝尔曼算法只限制于没有负环的(负环指:这个环的所有边相加是负值)
int bellman_ford(int u){
memset(dist, 63, sizeof(dist));
dist[u] = 0;
for(int i = 1; i < n; i++ )//这里控制的是通过几条边到达源点,这里最多n-1条,不能n次循环,因为如果要经过n-1条边到达,那就是有环了
{
bool flag = 0;
for(int j = 1; j <= n ;j++)//先找到一条边到达源点的点的距离,第二次循环在第一次找到的结果上继续找经过两条边到达源点的点。
for(int z = head[j]; ~z; z = edge[z].next)
{
if(dist[j]+edge[z].w < dist[edge[z].v])
{
dist[edge[z].v] = dist[j]+edge[z].w;
flag = 1;
}
//由于这部操作所以带有负值的环只会执行一次
}
if(flag)
break;
}
bool flag = 0;//因为用了链式前面向星,所以不是很方便直接遍历所有边,通过点来遍历,寻找负环,有负环直接break
for(int i = 1; i <= n; i++)
{
for(int z = head[i]; ~z; z = edge[z].next)
{
if(dist[edge[z].v] > (dist[edge[z].u]+edge[z].w))
{
flag = 1;
break;
}
}
}
if(flag)
return 0;
else
return 1;
}
外循环其实就只有n-1次,执行外循环一次代表找经过一条边到达源点的距离,执行两次代表经过两次到达源点的距离,执行n-1次代表经过n-1次到达源点的距离。
词条模板
#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 0x3f3f3f3f
#define N 1010
int nodenum, edgenum, original; //点,边,起点
typedef struct Edge //边
{
int u, v;
int cost;
}Edge;
Edge edge[N];
int dis[N], pre[N], aft[N];
bool Bellman_Ford()
{
for (int i = 1; i <= nodenum; ++i) //初始化
dis[i] = (i == original ? 0 : MAX);
for (int i = 1; i <= nodenum - 1; ++i)
for (int j = 1; j <= edgenum; ++j)
if (dis[edge[j].v] > dis[edge[j].u] + edge[j].cost) //松弛(顺序一定不能反~)
{
dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;//源点到v距离=源点到u距离 + u到v距离
pre[edge[j].v] = edge[j].u;//pre[i]->i
aft[edge[j].u] = edge[j].v;//i->aft[i]
}
bool flag = 1; //判断是否含有负权回路
for (int i = 1; i <= edgenum; ++i)
if (dis[edge[i].v] > dis[edge[i].u] + edge[i].cost)
{
flag = 0;
break;
}
return flag;
}
void print_path(int root) //打印最短路的路径(反向)
{
while (root != pre[root]) //前驱
{
printf("%d-->", root);
root = pre[root];
}
if (root == pre[root])
printf("%d\n", root);
}
int main()
{
scanf_s("%d%d%d", &nodenum, &edgenum, &original);
pre[original] = original;
for (int i = 1; i <= edgenum; ++i)
{
scanf_s("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].cost);
}
if (Bellman_Ford())
for (int i = 1; i <= nodenum; ++i) //每个点最短路
{
printf("%d ", dis[i]);
printf("Path:");
print_path(i);
}
else
printf("have negative circle\n");
return 0;
}
SPFA
SPFA算法是bellman-ford算法的队列优化实现的算法,我觉的SPFA的过程是有点像BFS
思维过程:
- 初始,将源点入队
- 每次取出一个点,将与他的相连的顶点松弛,若松弛成功,将该点入队
- 重复操作知道队列为空
queue<int> q;
void spfa(int u)
{
memset(dist, 63, sizeof(dist));
dist[u] = 0;
s[u] = 1; //判断是不是在队列中
deep[u] = 1; //防止出现负环,无休止的减下去
q.push(u);
while(!q.empty)
{
int h = q.front();
if(deep[h] > n)
break;
q.pop();
s[u] = 0;//释放u点,可以重新入队
for(int z = head[h]; ~z; z = edge[z].next)
{
if(dist[edge[z].v] > dist[h]+edge[z].w)
{
dist[edge[z].v] = dist[h]+edge[z].w;
if(!s[edge[z].v])
{
q.push(edge[z].v);
deep[edge[z].v]++;
}
}
}
}
}