/*
Bellman-Ford 思想:对每一条边进行松弛操作(dis[v[i]]>dis[u[i]]+w[i])。共进行(n-1)轮,每进行一轮松弛操作,相当于扩充了一些新的边
当进行完k轮时,就已经找到从源点发出“最多经过k条边”到达各个顶点的最短路。n个顶点,最短路径上最多有n-1条边
如果进行n-1轮,还可以对边进行松弛成功,则此图一定存在负权回路~
负权回路:即回路权值之和为负
以此题而言:第一轮松弛操作后,dis[3]=inf(无穷大),但第二轮后dis[3] =-1; 是因为在第一轮后dis[2]=2;
dis[3] = dis[2]+2->3; 即dis[3] = dis[1]+1->2 + 2—>3;所以每一轮都相当于扩充了一些新边。
当进行完k轮时,就已经找到从源点发出“最多经过k条边”到达各个顶点的最短路
*/
#pragma mark - - Bellman-Ford 算法
-(void)test1 {
// 存储边数据 例如:{2,3,2} 表示顶点2到顶点3的权值为2
// e[0][3] 没有任何意义,只是为了让边的遍历从1开始
int e[6][3] = {{0,0,0},{2,3,2},{1,2,-3},{1,5,5},{4,5,2},{3,4,3}};
// u[6] v[5] w[5] 分别存储两个顶点和权值
int u[6];
int v[6];
int w[6];
for (int i=1; i<=5; i++) {
u[i] = e[i][0];
v[i] = e[i][1];
w[i] = e[i][2];
}
int n=5; // 顶点数
int m=5; // 边数
int inf=9999; // 表示∞
// dis[6] 存储源点到其他顶点的权值
int dis[6];
for (int i=0; i<=n; i++) {
dis[i] =inf;
}
// 设置源点为顶点1
dis[1] =0;
// 共进行n-1轮
for (int k=1; k<=n-1; k++) {
// 对边进行松弛操作
for (int i=1; i<=m; i++) {
if (dis[v[i]] > dis[u[i]] + w[i]) {
dis[v[i]] = dis[u[i]] +w[i];
}
}
}
// 判断是否负权回路
int flag=0;
for (int i=1; i<=m; i++) {
if (dis[v[i]] > dis[u[i]] + w[i]) {
flag=1;
}
}
if (flag==1) {
printf("该图存在回路\n");
}else {
// 打印数据
for (int i=1; i<=n; i++) {
printf("%4d",dis[i]);
}
printf("\n");
}
}
// 对上述算法进行优化
/*
在实际操作中,有时只进行了K轮(k
*/
-(void)test2 {
// 存储边数据 例如:{2,3,2} 表示顶点2到顶点3的权值为2
// e[0][3] 没有任何意义,只是为了让边的遍历从1开始
int e[6][3] = {{0,0,0},{2,3,2},{1,2,-3},{1,5,5},{4,5,2},{3,4,3}};
int n=5; // 顶点数
int m=5; // 边数
// u[] v[] w[] 分别存储顶点和权值
int u[6];
int v[6];
int w[6];
for (int i=1; i<=m; i++) {
u[i] =e[i][0];
v[i] =e[i][1];
w[i] =e[i][2];
}
int dis[6]; // 存储源点到各个顶点的最短距离
int inf=9999; // 表示无穷大
for (int i=0; i<=n; i++) {
dis[i] = inf;
}
// 设置源点为顶点1
dis[1] =0;
// 进行n-1轮
for (int k=1; k<=n-1; k++) {
// 检查是否松弛成功
int check =0;
// 对每一条边进行松弛操作
for (int i=1; i<=m; i++) {
if (dis[v[i]] > dis[u[i]] + w[i]) {
dis[v[i]] = dis[u[i]] + w[i];
check =1;
}
}
if (check==0) {
// check=0 表示当前这一轮 dis[] 没有变化,那么再进行一轮也不会有变化
// 提前退出循环
break ;
}
}
// 判断是否存在负权回路
int flag=0;
for (int i=1; i<=m; i++) {
if (dis[v[i]] > dis[u[i]] + w[i]) {
flag =1;
}
}
if (flag==1) {
printf("该图存在回路\n");
}else {
// 打印数据
for (int i=1; i<=n; i++) {
printf("%4d",dis[i]);
}
printf("\n");
}
}
/*
当对边进行松弛操作后,某些顶点的最短距离dis[k]已经固定下来,在后面的对边的松弛操作中,这个dis[k]
不会发生变化。如果dis[u[i]] 固定下来了,那么对顶点u[i]的所有边做松弛操作都不会有效果(dis[v[i]] = dis[u[i]] + w[i])。所以仅对最短路程发生变化的顶点的相邻边进行松弛操作~
/
/
Bellman—Ford的队列优化:每次仅对最短路程发生变化的点的相邻边进行松弛操作。即对一个顶点的所有边进行
松弛操作,如果dis[v[i]] > dis[u[i]] + w[i],则将dis[v[i]] 加入队列中,循环队列(head++)~
*/
#pragma mark - - Bellman-Ford的队列优化
-(void)test3 {
// 存储边数据 例如:{2,3,2} 表示顶点2到顶点3的权值为2
// e[0][3] 没有任何意义,只是为了让边的遍历从1开始
int e[6][3] = {{0,0,0},{2,3,2},{1,2,-3},{1,5,5},{4,5,2},{3,4,3}};
int n=5;
int m=5;
// 邻接表
// first[i] 存储顶点i的第一条边的编号
int first[6];
// next[i] 存储边i的下一条边的编号
int next[6];
// 初始化first[]
for (int i=0; i<=m; i++) {
first[i] = -1;
}
// u[] v[] w[] 分别存储顶点和权值
int u[6];
int v[6];
int w[6];
// 邻接表 赋值
for (int i=1; i<=m; i++) {
u[i] = e[i][0];
v[i] = e[i][1];
w[i] = e[i][2];
// 邻接表 核心代码
next[i] =first[u[i]];
first[u[i]] = i;
}
// 初始化 dis[]
int dis[6];
int inf = 9999;
for (int i=0; i<=5; i++) {
dis[i] = inf;
}
// 初始化 队列
int que[10];
int head=1;
int tail=1;
// 初始化标记数组(标记队列里的点)
int book[10]= {0};
// 源点1赋值
// 赋值dis[]
dis[1]=0;
// 入队
que[tail] = 1;
tail ++;
// 标记
book[1] =1;
// 核心代码
// 循环队列
// 如果存在负权回路,队列会一直执行
int sum=0; // 记录松弛操作次数
int flag=0; // 标记是否存在负权回路
while (head dis[u[k]] + w[k]) {
// 松弛成功
dis[v[k]] = dis[u[k]] + w[k];
// book[] 数组用来判断当前点是否在队列里
if (book[v[k]] == 0) {
// 标记
book[v[k]] = 1;
// 入队
que[tail] = v[k];
tail++;
}
}
// 下一条边
k = next[k];
}
// 出队
/*
当一个顶点的最短路程变短后,需要对这个顶点的所有边进行松弛操作,但是如果这个顶点的最短路程
再次变小,需要再次对这个顶点的所有边进行松弛操作~
*/
book[que[head]] = 0; //把出队的点重新标记为0
head ++ ; // head++ 才能继续执行队列里的其他点
// 当图不存在负权回路时,最多会进行n*m次松弛操作
// 如果sum(松弛操作次数)>n*m+1 ,那一定存在负权回路
// 为什么是n*m,Bellman-Ford 算法最多执行(n-1)*m次就可以使源点到各个顶点的距离达到最小值
if (sum>n*m+1) {
flag =1;
break ;
}
}
// 打印
if (flag==1) {
printf("该图存在回路\n");
}else {
// 打印数据
for (int i=1; i<=n; i++) {
printf("%4d",dis[i]);
}
printf("\n");
}
}