【数据结构】最长的最短路径的求解
任务描述
设计一个算法,求图G中距离顶点v的最短路径长度最大的一个顶点。
编程要求
输入
多组数据,每组数据m+2行。每组数据第一行为两个整数n和m,代表有n个顶点m条路。顶点编号为1到n。第二行到第m+1行每行有三个整数a,b和c,代表顶点a和顶点b之间有一条长度为c的路。第m+2有一个整数v,代表顶点v。当n和m都等于0时,输入结束。
输出
每组数据输出两行。第一行为最短路径最长的顶点编号c,第二行为两点的最短距离d。
测试说明
平台会对你编写的代码进行测试:
输入样例:
在这里给出一组输入。例如:
4 4
1 2 1
2 3 1
3 4 1
2 4 1
4
4 3
1 2 3
2 3 2
2 4 6
3
0 0
输出样例:
在这里给出相应的输出。例如:
1
2
4
8
整体实现流程
1.整体结构与核心功能
代码主要包含 3 个部分:
- 数据结构定义:用邻接矩阵存储无向图;
- 图的构建:通过输入初始化邻接矩阵;
- 最短路径求解:用 Dijkstra 算法计算源点到所有顶点的最短路径,最终输出最远顶点及其距离。
2.数据结构与宏定义
1. 宏定义
#define OK 1
#define ERROR 0
#define OVERFLOW -2
#define MVNum 100 // 最大顶点数(图的顶点上限)
#define MaxInt 10000 // 表示“无穷大”(用于初始化未直接相连的顶点距离)
2. 图的结构体定义
typedef char VerTexType; // 顶点类型(此处未实际使用,代码中顶点用索引表示)
typedef int ArcType; // 边的权重类型(整型)
typedef struct {ArcType arcs[MVNum][MVNum]; // 邻接矩阵:arcs[i][j]表示顶点i到j的权重int vexnum, arcnum; // 图的顶点数和边数
} MGraph;
3.图的构建(CreateGraph
函数)
功能:根据输入的顶点数、边数及边的信息,初始化邻接矩阵,构建无向图。
实现步骤:
初始化邻接矩阵:
- 对角线元素(顶点到自身的距离)设为 0;
- 非对角线元素(顶点间无直接边)初始化为
MaxInt
(无穷大)。
读取边信息并更新矩阵:
- 输入每条边的起点
s
、终点e
、权重w
(注意:输入的顶点是 1-based 索引,代码中转换为 0-based); - 由于是无向图,边是双向的,因此同时更新
arcs[s-1][e-1]
和arcs[e-1][s-1]
为w
。
- 输入每条边的起点
4.Dijkstra 算法实现(ShortestPath_DIJ
函数)
Dijkstra 算法用于求解带非负权重的图中,从源点到其他所有顶点的最短路径。核心思想是:通过逐步将 “距离源点最近的未访问顶点” 加入 “已确定最短路径的集合”,并更新其他顶点的距离。
1. 核心变量说明
final[v]
:布尔数组,标记顶点v
是否已加入 “已确定最短路径的集合”(true
表示已加入);D[v]
:数组,存储源点v0
到顶点v
的当前最短距离;p[v][w]
:二维数组,记录源点到顶点v
的最短路径经过的顶点(p[v][i]
表示路径上的第i+1
个顶点,-1
表示路径结束)。
2. 算法步骤(详细流程)
(1)初始化
for (v = 0; v < G.vexnum; v++) {final[v] = false; // 所有顶点初始均未加入“已确定集合”D[v] = G.arcs[v0][v]; // 初始化源点到各顶点的距离(直接边的权重)for (w = 0; w < G.vexnum; w++) p[v][w] = -1; // 路径数组初始化为-1(无路径)if (D[v] < MaxInt) { // 若源点到v有直接边p[v][0] = v0; // 路径第一个顶点是源点v0p[v][1] = v; // 路径第二个顶点是v(直接到达)}
}
D[v0] = 0; // 源点到自身的距离为0
final[v0] = true; // 源点加入“已确定集合”
(2)主循环:逐步确定最短路径
循环G.vexnum - 1
次(需确定除源点外所有顶点的最短路径):
for (i = 1; i < G.vexnum; i++) {// 步骤1:找到“未加入集合且距离源点最近”的顶点vmin = MaxInt;for (w = 0; w < G.vexnum; w++) {if (!final[w] && D[w] < min) { // 未加入集合且距离更小v = w;min = D[w];}}final[v] = true; // 将v加入“已确定集合”// 步骤2:更新其他未加入集合的顶点到源点的距离for (w = 0; w < G.vexnum; w++) {// 若w未加入集合,且“源点→v→w”的距离 < 当前“源点→w”的距离if (!final[w] && min < MaxInt && G.arcs[v][w] < MaxInt && (min + G.arcs[v][w] < D[w])) {D[w] = min + G.arcs[v][w]; // 更新最短距离// 更新路径:源点到w的路径 = 源点到v的路径 + wfor (j = 0; j < G.vexnum; j++) {p[w][j] = p[v][j]; // 复制源点到v的路径if (p[w][j] == -1) { // 在路径末尾添加wp[w][j] = w;break;}}}}
}
(3)输出 “距离源点最远的顶点” 及其距离
int k, max = D[0];
for (i = 0; i < G.vexnum; i++) {if (max < D[i]) { // 找到D数组中的最大值max = D[i];k = i; // 记录对应顶点(0-based)}
}
cout << k + 1 << endl; // 转换为1-based输出顶点编号
cout << max << endl; // 输出最大距离
5.主函数(main
函数)
功能:循环读取输入,构建图并调用 Dijkstra 算法,直到输入n=0
且m=0
时退出。
流程:
- 读取顶点数
n
和边数m
,若均为 0 则退出; - 调用
CreateGraph
构建图; - 读取源点
v0
(1-based),转换为 0-based 后调用ShortestPath_DIJ
求解。
6.关键特性与注意事项
- 无向图:代码中边是双向的(
arcs[s-1][e-1]
和arcs[e-1][s-1]
均赋值); - 非负权重:Dijkstra 算法要求边的权重非负,代码中未做检查,但默认输入满足;
- 可达性假设:题目中说明 “该顶点可到达其余各个顶点”,因此无需处理不可达的情况(
D[v]
不会为MaxInt
); - 路径记录:
p
数组详细记录了每条最短路径的顶点序列,可用于后续打印路径。
7.C++代码完整实现
#include<iostream>
#define OK 1
#define ERROR 0
#define OVERFLOW -2
#define MVNum 100 //最大顶点数
#define MaxInt 10000
using namespace std;
typedef char VerTexType; //设置顶点类型为字符型
typedef int ArcType; //设置权重为整型
typedef struct
{ArcType arcs[MVNum][MVNum]; //邻接矩阵 int vexnum, arcnum; //顶点数和边数
}MGraph;
void CreateGraph(MGraph* G,int n,int m);
void ShortestPath_DIJ(MGraph G, int v0, int p[][MVNum], int D[]);//3.途中距离顶点的最短路径长度最大的一个顶点,设该顶点可到达其余各个顶点
int main()
{while(1){MGraph G;int n,m;cin>>n>>m;if(n==0&&m==0){break;}CreateGraph(&G,n,m);int v0,p[MVNum][MVNum],D[MVNum];cin>>v0;ShortestPath_DIJ(G,v0-1,p,D);}
}
void CreateGraph(MGraph* G,int n,int m)
{int i, j;//图的顶点和边数 int s, e, w;//边的起始点,终点,权值 G->vexnum=n;G->arcnum=m;//邻接矩阵初始化for (i = 0; i < G->vexnum; i++){for (j = 0; j < G->vexnum; j++){if(i!=j){G->arcs[i][j] = MaxInt;}else{G->arcs[i][j]=0;}}}for (j = 0; j < G->arcnum; j++){cin >> s >> e >> w;//输入边的起始点,终点,权值G->arcs[s - 1][e - 1] = w;G->arcs[e - 1][s - 1] = w;//矩阵沿对角线对称 }
}
//迪杰斯特拉算法求有向网G的v0顶点到其余顶点v的最短路径p[v]及带权长度D[v]
//p[][]=-1表示没有路径,p[v][i]存的是从v0到v当前求得的最短路径经过的第i+1个顶点(这是打印最短路径的关键),则v0到v的最短路径即为p[v][0]到p[v][j]直到p[v][j]=-1,路径打印完毕。
//final[v]为true当且仅当v∈S,即已经求得从v0到v的最短路径。
void ShortestPath_DIJ(MGraph G, int v0, int p[][MVNum], int D[])
{int v, w, i, j, min;bool final[10];for (v = 0; v < G.vexnum; v++) {final[v] = false;//设初值D[v] = G.arcs[v0][v];//D[]存放v0到v得最短距离,初值为v0到v的直接距离for (w = 0; w < G.vexnum; w++){p[v][w] = -1;//设p[][]初值为-1,即没有路径}if (D[v] < MaxInt){ //v0到v有直接路径p[v][0] = v0;//v0到v最短路径经过的第一个顶点p[v][1] = v;//v0到v最短路径经过的第二个顶点}}D[v0] = 0;//v0到v0距离为0final[v0] = true;//v0顶点并入S集for (i = 1; i < G.vexnum; i++) { //其余G.vexnum-1个顶点//开始主循环,每次求得v0到某个顶点v的最短路径,并将v并入S集,然后更新p和Dmin = MaxInt;for (w = 0; w < G.vexnum; w++)//对所有顶点检查{if (!final[w] && D[w] < min){ //在S集之外(即final[]=false)的顶点中找离v0最近的顶点,将其赋给v,距离赋给minv = w;min = D[w];}}final[v] = true;//v并入S集for (w = 0; w < G.vexnum; w++) { //根据新并入的顶点,更新不在S集的顶点到v0的距离和路径数组if (!final[w] && min < MaxInt && G.arcs[v][w] < MaxInt && (min + G.arcs[v][w] < D[w])){//w不属于S集且v0->v->w的距离<目前v0->w的距离D[w] = min + G.arcs[v][w];//更新D[w]for (j = 0; j < G.vexnum; j++) { //修改p[w],v0到w经过的顶点包括v0到v经过的所有顶点再加上顶点wp[w][j] = p[v][j];if (p[w][j] == -1) { //在p[w][]第一个等于-1的地方加上顶点wp[w][j] = w;break;}}}}}int k,max=D[0];for(i=0;i<G.vexnum;i++){if(max<D[i]){max=D[i];k=i;}}cout<<k+1<<endl;cout<<max<<endl;
}
8.Python代码完整实现
import sys# 定义常量,与原C++保持一致
MaxInt = 10000
MVNum = 100 # 最大顶点数,与原C++一致def create_graph(n: int, m: int) -> dict:"""构建无向图的邻接矩阵(对应原C++ CreateGraph函数):param n: 顶点数(1开始编号):param m: 边数:return: 图结构字典,包含顶点数(vexnum)和邻接矩阵(arcs)"""# 初始化邻接矩阵:对角线为0,其余为MaxIntarcs = [[MaxInt for _ in range(n)] for _ in range(n)]for i in range(n):arcs[i][i] = 0 # 自身到自身距离为0# 读取m条边,无向图双向赋值权重for _ in range(m):s, e, w = map(int, sys.stdin.readline().split())# 转换为0开始的索引s_idx = s - 1e_idx = e - 1arcs[s_idx][e_idx] = warcs[e_idx][s_idx] = wreturn {"vexnum": n,"arcs": arcs}def shortest_path_dij(G: dict, v0: int) -> tuple:"""迪杰斯特拉算法(对应原C++ ShortestPath_DIJ函数):param G: 图结构字典:param v0: 起点(0开始编号):return: (最远顶点编号(1开始), 最远距离)"""n = G["vexnum"]# 初始化辅助数组final = [False] * n # 标记是否已确定最短路径D = [0] * n # 存储v0到各顶点的最短距离# 路径数组p:p[v][i]表示v0到v的路径第i个顶点(-1表示无后续)p = [[-1 for _ in range(n)] for _ in range(n)]# 初始化D和p数组for v in range(n):D[v] = G["arcs"][v0][v]# 若v0到v有直接路径,初始化路径if D[v] < MaxInt:p[v][0] = v0p[v][1] = v# 起点自身处理D[v0] = 0final[v0] = True# 迭代求其余n-1个顶点的最短路径for i in range(1, n):# 步骤1:找当前未确定最短路径中距离v0最近的顶点vmin_dist = MaxIntv = -1for w in range(n):if not final[w] and D[w] < min_dist:min_dist = D[w]v = wif v == -1:break # 剩余顶点不可达,提前退出final[v] = True # 将v加入已确定集合# 步骤2:更新所有未确定顶点的最短距离和路径for w in range(n):if not final[w] and min_dist < MaxInt and G["arcs"][v][w] < MaxInt:if min_dist + G["arcs"][v][w] < D[w]:D[w] = min_dist + G["arcs"][v][w]# 复制v的路径到w,并在末尾添加wj = 0while p[v][j] != -1:p[w][j] = p[v][j]j += 1p[w][j] = w# 找距离v0最远的顶点(可到达的,D[w] < MaxInt)max_dist = -1far_vex = v0 # 默认起点自身for v in range(n):if D[v] < MaxInt and D[v] > max_dist:max_dist = D[v]far_vex = v# 转换为1开始的顶点编号return far_vex + 1, max_distdef main():while True:# 读取顶点数n和边数mline = sys.stdin.readline()while line.strip() == "": # 跳过空行line = sys.stdin.readline()n, m = map(int, line.strip().split())if n == 0 and m == 0:break # 输入结束# 构建图G = create_graph(n, m)# 读取起点v0(1开始)v0_line = sys.stdin.readline()while v0_line.strip() == "":v0_line = sys.stdin.readline()v0 = int(v0_line.strip()) - 1 # 转换为0开始# 计算并输出结果far_vex, max_dist = shortest_path_dij(G, v0)print(far_vex)print(max_dist)if __name__ == "__main__":main()
9.Java代码完整实现
import java.util.Scanner;// 常量定义(与原C++一致)
class Constant {public static final int OK = 1;public static final int ERROR = 0;public static final int OVERFLOW = -2;public static final int MVNum = 100; // 最大顶点数public static final int MaxInt = 10000; // 表示无穷大
}// 图结构类(对应原C++的MGraph结构体)
class MGraph {int[][] arcs; // 邻接矩阵int vexnum; // 顶点数int arcnum; // 边数// 构造方法(对应原C++的CreateGraph函数)public MGraph(int n, int m, Scanner scanner) {this.vexnum = n;this.arcnum = m;// 初始化邻接矩阵:自身距离为0,其余为无穷大arcs = new int[Constant.MVNum][Constant.MVNum];for (int i = 0; i < this.vexnum; i++) {for (int j = 0; j < this.vexnum; j++) {arcs[i][j] = (i == j) ? 0 : Constant.MaxInt;}}// 读取边信息(无向图:双向赋值权重)for (int j = 0; j < this.arcnum; j++) {int s = scanner.nextInt() - 1; // 转换为0开始索引int e = scanner.nextInt() - 1;int w = scanner.nextInt();arcs[s][e] = w;arcs[e][s] = w;}}
}public class DijkstraAlgorithm {// 迪杰斯特拉算法(对应原C++的ShortestPath_DIJ函数)public static void shortestPathDIJ(MGraph G, int v0) {int n = G.vexnum;boolean[] finalArr = new boolean[n]; // 标记是否已确定最短路径(替换原C++的final数组)int[] D = new int[n]; // 存储v0到各顶点的最短距离int[][] p = new int[Constant.MVNum][Constant.MVNum]; // 存储最短路径// 1. 初始化D、p数组for (int v = 0; v < n; v++) {finalArr[v] = false;D[v] = G.arcs[v0][v];// 初始化路径数组为-1(表示无路径)for (int w = 0; w < Constant.MVNum; w++) {p[v][w] = -1;}// 若v0到v有直接路径,初始化路径if (D[v] < Constant.MaxInt) {p[v][0] = v0;p[v][1] = v;}}// 起点自身处理D[v0] = 0;finalArr[v0] = true;// 2. 迭代求其余n-1个顶点的最短路径for (int i = 1; i < n; i++) {// 找当前未确定路径中距离v0最近的顶点int min = Constant.MaxInt;int v = -1;for (int w = 0; w < n; w++) {if (!finalArr[w] && D[w] < min) {min = D[w];v = w;}}if (v == -1) break; // 剩余顶点不可达,提前退出finalArr[v] = true; // 将v加入已确定集合// 3. 更新所有未确定顶点的距离和路径for (int w = 0; w < n; w++) {if (!finalArr[w] && min < Constant.MaxInt && G.arcs[v][w] < Constant.MaxInt && (min + G.arcs[v][w] < D[w])) {// 更新最短距离D[w] = min + G.arcs[v][w];// 复制v的路径到w,并追加w作为终点int j = 0;while (p[v][j] != -1) {p[w][j] = p[v][j];j++;}p[w][j] = w;}}}// 4. 找距离v0最远的顶点(与原C++逻辑一致)int maxDist = D[0];int farVex = 0;for (int i = 0; i < n; i++) {if (D[i] > maxDist) {maxDist = D[i];farVex = i;}}// 输出结果(转换为1开始的顶点编号)System.out.println(farVex + 1);System.out.println(maxDist);}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);while (true) {// 读取顶点数n和边数mint n = scanner.nextInt();int m = scanner.nextInt();if (n == 0 && m == 0) {break; // 输入结束,退出循环}// 构建图(读取m条边)MGraph G = new MGraph(n, m, scanner);// 读取起点v0(转换为0开始索引)int v0 = scanner.nextInt() - 1;// 执行迪杰斯特拉算法并输出结果shortestPathDIJ(G, v0);}scanner.close();}
}
10.总结
本文实现了一个基于Dijkstra算法的图最短路径求解系统,用于找到图中距离指定顶点最远顶点及其最短距离。系统核心功能包括:
(1)构建无向图邻接矩阵;
(2)使用Dijkstra算法计算单源最短路径;
(3)找出距离源点最远的顶点。
实现采用邻接矩阵存储图结构,包含C++、Python和Java三种语言版本,支持多组数据输入。算法时间复杂度为O(n²),适用于顶点数不超过100的图。测试样例验证了系统在无向图、带权边等场景下的正确性,满足题目要求的输入输出格式和功能需求。