目录
实验目的及内容
根据一张旅游路线图,已知城市之间的路线长度以及花费的费用。请编写程序输出一条从出发地到目的地之间的最短规划路线。如果存在若干条最短路径,则输出费用最少的一条路线。
解题思路
1.分析需求:
首先,大体上这是一个求单源最短路径问题,因而我打算基于迪杰斯特拉算法来求解,正好也可以复习一下课堂学习知识;
但是,题目还有进一步要求:(1).不仅要比较路径,同时要比较过路费;(2)还需要记录最短路径的具体“路线”,即这条路是怎么走的;
思考:
(1)倒是好解决,我们其实只需要照葫芦画瓢,把迪杰斯特拉算法稍作改动,加入比较过路费的操作即可,只是写代码的时候要注意,路径的优先级要高于路费;
(2)要记录路径,其实只需对迪杰斯特拉算法足够熟悉,就能想到办法——记录每个节点的前驱,然后再逐级递归,直至出发点即可打印出完整的路径;如何完成对前驱的记录呢?
可以借助一个int path[]数组,其下标代表每个顶点的编号,其值存储前驱的编号;
首先,我们将与源点有通路的顶点前驱初始化为源点,没有通路的那些点,前驱初始化为无穷;
选择当前距离最少的点Ci加入源点所在S集合之后,我们需要更新各个顶点到S集合的最小距离,此时,重点来了,如果发现S集合外有一个点Bi的最短距离因为Ci的加入而变得更短了,那么就要把Ci设为其前驱;
因为如果这个Bi被加入了S集合,那么一定是通过Ci点与S集合进行连接的;所以,利用递推思想可以发现,只要每次“更新最短距离”的时候,也同时更新各个点的前驱,就可以记录路径了;
2.核心算法:迪杰斯特拉算法
实验代码及注释
#include <iostream>
#define Max 503
#define INF 0xcffffff//宏定义路程极大值
using namespace std;
int source; //出发地编号
int destination;//目的地编号
//定义道路类
class road
{
public:
int distance;
int cost;
};
//定义地图类
typedef class AMGraph
{
public:
int vex, arc; //顶点数和边数
road arcs[Max][Max];//邻接矩阵
};
int dis[Max];//dis保存source到其他各个点的最短路径总长
int path[Max];//path通过保存每个点的前驱结点来记录路径
bool book[Max]; //标记起点到某个点是否已找到最短路
int totalcost[Max];//保存路径的总路费
//改造版迪杰斯特拉算法
void Dijkstra(AMGraph &G,int start)
{
//整体初始化工作
for (int i = 1; i <= G.vex; i++)
{
dis[i] = G.arcs[start][i].distance;
path[i] = dis[i] < INF ? 1 : -666;
book[i] = false;
totalcost[i]=G.arcs[start][i].cost;
}
//起点初始化
book[start] = true;
dis[start] = 0;//起点和起点间距离为0
totalcost[start]=0;//呆在原地不用花钱
//寻找start和其他各个点间的最小路径,大循环vex-1次
for (register int i = 2; i <= G.vex; i++)
{
int mincost = INF; //记录最少路费
int mindistance = INF;//记录最短路长
int u=start;//记录每次加入book集合的点
//按照距离和路费两个指标,查找当前的最优点,加入book集合
for (register int j = 1; j <= G.vex; j++)
{
if(!book[j])
{
//先寻找当前哪个点距离start最近
if (mindistance > dis[j])
{
mindistance = dis[j];
u = j;
mincost= totalcost[j];
}
//如若距离相同,则找路费更少的那个点
else if(mindistance==dis[j] && mincost > totalcost[j])
{
mincost = totalcost[j];
u=j;
}
}
}
book[u] = true;//将最优点加入book集合
//加入后,再次遍历所有book外面的点,更新start到它们的最短路程长和路费
for ( int j = 1; j <= G.vex; j++)
{
if(!book[j])
{
if (dis[j] > dis[u] + G.arcs[u][j].distance)
{
dis[j] = dis[u] + G.arcs[u][j].distance; //更新最短路径值
totalcost[j]= totalcost[u]+G.arcs[u][j].cost;//更新最短路径的路费
path[j] = u;//修改j的前驱为u
}
//如若距离相同,则选择路费更少的那一条路
else if (dis[j] == dis[u]+G.arcs[u][j].distance && totalcost[j] > totalcost[u] + G.arcs[u][j].cost)
{
totalcost[j]= totalcost[u]+G.arcs[u][j].cost;//更新最短路径的路费
path[j] = u;//修改j的前驱为u
}
}
}
}
return;
}
//递归输出最短路径
void findroad(int x)
{
if (path[x] == source)
{
cout << "C"<<source;
}
else
{
findroad(path[x]);
}
cout << " -> C" << x;
return;
}
//提示用户输入,并创建图
void input(AMGraph &G)
{
cout<<"请输入所有城市的数量,以及道路总数:"<<endl;
cin >> G.vex >> G.arc;
cout<<"-----------------\n";
//初始化邻接矩阵,register int 使用CPU内部寄存器,加快速度
for (register int i = 1; i <= G.vex; i++)
{
for (register int j = 1; j <= G.vex; j++)
{
G.arcs[i][j].distance = INF;
G.arcs[i][j].cost=INF;
}
}
cout<<"输入每一条道路的信息:"<<endl;
for (register int i = 1; i <= G.arc; i++)
{
int u, v, w,x;
cout<<"其中一个城市是C";
cin >> u ;
cout<<"另外一个城市是C";
cin>> v;
cout<<"其间路长&路费:";
cin>>w>>x;
//无向图邻接矩阵必对称!
G.arcs[u][v].distance = w;
G.arcs[v][u].distance = w;
G.arcs[u][v].cost=x;
G.arcs[v][u].cost=x;
cout<<"-----------------\n";
}
cout<<"请输入出发地C";
cin>>source;
cout<<"请输入目的地C";
cin>>destination;
}
//输出
void output(AMGraph &G,int dest)
{
cout<<"********************************************************\n";
cout << "起点 C"<<source<<"到目的地C"<<dest<<"的最短路程长度为:";
cout << dis[dest] ;
cout<<",最少路费为:"<<totalcost[dest]<<endl;
cout << "起点 C"<<source<<"到目的地C" << dest << "的具体路线: ";
findroad(dest);
cout << endl;
}
int main()
{
AMGraph G;
input(G);
Dijkstra(G,source);
output(G,destination);
return 0;
}
输入输出说明及结果截图
输入:按照提示输入,本次测试样例基本上包含了各种可能情况,从C1到C2有两条长度相同的最短路径,并且在一开始C1到C3和C4距离都是最短的,因此验证了代码的健壮性
输出:输出题目要求的路线
心得体会
本次实验在迪杰斯特拉算法的基础上进行了一些拓展,加深了我对这个算法的理解,也让我对递归的写法更加熟练了。这次实验也充分告诉我,在写算法代码之前,在纸上把算法图示画一遍,对于代码的设计与实现是很有好处的!