算法步骤如下:贪心思想
这里默认1为起点,且每一轮必定找出一个其他点到1点的最短路径,那么将在n-1轮后找到所有点到1点的最短路径。
- 找出与 1 直接相邻的点,在这些邻点与 1 组成的边中选择权值最小的一条边
- 将第一步中选中的邻点与 1 点的连接关系改为不相邻,而将通过该邻点可以与 1 点进行连接的点与 1 点的连接关系改为相邻(如果本身这个点与 1 点就是相邻的,那么我们就要判断哪个原来的权值与通过邻点连接的边的权值的大小了)。1 点与这些后来连接上的邻点所构成的边的权值为:
1 点与中间邻点的边的权值 + 中间邻点和新连接的邻点所构成的边的权值
不断重复这个过程即可。
代码如下:
#include<iostream>
#include<cstring>
using namespace std;
/*
最短路径算法: Dijkstra算法
图的存储结构: 邻接矩阵(二维数组),当然也可以邻接表(一维数组 + 链表),这里用的邻接矩阵。这里默认是无向图
*/
int pointNum; //点的个数, 当然点从1开始
int lineNum; //边的个数
int begin=1, goal=1; //起始点,目标点
int Graph[2022][2022]; //用二维数组来表示图,Graph[1][2] = 6, 表示点1与2之间存在边,且这条边的权值为6. 不存在边记为-1, 该边已经计算过了记为-2
int path[2022]; //记录沿途路径, path[2] = 1, 表示在最短路径中,2点的前一个点是1.
/*
path举例说明:
如我们得出的一条由 1点 为源点到 4点 的最短路径为
1 -> 2 -> 4
那么就会有: path[4] = 2 、 path[2] = 1
*/
int weight[2022]; //记录由初始点到每个点的最短路径的权值
void Dijkstra();
void Input();
void Output();
int main()
{
Input();
Dijkstra();
Output();
return 0;
}
void Input()
{
cout<<"请输入点的个数(点的个数小于2021): "; cin>>pointNum;
cout<<"请输入边的个数: ";cin>>lineNum;
cout<<"请输入起点: "; cin>>goal; //这里将begin和goal倒置,方便输出
cout<<"请输入目标点: "; cin>>begin;
int n,m,w;
int k=1;
memset(Graph,-1,sizeof(Graph));
for(int i=0; i<lineNum; i++)
{
cout<<"依次输入无向边边的两个顶点和权值: ";cin>>n>>m>>w;
Graph[n][m] = w;
Graph[m][n] = w;
if(n==begin){
path[m] = begin;
}else if(m==begin){
path[n] = begin;
}
}
}
void Dijkstra()
{
path[begin] = begin;
weight[begin] = 0;
for(int i=0; i<pointNum-1; i++)
{
int j;
int minWeight = 999999999; //默认所有边权值总和不超过10亿
int point = -1;
for(j=1;j<=pointNum;j++)
{
if(j!=begin && Graph[begin][j]>0)
{
if(minWeight > Graph[begin][j]){
minWeight = Graph[begin][j];
point = j;
}
}
}
weight[point] = minWeight;
for(j=1;j<=pointNum;j++){
if(j!=begin && j!=point && Graph[point][j]!=-1)
{
if(Graph[begin][j] == -1)
{
Graph[begin][j] = Graph[begin][point] + Graph[point][j];
path[j] = point;
}else if(Graph[begin][j] > (Graph[begin][point] + Graph[point][j]))
{
Graph[begin][j] = Graph[begin][point] + Graph[point][j];
path[j] = point;
}
}
}
Graph[begin][point] = -2;
Graph[point][begin] = -2;
if(point == goal) //已经找到起点到目标点的最短路径,直接结束即可,无需继续进行,当然你也可以不结束,全部运行完后也能获得输出结果
{
break;
}
}
}
void Output()
{
cout<<"最短路径的权值为: "<<weight[goal]<<endl;
int n = path[goal];
cout<<"路径为: "<<endl;
cout<<goal<<" -> "<<n;
while(n != begin){
cout<<" -> "<<path[n];
n=path[n];
}
cout<<endl;
}
简要分析:
如果一个无相图有n个点,m条边,且这个无向图是连通图,那么至少有n-1条边。
Bellman-Ford算法/SPFA算法对于最短路径问题的时间复杂度为o(m * n)。
Dijkstra算法对于最短路径问题的时间复杂度为o(n * n),由于m>n-1,且m最大为n*(n-1),因此单论时间复杂度来讲,Dijkstra算法要更优。
如果你十分了解Dijkstra算法,那么会知道这个算法是可以进行堆优化的(每次找当前这一轮权值最小的边,使用小根堆优化),使得时间复杂度达到o(n*log₂n)。
Dijkstra算法缺点也很明显,不能有负权值的边。而Bellman-Ford算法/SPFA算法只需要没有负权值回路即可。
例题: 蓝桥杯省赛2020填空题
小蓝学习了最短路径之后特别高兴,他定义了一个特别的图,希望找到图中的最短路径。
小蓝的图由2021个结点组成,依次编号1至2021。
对于两个不同的结点a, b,如果a和b的差的绝对值大于21,则两个结点之间没有边相连;如果a和b的差的绝对值小于等于21,则两个点之间有一条长度为a和b的最小公倍数的无向边相连。
例如∶结点1和结点23之间没有边相连;结点3和结点24之间有一条无向边,长度为24;结点15和结点25之间有一条无向边,长度为75。
请计算,结点1和结点2021之间的最短路径长度是多少。
提示:建议使用计算机编程解决问题。
思路: 最短路径算法 + 辗转相除法
代码如下:
#include<iostream>
#include<cstring>
using namespace std;
/*
最短路径算法: Dijkstra算法
图的存储结构: 邻接矩阵(二维数组),当然也可以邻接表(一维数组 + 链表),这里用的邻接矩阵。这里默认是无向图
*/
int pointNum; //点的个数, 当然点从1开始
int begin, goal; //起始点,目标点
int Graph[2022][2022]; //用二维数组来表示图,Graph[1][2] = 6, 表示点1与2之间存在边,且这条边的权值为6. 不存在边记为-1, 该边已经计算过了记为-2
int path[2022]; //记录沿途路径, path[2] = 1, 表示在最短路径中,2点的前一个点是1.
/*
path举例说明:
如我们得出的一条由 1点 为源点到 4点 的最短路径为
1 -> 2 -> 4
那么就会有: path[4] = 2 、 path[2] = 1
*/
int weight[2022]; //记录由初始点到每个点的最短路径的权值
void Initialization();
void Dijkstra();
void Output();
int GetLeastCommonMultiple(int num1,int num2);
int main()
{
Initialization();
Dijkstra();
Output();
return 0;
}
void Initialization()
{
int i,j,k;
pointNum = 2021; goal = 1; begin = 2021;
memset(Graph,-1,sizeof(Graph));
for(i=1;i<=2021;i++)
{
if(i-21>0 && i+21<=2021){
j = i-20;
k = 21;
}else if(i-21<0){
j = 1;
k = 21;
}else if(i+21>2021){
j = i-20;
k = 2021-i;
}
for(j=i;j<=i+k;j++)
{
if(j!=i){
Graph[i][j] = GetLeastCommonMultiple(j,i);
Graph[j][i] = Graph[i][j];
if(i==begin){
path[j] = i;
}else if(j==begin){
path[i] = j;
}
}
}
}
}
int GetLeastCommonMultiple(int num1,int num2)
{
int n1 = num1, n2 = num2 ,exchange;
int MaximumCommonFactor = -1;
while(1){
if(n1%n2 == 0){
MaximumCommonFactor = n2;
break;
}else{
exchange = n1;
n1 = n2;
n2 = exchange%n2;
}
}
return num1*num2/MaximumCommonFactor;
}
void Dijkstra()
{
path[begin] = begin;
weight[begin] = 0;
for(int i=0; i<pointNum-1; i++)
{
int j;
int minWeight = 999999999; //默认所有边权值总和不超过10亿
int point = -1;
for(j=1;j<=pointNum;j++)
{
if(j!=begin && Graph[begin][j]>=0)
{
if(minWeight>Graph[begin][j]){
minWeight = Graph[begin][j];
point = j;
}
}
}
weight[point] = minWeight;
for(j=1;j<=pointNum;j++){
if(j!=begin && j!=point && Graph[point][j]!=-1)
{
if(Graph[begin][j] == -1)
{
Graph[begin][j] = Graph[begin][point] + Graph[point][j];
path[j] = point;
}else if(Graph[begin][j] > (Graph[begin][point] + Graph[point][j]))
{
Graph[begin][j] = Graph[begin][point] + Graph[point][j];
path[j] = point;
}
}
}
Graph[begin][point] = -2;
Graph[point][begin] = -2;
if(point == goal) //已经找到起点到目标点的最短路径,直接结束即可,无需继续进行,当然你也可以不结束,全部运行完后也能获得输出结果
{
break;
}
}
}
void Output()
{
cout<<"最短路径的权值为: "<<weight[goal]<<endl;
int n = path[goal];
cout<<"路径为: "<<endl;
cout<<goal<<" -> "<<n;
while(n != begin){
cout<<" -> "<<path[n];
n=path[n];
}
cout<<endl;
}
/*
最终答案: 10266837
*/