题目描述
思路分析
题目要求求出最短路径;且在有多条最短路径时,给出花费最小的那一条。这其实是Dijkstra算法的应用变种,在上一道题的基础上做了一些变换。
我最开始的想法是,先求出最短路径,然后再把所有最短路径拿出来计算花费,进行比较;但这样做实在是不好实现且繁琐。
后来想到,可以在求最短路径的过程中,就筛选出距离短且花费便宜节点,因此就有了以下思路:
①将dist数组
的类型设为一个结构体,其内包含长度
和花费
(邻接矩阵数组也要设为该类型)
②在Dijkstra算法中,不仅在当遇到距离更短的节点时,更新长度、花费、路径;而且在遇到距离相等且花费更少的节点时,更新花费、路径。
这样最终求得的dist数组
和path数组
中的值,必然是最短且最便宜的。(此结论可由Djkstra算法的特征推导出)
注:
Djkstra算法性质
- 该算法时将确定了最短路径的顶点逐个地收入集合
S
中,S
初始状态仅包含源点S; - 对于那些还未被收入
S
中的顶点V,定义dist[V]
为从源点S到顶点V的最短路径长度,但是该路径仅经过集合S
中的顶点(也就是说,它可能不是最终的最短路径)
代码展示
/*
求指定城市之间的最短路径——>仍然是一个单源最短路径问题——>用有权图还是无权图?有权图,权重设为1
思路:
先dijkstar建立dist与path数组。
有个问题:会不会存在起始点和终点相同,但是中间路径不同的路径的呢?——>有可能,但是Dijkstra算法
似乎无法实现有多条最短路径的问题
存在多条最短路径时,就是说一个点可能会走向多个不同的点,所以在dijkstra算法中,一个点可能会有多个
“前驱”点,即path[i]可能会有多个,感觉没那么容易实现,如果用数组的话,会浪费多余空间,用链表似乎可行
如果用floyd算法,能找到源点到目标点的最短路径
然后与源点直接相连的点,到目标点的最短路径也能找到;用这种迭代的方法似乎能够找到多条最短路径
还没有用过floyd算法,那就用一次吧
现在看来以上的两种思路并不对,对于floyd来说,这种迭代的方法难以实现
询问GPT后,考虑使用以下方法
特殊化一下邻接矩阵的数组元素,使其为结构体,既存储路径长度,又存储最便宜路径
在使用Dijkstra算法时,如果路径长度相等,但是更便宜,那么最短路径就取这个
*/
#include <stack>
#include <cmath>
#include <cstdlib>
#include <iostream>
#define MAXSIZE 500
#define INFINITY 65535
#define ERROR 65535
typedef int vertex;
/* */
struct weightType
{
int length;
int expense;
};
/* 边节点 */
struct ENode
{
vertex V1, V2;
weightType weight;
};
typedef ENode* ptrToENode;
typedef ptrToENode Edge;
/* 图节点 */
struct GNode
{
int Nv;
int Ne;
weightType G[MAXSIZE][MAXSIZE]; //邻接矩阵
};
typedef GNode* ptrToGNode;
typedef ptrToGNode MGraph;
MGraph creatGraph(int Nv)
{
MGraph G = (MGraph)malloc(sizeof(GNode));
G->Nv = Nv;
/* 初始化邻接矩阵 */
for (vertex i = 0; i < G->Nv; i++)
{
for (vertex j = 0; j < G->Nv; j++)
{
if (i == j)
{
G->G[i][j].length = 0;
G->G[i][j].expense = 0;
}
else
{
G->G[i][j].length = INFINITY;
G->G[i][j].expense = INFINITY;
}
}
}
return G;
}
/* 插入边 */
void InsertEdge(MGraph Graph, Edge E)
{
/* 插入边 (V1, V2) */
Graph->G[E->V1][E->V2].length = E->weight.length;
Graph->G[E->V1][E->V2].expense = E->weight.expense;
Graph->G[E->V2][E->V1].length = E->weight.length;
Graph->G[E->V2][E->V1].expense = E->weight.expense;
}
/* 建立完整的图 */
MGraph buildGraph(int Nv, int Ne)
{
/* 建立有Nv个顶点但没有边的图 */
MGraph G = creatGraph(Nv);
G->Ne = Ne;
/* 建立边 */
Edge E = (Edge)malloc(sizeof(ENode));
for (int i = 0; i < G->Ne; i++)
{
std::cin >> E->V1 >> E->V2 >> E->weight.length >> E->weight.expense;
InsertEdge(G, E);
}
return G;
}
vertex findMinDist(MGraph Graph, weightType dist[], bool collected[])
{
vertex V, minV;
int minLength = INFINITY;
int minExpense = INFINITY;
for (V = 0; V < Graph->Nv; V++)
{ /* 路径长度更短 或者 路径等于最小长度但是花费更小 */
if (collected[V] == false && (dist[V].length < minLength || (dist[V].expense < minExpense && dist[V].length == minLength)))
{
minLength = dist[V].length;
minExpense = dist[V].expense;
minV = V;
}
}
if (minLength < INFINITY)
return minV;
else return ERROR; //错误标记
}
/* djikstra */
void Dijkstra(MGraph Graph,vertex S, weightType dist[], int path[])
{
vertex V, W;
bool collected[MAXSIZE];
/* 初始化:此处默认邻接矩阵中不存在的边用INFINITY表示 */
for (V = 0; V < Graph->Nv; V++)
{
dist[V].length = Graph->G[S][V].length;
dist[V].expense = Graph->G[S][V].expense;
if (S == V)
path[V] = -1;
else if (dist[V].length != INFINITY && dist[V].expense != INFINITY)
path[V] = S;
else
path[V] = -1;
collected[V] = false;
}
/* 将源点收入集合 */
dist[S].expense = 0;
dist[S].length = 0;
collected[S] = true;
while (1)
{
V = findMinDist(Graph, dist, collected);
if (V == ERROR)
break;
collected[V] = true;
for (W = 0; W < Graph->Nv; W++)
{ /* 访问V的邻接节点 */
if (collected[W] == false && Graph->G[V][W].length != INFINITY)
{ /* 距离更短时,更新长度、花费、路径 */
if (dist[V].length + Graph->G[V][W].length < dist[W].length)
{
dist[W].length = dist[V].length + Graph->G[V][W].length;
dist[W].expense = dist[V].expense + Graph->G[V][W].expense;
path[W] = V;
}
/* 距离相等,但是花费更少,更新花费、路径 */
else if (dist[V].length + Graph->G[V][W].length == dist[W].length && dist[V].expense + Graph->G[V][W].expense < dist[W].expense)
{
dist[W].expense = dist[V].expense + Graph->G[V][W].expense;
path[W] = V;
}
}
}
}
}
int main()
{
weightType dist[MAXSIZE];
int path[MAXSIZE];
int Nv;
int Ne;
vertex S, D;
std::cin >> Nv >> Ne >> S >> D; //N是城市的个数;M是高速公路的条数;S是出发地的城市编号;D是目的地的城市编号
MGraph Graph = buildGraph(Nv, Ne);
Dijkstra(Graph, S, dist, path);
std::cout << dist[D].length << ' ' << dist[D].expense;
return 0;
}
心路历程
虽然经过上一题的洗礼,但这道题一开始还是没有头绪…又被自己菜哭了