动态规划---解决多段图问题

ok 小伙伴们,我现在有点小小的红温,有点毛躁。

怎么解决多段图问题呢?求取最短路径有多种方法可取。

家人们,毫无思绪可言....................................

要实现动态规划条件:子问题重叠度较高,并且满足当下(局部)最优解的要求。

1.Dijkstra算法(贪心算法)

单源最短路径的算法,即从图中某个指定的起点(源节点)到其他所有节点的最短路径。它适用于没有负权重边的图。算法的基本思想是逐步扩展最短路径,每次选择一个当前已知最短路径最小的未处理节点,并 更新其邻居节点的最短路径。

  • 适用场景:该算法适用于边权非负的单源最短路径问题,即从一个源点到图中所有其他点的最短路径。
  • 主要特点:以起始点为中心向外层层扩展,直到扩展到终点为止。能得出最短路径的最优解,但由于遍历计算的节点较多,效率相对较低。
  • 时间复杂度 :朴素Dijkstra算法的时间复杂度为O(n^2+m),其中n表示点数,m表示边数。 优化的Dijkstra算法适用于稀疏图,时间复杂度为O(mlogn)。

理解重要点:

现在有点思路,Dijkstra 在我看来就是加点法。

现在需要两个集合,一个集合s[]存储目前在最短路径中的点,集合U[]存储未加入到最短路径中的点。

不断地找到原点v最短路径,加入相关点到s[i]集合中。

ok,现在我们要实现该算法

先设计图的存储方式,将"多段图中边权重"用动态数组比较好一点。

#include <stdio.h>

#include <stdlib.h>

#include <limits.h>

#define INF INT_MAX

#define Max_Vertex_Num 13

typedef struct Graph

{ int n;

int e;

int edges[Max_Vertex_Num][Max_Vertex_Num];

} Graph;

void CreateGraph(Graph *G)

{ int i, j, k, w;

printf("请输入该图的边数和点数 :");

scanf("%d%d", &G->e, &G->n);

for (i = 0; i < G->n; i++)

for (j = 0; j < G->n; j++)

G->edges[i][j] = INF;

printf("请输入%d 条边和对应权值:(a,b,c):\n", G->e);

for (k = 0; k < G->e; k++)

{ scanf("%d%d%d", &i, &j, &w);

G->edges[i][j] = w; // 有向图

}

}

void Dispath(Graph *G, int dist[], int path[], int s[], int v)

{ int i, j, k, d;

int apath[Max_Vertex_Num];

for (i = 0; i < G->n; i++)

{ if (s[i] == 1 && i != v)

{ printf("顶点%d到顶点%d路径长度为:%d\t 路径:", v, i, dist[i]);

d = 0;

apath[d] = i;

k = path[i];

if (k == -1)

{ printf("不存在路径\n");

}

else

{ while (k != v)

{ d++;

apath[d] = k;

k = path[k];

}

d++;

apath[d] = v;

for (j = d; j >= 0; j--)

printf("%d ", apath[j]);

printf("\n");

}

}

}

}

void Dijsktra(Graph *G, int v)

{ int dist[Max_Vertex_Num], path[Max_Vertex_Num], s[Max_Vertex_Num];

int mindis, i, j, u;

for (i = 0; i < G->n; i++)

{ dist[i] = G->edges[v][i];

s[i] = 0;

if (G->edges[v][i] < INF)

{ path[i] = v;

}

else

{ path[i] = -1;

}

}

s[v] = 1;

path[v] = -1; // 或者0,取决于定义,这里-1表示没有前驱节点

for (i = 0; i < G->n - 1; i++)
{ mindis = INF;
for (j = 0; j < G->n; j++)
{ if (s[j] == 0 && dist[j] < mindis)
{ u = j;
mindis = dist[j];
}
}
s[u] = 1;
for (j = 0; j < G->n; j++)
{ if (s[j] == 0 && G->edges[u][j] < INF && dist[u] + G->edges[u][j] < dist[j])
{ dist[j] = dist[u] + G->edges[u][j];
path[j] = u;
}
}
}

/*

  • G 是一个图的数据结构,其中 G->n 表示图中的节点数,G->edges 是一个二维数组,表示节点之间的边的权重。如果 G->edges[i][j]INF(通常表示一个非常大的数,用来表示节点 ij 之间没有直接的边),则意味着节点 ij 之间没有直接的连接。
  • s 是一个布尔数组,用于跟踪哪些节点已经被处理过(即已经找到了从源节点到该节点的最短路径)。s[i]0 表示节点 i 尚未处理,为 1 表示已处理。
  • dist 是一个数组,用于存储从源节点到每个节点的最短路径的估计距离。初始时,dist[source] 被设置为 0(假设源节点到自己的距离为0),而其他所有节点的 dist 值被设置为 INF(表示初始时,从源节点到其他节点的距离未知,或者说无穷大)。
  • path 是一个数组,用于记录最短路径上的前一个节点,以便最终可以重建最短路径。
  • INF 是一个非常大的数,用来表示无穷大或不存在的边。
  • 外层循环 (for (i = 0; i < G->n - 1; i++)):
    • 这个循环迭代 G->n - 1 次,因为一旦找到了从源节点到 n-1 个其他节点的最短路径,最后一个节点的最短路径也就自动确定了(假设图是连通的)。
  • 寻找当前最近的未处理节点 (mindis = INF; 和相关的内层循环):
    • 初始化 mindisINF,然后遍历所有节点,寻找那些尚未处理(s[j] == 0)且当前估计距离(dist[j])最小的节点 u
    • 一旦找到这样的节点 u,就更新 mindis 为该节点的估计距离,并记住该节点 u
  • 标记节点 u 为已处理 (s[u] = 1;):
    • 将节点 u 标记为已处理,表示已经找到了从源节点到该节点的最短路径。
  • 更新其他节点的估计距离 (第二个内层循环):
    • 遍历所有节点 j,如果节点 j 尚未处理(s[j] == 0),并且存在一条从 uj 的边(G->edges[u][j] < INF),且通过 u 到达 j 的路径比当前已知的通过其他路径到达 j 的路径更短(dist[u] + G->edges[u][j] < dist[j]),则更新 dist[j] 为新的更短的路径长度,并记录路径上的前一个节点为 upath[j] = u)。
  • */

Dispath(G, dist, path, s, v);

}

int main()

{ int v = 1;

Graph *G = (Graph *)malloc(sizeof(Graph));

CreateGraph(G);

int dist[Max_Vertex_Num], s[Max_Vertex_Num], path[Max_Vertex_Num];

for (int i = 0; i < Max_Vertex_Num; i++)

{ dist[i] = 0;

s[i] = 0;

path[i] = -1;

}

Dijsktra(G, v);

free(G); // 释放内存

return 0;

}

图例:

求从节点1到节点12的最短路径长度,以及路径中经过的节点数目。

Dijkstra代码:
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define INF INT_MAX
#define Max_Vertex_Num 13

typedef struct Graph
{	int n;
	int e;
	int edges[Max_Vertex_Num][Max_Vertex_Num];
} Graph;

void CreateGraph(Graph *G)
{	int i, j, k, w;
	printf("请输入该图的边数和点数 :");
	scanf("%d%d", &G->e, &G->n);
	for (i = 0; i < G->n; i++)
		for (j = 0; j < G->n; j++)
			G->edges[i][j] = INF;
	printf("请输入%d 条边和对应权值:(a,b,c):\n", G->e);
	for (k = 0; k < G->e; k++)
	{	scanf("%d%d%d", &i, &j, &w);
		G->edges[i][j] = w; // 有向图
	}
}

void Dispath(Graph *G, int dist[], int path[], int s[], int v)
{	int i, j, k, d;
	int apath[Max_Vertex_Num];
	for (i = 0; i < G->n; i++)
	{	if (s[i] == 1 && i != v)
		{	printf("顶点%d到顶点%d路径长度为:%d\t 路径:", v, i, dist[i]);
			d = 0;
			apath[d] = i;
			k = path[i];
			if (k == -1)
			{	printf("不存在路径\n");
			}
			else
			{	while (k != v)
				{	d++;
					apath[d] = k;
					k = path[k];
				}
				d++;
				apath[d] = v;
				for (j = d; j >= 0; j--)
					printf("%d ", apath[j]);
				printf("\n");
			}
		}
	}
}

void Dijsktra(Graph *G, int v)
{	int dist[Max_Vertex_Num], path[Max_Vertex_Num], s[Max_Vertex_Num];
	int mindis, i, j, u;

	for (i = 0; i < G->n; i++)
	{	dist[i] = G->edges[v][i];
		s[i] = 0;
		if (G->edges[v][i] < INF)
		{	path[i] = v;
		}
		else
		{	path[i] = -1;
		}
	}
	s[v] = 1;
	path[v] = -1;

	for (i = 0; i < G->n - 1; i++)
	{	mindis = INF;
		for (j = 0; j < G->n; j++)
		{	if (s[j] == 0 && dist[j] < mindis)
			{	u = j;
				mindis = dist[j];
			}
		}
		s[u] = 1;
		for (j = 0; j < G->n; j++)
		{	if (s[j] == 0 && G->edges[u][j] < INF && dist[u] + G->edges[u][j] < dist[j])
			{	dist[j] = dist[u] + G->edges[u][j];
				path[j] = u;
			}
		}
	}
	Dispath(G, dist, path, s, v);
}

int main()
{	int v = 1;
	Graph *G = (Graph *)malloc(sizeof(Graph));
	CreateGraph(G);
	int dist[Max_Vertex_Num], s[Max_Vertex_Num], path[Max_Vertex_Num];
	for (int i = 0; i < Max_Vertex_Num; i++)
	{	dist[i] = 0;
		s[i] = 0;
		path[i] = -1;
	}
	Dijsktra(G, v);
	free(G); // 释放内存
	return 0;
}
运行结果显示:

最后得出了最短路径经过的点为1 3 6 10 12 ,最短路径距离 为16

注意!!!!上述输入了13个节点,是因为在图中有12个节点但是,节点是从0开始计算,因此直接写为13个节点,不会影响输出结果。

2.Bellman-Ford算法

  • 适用场景 :适用于处理可能存在负权边的单源最短路径问题
  • 主要特点通过不断松弛****边来更新最短路径,可以判断图中是否存在负权回路(若存在,则不存在最短路)
  1. 处理负权边 :可以处理图中包含负权边的情况。
  2. 检测负权环 :能够检测图中是否存在负权环
  3. 时间复杂度:对于有V个顶点和E条边的图,算法的时间复杂度为O(VE)。

算法步骤:

  1. 初始化:将源点到自身的距离设为0,到所有其他顶点的距离设为无穷大(∞)。
  2. 松弛操作:对图中的所有边进行V-1次松弛操作。松弛操作是指如果通过某条边可以找到从源点到某个顶点的更短路径,则更新该路径的长度。
  3. 检测负权环:进行第V次遍历所有边,如果还能进行松弛操作,则说明图中存在负权环。
伪代码

function BellmanFord(Graph, source)

// Step 1: Initialize distances from source to all vertices as INFINITE

for each vertex v in Graph do

distance[v] ← INFINITE

distance[source] ← 0

// Step 2: Relax all edges |V| - 1 times

for i from 1 to |V| - 1 do

for each edge (u, v) with weight w in edges do

if distance[u] + w < distance[v] then

distance[v] ← distance[u] + w

// Step 3: Check for negative-weight cycles

for each edge (u, v) with weight w in edges do

if distance[u] + w < distance[v] then

return "Graph contains a negative-weight cycle"

return distance[]

Bellman-Ford代码:
cpp 复制代码
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <stdbool.h>

#define numV 5  // 假设图中有5个顶点

// 用于表示图的结构
typedef struct Graph
{	int V;    // 顶点数
	int E;    // 边数
	int **adj; // 邻接矩阵
} Graph;

// 创建图
Graph *createGraph(int V, int E)
{	Graph *graph = (Graph *)malloc(sizeof(Graph));
	graph->V = V;
	graph->E = E;
	graph->adj = (int **)malloc(V * sizeof(int *));
	for (int i = 0; i < V; ++i)
		graph->adj[i] = (int *)malloc(V * sizeof(int));
	return graph;
}

// 添加边
void addEdge(Graph *graph, int src, int dest, int weight)
{	graph->adj[src][dest] = weight;
}

// Bellman-Ford算法实现
void BellmanFord(Graph *graph, int src)
{	int V = graph->V;
	int dist[V];

	// 初始化距离数组
	for (int i = 0; i < V; ++i)
		dist[i] = INT_MAX;
	dist[src] = 0;

	// 松弛所有边 |V| - 1 次
	for (int i = 0; i < V - 1; ++i)
	{	for (int u = 0; u < V; ++u)
		{	for (int v = 0; v < V; ++v)
			{	if (graph->adj[u][v] != 0 && dist[u] != INT_MAX && dist[u] + graph->adj[u][v] < dist[v])
				{	dist[v] = dist[u] + graph->adj[u][v];
				}
			}
		}
	}

	// 检测负权环
	for (int u = 0; u < numV; ++u)
	{	for (int v = 0; v < V; ++v)
		{	if (graph->adj[u][v] != 0 && dist[u] != INT_MAX && dist[u] + graph->adj[u][v] < dist[v])
			{	printf("Graph contains negative weight cycle\n");
				return;
			}
		}
	}

	// 打印最短路径
	for (int i = 0; i < numV; ++i)
		printf("Distance from %d to %d is %d\n", src, i, dist[i]);
}

int main()
{	/* 创建如下图
	    0
	   / \
	  |   \
	  1---2
	  |    \
	  3-----4
	*/
	int V = 5;  // 顶点数
	int E = 6;  // 边数
	Graph *graph = createGraph(V, E);

	addEdge(graph, 0, 1, -1);
	addEdge(graph, 0, 2, 4);
	addEdge(graph, 1, 2, 3);
	addEdge(graph, 1, 3, 2);
	addEdge(graph, 1, 4, 2);
	addEdge(graph, 3, 2, 5);
	addEdge(graph, 3, 1, 1);
	addEdge(graph, 4, 3, -3);

	BellmanFord(graph, 0);

	return 0;
}
运行结果:

3.SPFA算法(Shortest Path Faster Algorithm):

  • 适用场景 :同样适用于处理存在负权边单源最短路径问题
  • 主要特点 :是Bellman-Ford算法的队列优化版,通过维护一个队列来减少重复的操作次数,从而提高效率。
  • 时间复杂度:不稳定,最好情况下为O(E),最坏情况下退化为Bellman-Ford算法的时间复杂度O(VE)。其中E为边数,V为点数。

SPFA算法的基本思想:

  1. 初始化:将源点到自身的距离设为0,到所有其他顶点的距离设为无穷大。
  2. 使用队列:将源点加入队列。
  3. 循环处理 :当队列非空时,循环执行以下操作:
    • 出队一个顶点u。
    • 遍历u的所有邻接点v,对每条边(u, v)进行松弛操作,如果通过u到达v的距离更短,则更新v的距离,并将v加入队列。
  4. 检测负权环:如果在执行松弛操作时,某个顶点已经被在队列中,并且再次被加入队列,则说明存在负权环。
SPFA算法的步骤:
  1. 初始化距离数组dist[],将源点到自身的距离设为0,其他顶点的距离设为无穷大。
  2. 将源点加入队列。
  3. 当队列非空时,执行以下操作:
    • 出队队列头部的顶点u。
    • 对于u的每一个邻接点v,如果通过u到达v的路径更短(即dist[u] + weight(u, v) < dist[v]),则更新dist[v],并将v加入队列。
  4. 如果在更新过程中,某个顶点被重复加入队列,则说明存在负权环。
相关代码:
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define V 5  // 顶点数
#define E 7  // 边数

int dist[V];  // 存储最短路径
int inQueue[V];  // 标记是否在队列中
int graph[V][V];  // 邻接矩阵表示图

// 初始化图
void initGraph() {
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            if (i == j) graph[i][j] = 0;
            else graph[i][j] = INT_MAX;
        }
    }
}

// 添加边
void addEdge(int src, int dest, int weight) {
    graph[src][dest] = weight;
}

// SPFA算法
bool SPFA(int src) {
    int queue[V], front = 0, rear = 0;
    for (int i = 0; i < V; i++) dist[i] = INT_MAX, inQueue[i] = 0;
    dist[src] = 0;
    queue[rear++] = src;
    inQueue[src] = 1;

    while (front != rear) {
        int u = queue[front++];
        inQueue[u] = 0;

        for (int v = 0; v < V; v++) {
            if (graph[u][v] != INT_MAX && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) {
                dist[v] = dist[u] + graph[u][v];
                if (!inQueue[v]) {
                    queue[rear++] = v;
                    inQueue[v] = 1;
                }
            }
        }
    }

    for (int i = 0; i < V; i++) {
        if (dist[i] == INT_MAX) {
            printf("%d is not reachable from %d\n", i, src);
        } else {
            printf("Shortest path from %d to %d is %d\n", src, i, dist[i]);
        }
    }

    // 检测负权环
    for (int u = 0; u < V; u++) {
        for (int v = 0; v < V; v++) {
            if (graph[u][v] != INT_MAX && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) {
                return false;  // 存在负权环
            }
        }
    }
    return true;
}

int main() {
    initGraph();
    addEdge(0, 1, -1);
    addEdge(0, 2, 4);
    addEdge(1, 2, 3);
    addEdge(1, 3, 2);
    addEdge(2, 1, 1);
    addEdge(2, 3, 1);
    addEdge(2, 4, 2);

    if (!SPFA(0)) {
        printf("Graph contains negative weight cycle\n");
    }

    return 0;
}
运行结果:

4.Floyd-Warshall算法:(动态规划)

  • 适用场景 :适用于求解任意两点间的最短路径问题,可以正确处理有向图或负权的最短路径问题,适用于稠密图,即边的数量接近顶点数的平方的图。
  • 主要特点:通过动态规划的思想,逐步求出任意两点之间的最短路径。
  • 时间复杂度:对于有V个顶点的图,算法的时间复杂度为O(V^3)。
  • 空间复杂度:需要O(V^2)的空间来存储最短路径的中间结果。

算法步骤:

  1. 初始化:创建一个V×V的矩阵D,其中D[i][j]表示顶点i到顶点j的直接距离。如果i和j之间没有直接的边,则D[i][j]设为无穷大(∞)。对角线上的D[i][i]设为0。
  2. 迭代更新:对于每个顶点k,检查是否可以通过k来找到从i到j的更短路径。具体来说,对于每一对顶点i和j,如果D[i][j] > D[i][k] + D[k][j],则更新D[i][j] = D[i][k] + D[k][j]。
  3. 检测负权环:在算法结束后,检查对角线上的D[i][i]是否大于0,如果是,则图中存在负权环。

应用场景:

  • 适用于需要计算图中所有顶点对之间最短路径的问题。
  • 适用于不包含负权环的图。

限制:

  • 不适用于包含负权环的图。
  • 时间复杂度较高,对于大规模稀疏图可能效率较低。
伪代码

function FloydWarshall(Graph)

// Step 1: Initialize the distance matrix

for each node v from 1 to number_of_nodes do

for each node w from 1 to number_of_nodes do

if v == w then

dist[v][w] ← 0

else if there is an edge (v, w) then

dist[v][w] ← weight(v, w)

else

dist[v][w] ← ∞

// Step 2: Update the distance matrix

for each node k from 1 to number_of_nodes do

for each node i from 1 to number_of_nodes do

for each node j from 1 to number_of_nodes do

if dist[i][j] > dist[i][k] + dist[k][j] then

dist[i][j] ← dist[i][k] + dist[k][j]

// Step 3: Check for negative-weight cycles

for each node i from 1 to number_of_nodes do

if dist[i][i] < 0 then

return "Graph contains a negative-weight cycle"

return dist

Floyd_Warshall算法相关代码:

Floyd-Warshall算法可以通过以下步骤实现:
  1. 初始化距离矩阵 :创建一个二维数组dist[][],其中dist[i][j]表示顶点i到顶点j的直接距离。如果ij之间有直接的边,则dist[i][j]是边的权重;如果没有直接的边,则dist[i][j]设为无穷大(∞)。对角线上的dist[i][i]设为0。

  2. 迭代更新距离矩阵 :使用三层循环,外层循环遍历每一个顶点k,内层两个循环遍历所有顶点ij。检查是否可以通过顶点k来找到从ij的更短路径。如果dist[i][k] + dist[k][j]小于当前的dist[i][j],则更新dist[i][j]

  3. 检测负权环 :在算法结束后,再次遍历距离矩阵,如果发现任何对角线元素dist[i][i]小于0,则图中存在负权环。

cpp 复制代码
#include <stdio.h>
#include <limits.h>

#define V 4  // 定义顶点的数量

// 打印解决方案矩阵
void printSolution(int dist[][V], int V) {
    for (int i = 0; i < V; i++) {
        for (int j = 0; j < V; j++) {
            if (dist[i][j] == INT_MAX)
                printf("%7s", "∞");
            else
                printf("%7d", dist[i][j]);
        }
        printf("\n");
    }
}

// 实现Floyd-Warshall算法
void floydWarshall(int graph[][V]) {
    int dist[V][V], i, j, k;

    // 初始化距离矩阵
    for (i = 0; i < V; i++)
        for (j = 0; j < V; j++)
            dist[i][j] = graph[i][j];

    // 迭代更新距离矩阵
    for (k = 0; k < V; k++) {
        for (i = 0; i < V; i++) {
            for (j = 0; j < V; j++) {
                if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX && dist[i][k] + dist[k][j] < dist[i][j])
                    dist[i][j] = dist[i][k] + dist[k][j];
            }
        }
    }

    // 打印解决方案矩阵
    printSolution(dist, V);
}

// 主函数
int main() {
    int graph[V][V] = { { 0,   5,  INF, 10 },
                        { INF, 0,   3, INF },
                        { INF, INF, 0,   1 },
                        { INF, INF, INF, 0 } };

    floydWarshall(graph);

    return 0;
}
运行结果:

Vertex 0 Vertex 1 Vertex 2 Vertex 3

Vertex 0 0 5 8 9

Vertex 1 INF 0 3 4

Vertex 2 INF INF 0 1

Vertex 3 INF INF INF 0

解释:

  • 从顶点0到顶点0的最短路径是0(自身到自身)。
  • 从顶点0到顶点1的最短路径是5。
  • 从顶点0到顶点2的最短路径是7(通过顶点1:0->1->2)。
  • 从顶点0到顶点3的最短路径是9(通过顶点1:0->1->3 或者 通过顶点2:0->2->3)。
  • 从顶点1到顶点2的最短路径是3。
  • 从顶点1到顶点3的最短路径是4(通过顶点2:1->2->3)。
  • 其他路径没有更短的路径,所以保持为INF

5.Johnson算法

  • 解决多源最短路径问题的有效算法,尤其适用于稀疏图。
  • 该算法通过重新赋权值,将带权图转化为一个满足三角形不等式的图,然后利用Dijkstra算法求解各顶点的最短路径。

Johnson算法的主要步骤如下:

  1. 新建虚拟节点:创建一个虚拟节点(通常编号为0),并从这个节点向所有其他节点连一条权重为0的边。

  2. 使用Bellman-Ford算法:使用Bellman-Ford算法(或SPFA算法)计算从虚拟节点到所有其他节点的最短路径,记为h_i。

  3. 重新赋予权重:对于图中每一条边(u, v),重新赋予权重w' = w + h(u) - h(v),其中w是原边的权重。

  4. 使用Dijkstra算法:以每个节点为起点,运行Dijkstra算法来计算到所有其他节点的最短路径。由于所有边的权重都已非负,可以使用Dijkstra算法。

正确性证明:

Johnson算法通过重新赋予权重的方式确保了所有边的权重非负,从而可以使用Dijkstra算法求解最短路径。这种方法的正确性在于,通过引入虚拟节点和重新赋予权重,算法实际上为每条路径增加了一个常数,这个常数与路径无关,因此不会影响路径之间的相对长度。

伪代码

function Johnson(graph):

n = number of vertices in graph

m = number of edges in graph

D = array of size n * n initialized to infinity

for each edge (u, v) in graph:

D[u][v] = weight(u, v)

// Step 1: Add a dummy source and calculate shortest paths to all vertices

dummy = new vertex

for each vertex v in graph:

D[dummy][v] = 0

for each vertex v in graph:

BellmanFord(D, dummy, n)

// Step 2: Adjust edge weights

for each edge (u, v) in graph:

D[u][v] += D[u][dummy] - D[v][dummy]

// Step 3: Run Dijkstra's algorithm from each vertex

for each vertex v in graph:

sort edges by weight for Dijkstra's algorithm

Dijkstra(D, v, n)

return D

最终代码示例:

步骤1:添加虚拟节点并初始化距离数组

首先,我们添加一个虚拟节点(通常编号为0),并从这个节点向所有其他节点连一条权重为0的边。然后,我们使用Bellman-Ford算法计算从虚拟节点到所有其他节点的最短路径。

步骤2:重新赋予权重

使用每个顶点的势能值重新调整图中所有边的权重,确保所有边的权重都是非负的。

步骤3:使用Dijkstra算法计算所有顶点对的最短路径

在重新加权后的图上,对每个顶点应用Dijkstra算法来找到到所有其他顶点的最短路径。

Vertex 0 Vertex 1 Vertex 2 Vertex 3

Vertex 0 0 2 -3 -2

Vertex 1 2 0 INF 4

Vertex 2 1 INF 0 3

Vertex 3 INF INF INF 0

代码示例:
cpp 复制代码
#include <stdio.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>

#define numV 4
#define INF INT_MAX

int dist[numV][numV];
int graph[numV][numV];

// 用于Dijkstra算法的优先队列比较函数
int minDistance(int dist[][numV], bool sptSet[], int V)
{	int min = INF, min_index;
	for (int v = 0; v < V; v++)
		if (sptSet[v] == false && dist[v][0] < min)
			min = dist[v][0], min_index = v;
	return min_index;
}

// 打印解决方案矩阵
void printSolution(int dist[][numV], int V)
{	for (int i = 0; i < V; i++)
	{	for (int j = 0; j < V; j++)
		{	if (dist[i][j] == INF)
				printf("%7s", "∞");
			else
				printf("%7d", dist[i][j]);
		}
		printf("\n");
	}
}

// Dijkstra算法
void dijkstra(int graph[][numV], int V, int src, int dist[][numV])
{	bool sptSet[numV] = {false};
	for (int i = 0; i < V; i++)
		dist[src][i] = graph[src][i];
	dist[src][src] = 0;
	for (int count = 0; count < V - 1; count++)
	{	int u = minDistance(dist, sptSet, V);
		sptSet[u] = true;
		for (int v = 0; v < V; v++)
			if (!sptSet[v] && graph[u][v] != INF && dist[src][u] != INF && dist[src][u] + graph[u][v] < dist[src][v])
				dist[src][v] = dist[src][u] + graph[u][v];
	}
}

// Bellman-Ford算法
bool bellmanFord(int graph[][numV], int V, int src, int dist[][numV])
{	for (int i = 0; i < V; i++)
		for (int j = 0; j < V; j++)
			dist[i][j] = graph[i][j];
	for (int i = 0; i < V; i++)
		dist[i][i] = 0;

	for (int k = 0; k < V - 1; k++)
		for (int i = 0; i < V; i++)
			for (int j = 0; j < V; j++)
				if (dist[i][j] > dist[i][k] + dist[k][j])
					dist[i][j] = dist[i][k] + dist[k][j];

	for (int i = 0; i < V; i++)
		for (int j = 0; j < V; j++)
			if (dist[i][j] != graph[i][j] && graph[i][j] != 0)
				return false;

	return true;
}

// Johnson算法
void johnson()
{	// Step 1: 使用Bellman-Ford算法计算势能值
	for (int i = 0; i < numV; i++)
		for (int j = 0; j < numV; j++)
			if (graph[i][j] == 0)
				graph[i][j] = INF;
	for (int i = 0; i < numV; i++)
		if (!bellmanFord(graph, numV, i, dist))
		{	printf("Graph contains a negative weight cycle\n");
			return;
		}

	// Step 2: 重新赋予权重
	for (int i = 0; i < numV; i++)
		for (int j = 0; j < numV; j++)
			if (graph[i][j] != INF)
				graph[i][j] = graph[i][j] + dist[i][0] - dist[j][0];

	// Step 3: 使用Dijkstra算法计算所有顶点对的最短路径
	for (int i = 0; i < numV; i++)
		dijkstra(graph, numV, i, dist);

	// 打印解决方案矩阵
	printSolution(dist, numV);
}

int main()
{	// 初始化图
	int graph[numV][numV] =
	{	{0, -6, INF, -4},
		{-2, 0, -4, -2},
		{INF, INF, 0, 3},
		{INF, INF, INF, 0}
	};

	johnson();

	return 0;
}

运行结果:

相关推荐
cwj&xyp13 分钟前
Python(二)str、list、tuple、dict、set
前端·python·算法
xiaoshiguang34 小时前
LeetCode:222.完全二叉树节点的数量
算法·leetcode
爱吃西瓜的小菜鸡4 小时前
【C语言】判断回文
c语言·学习·算法
别NULL5 小时前
机试题——疯长的草
数据结构·c++·算法
TT哇5 小时前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
yuanbenshidiaos6 小时前
C++----------函数的调用机制
java·c++·算法
唐叔在学习6 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA6 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
chengooooooo6 小时前
代码随想录训练营第二十七天| 贪心理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和
算法·leetcode·职场和发展
jackiendsc7 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法