有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径。
输入格式:
输入说明:输入数据的第1行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0~(N−1);M是高速公路的条数;S是出发地的城市编号;D是目的地的城市编号。随后的M行中,每行给出一条高速公路的信息,分别是:城市1、城市2、高速公路长度、收费额,中间用空格分开,数字均为整数且不超过500。输入保证解的存在。
输出格式:
在一行里输出路径的长度和收费总额,数字间以空格分隔,输出结尾不能有多余空格。
输入样例:
4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20
输出样例:
3 40
解题
单源最短路径;
除了得到最短路径,还要在此基础上得到最便宜提的路径;
单源最短路径用dijkstra算法
用dijkstra算法时,邻接矩阵表示图更方便;
设置这么多结构函数虽慢,debug和修改代码会更快;
1.设置图结构
把Vertex变为road类型,有distance和cost两个成员变量
#include
using namespace std;
typedef struct road Vertex;
typedef int weighttype;
#define MAX 501
#define INFINITY 65533
struct road{
int distance;
int cost;
};
typedef struct GNode *ptrtoGNode; //图结构
struct GNode{
int Nv; //节点数
int Ne; //边数
Vertex G[MAX][MAX]; //存放500个序号
int start;
int end;
};
typedef ptrtoGNode MGraph;
2.初始化图函数
MGraph CreateGraph(int n)
{
MGraph Graph = new struct GNode;
Graph->Nv=n;
Graph->Ne=0;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
Graph->G[i][j].cost=INFINITY;
Graph->G[i][j].distance=INFINITY;
}
return Graph;
//距离全部初始化为最大值
}
3.边结构
typedef struct ENode * ptrtoENode;
struct ENode{
//边结构
int V1;
int V2;
weighttype distance;
weighttype cost;
};
typedef ptrtoENode Edge;
4.边插入图中的函数
标准的无向图插法
void InsertEdge(MGraph Graph,Edge E)
{
Graph->G[E->V1][E->V2].cost=E->cost;
Graph->G[E->V1][E->V2].distance=E->distance;
Graph->G[E->V2][E->V1].cost=E->cost;
Graph->G[E->V2][E->V1].distance=E->distance;
}
5.建图函数
MGraph BuildGraph()
{
int N,M,S,D;
cin>>N;
//s出发,d停止
MGraph Graph= CreateGraph(N);
cin>>Graph->Ne>>Graph->start>>Graph->end;
if(Graph->Ne!=0)
for(int i=0;i<Graph->Ne;i++)
{
Edge E=new struct ENode;
cin>>E->V1>>E->V2>>E->distance>>E->cost;
InsertEdge(Graph,E);
}
return Graph;
}
上面代码进行了输入操作,并构建了图;
可以建立MGraph Graph保存;
下面用Dijkstra函数对图进行处理并输出;
注意点
Dijkstra函数每次都拿未处理过的distance最小值来处理;
需要FindMinDist函数,得到从s出发的最小distance的e的下标;若所有目的地都处理过,返回ERROR值-1即可;
int FindMinDist(MGraph Graph,int dist[],int collected[])
{
int MinV,V; //下标
int MinDist = INFINITY;
for(V=0;V<Graph->Nv;V++)
{ //未收入且小于当前最小值
if(collected[V]==false && dist[V]<MinDist)
{
MinDist = dist[V];
MinV=V; //保存下标
}
}
if(MinDist<INFINITY)
return MinV;
else return -1;
}
Dijkstra函数
先初始化dist列表,表示从s到每个城市的最短距离;
再初始化cost,表示从s到每个城市的最短距离路径上所画的钱;
再初始化path,表示从s到每个城市的最短距离的路径;
dist初始化为s行的每个元素的distance;无边则INFINITY;
cost初始化为s行的每个元素的cost;无边则INFINITY;
初始化起点距离和cost,都为0,因为自己到自己距离为0,花费为0;
将起点收入collected;
开始循环:
得到s行里没遍历过的城市下标;
收入该城市;
循环该城市到每一个S行城市的距离:
若另一个城市未被收录,且两者距离
if(dist[V]+Graph->G[V][i].distance
S从距离最短的V通过V-i的边到i的距离比当前到i的距离小:更新i的距离
也要更新花费的钱;和路径(本题不用保存路径);
若距离相等——而花费更小,也要更新;
bool Dijkstra(MGraph Graph, int s,int e) //S为起点
{ //传入图,dist为距离列表,path为路径,s为节点
int collected[MAX]; //表示放入的结点
int V;
int dist[MAX];
int cost[MAX];
int path[MAX]; //保存路径
for(int i=0;i<Graph->Nv;i++)
{
dist[i]=Graph->G[s][i].distance; //距离
cost[i]=Graph->G[s][i].cost;
if (dist[i]<INFINITY)
path[i]=s; //i的上一个为s
else
path[i]=-1; //没有路
collected[i]=false;
}
dist[s]=0; //起点收入集合
cost[s]=0;
collected[s]=true;
while(1)
{
V=FindMinDist(Graph,dist,collected);
if(V==-1)
break;
collected[V]=true;
for( int i=0;i<Graph->Nv;i++)
if(collected[i]==false && Graph->G[V][i].distance<INFINITY){
if(Graph->G[V][i].distance<0)
return false; //有负边——失败
if(dist[V]+Graph->G[V][i].distance<dist[i])
{
dist[i]=dist[V]+Graph->G[V][i].distance; //更新dist[i]
cost[i]=cost[V]+Graph->G[V][i].cost;
path[i]=V;
}
//如果距离相等,花费更小;
if(dist[V]+Graph->G[V][i].distance==dist[i]&&cost[V]+Graph->G[V][i].cost<cost[i]){
dist[i]=dist[V]+Graph->G[V][i].distance; //更新dist[i]
cost[i]=cost[V]+Graph->G[V][i].cost;
path[i]=V;
}
}
} //while 结束
cout<<dist[e]<<" "<<cost[e]<<endl;
return true;
}
main函数
int main()
{
MGraph Graph= BuildGraph();
bool t = Dijkstra(Graph,Graph->start,Graph->end);
}
不小心也搞了个floyd的算法,复杂度太高超时了
因为建图规范,所以只要改变main函数和floyd函数就行
void Floyd(MGraph Graph,Vertex D[][MAX]){
for(int i=0;i<Graph->Nv;i++)
for(int j=0;j<Graph->Nv;j++)
D[i][j]=Graph->G[i][j];
for(int k=0;k<Graph->Nv;k++)
for(int i=0;i<Graph->Nv;i++)
for(int j=0;j<Graph->Nv;j++)
{
if(D[i][k].distance+D[k][j].distance<D[i][j].distance)
{
D[i][j].distance = D[i][k].distance+D[k][j].distance;
D[i][j].cost = D[i][k].cost+D[k][j].cost;
}
if(D[i][k].distance+D[k][j].distance==D[i][j].distance
&&D[i][k].cost+D[k][j].cost<D[i][j].cost)
{
D[i][j].distance = D[i][k].distance+D[k][j].distance;
D[i][j].cost = D[i][k].cost+D[k][j].cost;
}
}
}
//此时
void output(MGraph Graph)
{
Vertex D[MAX][MAX];
Floyd(Graph,D);
//此时D存放最短distance和最短cost;
cout<<D[Graph->start][Graph->end].distance<<" "<<D[Graph->start][Graph->end].cost;
}
//
int main()
{
MGraph Graph=BuildGraph();
output(Graph);
}
总结
该题在distance相同的情况下,多加了一个cost的判断;
Dijkstra算法:(有负边则失效) if ( Graph->G[V][W]<0 )
需要collected列表,保存已找到最小值的点;
需要dist列表,保存当前点到每个其他点的最短路径,初始化为INFINITY;
计算从S到v的最短路径的方法为
for(int i=0;iNv;i++)
if(dist[i]+Graph->G[i][V] < dist[V]) dist[V]=dist[i]+Graph->G[i][V] ;
复杂度为O(N^2),在于查找最小值的复杂度;
若用最小堆可降至O(NlogN);
Floyd算法:(有负圈则失效) if ( i==j && D[i][j]<0 ) 转一圈回来为负值
得到图的邻接矩阵表示;无边则初始化为最大值INFINITY;
在此基础上,得到每两个点的最短距离;
无需collected列表
for(int k=0;kNv;k++)
for(int i=0;iNv;i++)
for(int j=0;jNv;j++)
if(D[i][k]+D[k][j]
调用时——要跳过D[n][n],自己到自己最小值为0;
三重循环,得到每两个点的最小值——有动态规划的最优子问题继承;
复杂度O(N^3);
若需要计算cost,Dijkstra算法需要增加一个cost列表,保存起始点到每个其他点的cost;
旅游规划问题推广:
要求数最短路径有多少条
count[s] = 1;
如果找到更短路:count[W]=count[V];因为W与V只差一条边,所以两者相同;
如果找到等长路:count[W]+=count[V];登场路的数量为W前面一个点的路的数量;
要求边数最少的最短路
count[s] = 0;
如果找到更短路:count[W]=count[V]+1;
如果找到等长路:count[W]=count[V]+1;
你可能感兴趣的:(数据结构,算法,图)