hnu 数据结构 实验七

实验7 图的经典问题

分别提交两个附件:
1:设计文档(提交PDF格式的文件,文件名要规范,参看规范)。
2:实验源码包。(文件名也要规范,可以参考报告文件的格式)

实验七 图的应用二(行车路线)
一、问题分析

题目描述
1.问题描述
小明和小芳出去乡村玩,小明负责开车,小芳来导航。
  小芳将可能的道路分为大道和小道。大道比较好走,每走1公里小明会增加1的疲劳度。小道不好走,如果走小道,小明的疲劳值会快速增加,走s公里小明会增加s2的疲劳度。所有的小道不相交。
  例如:有5个路口,1号路口到2号路口为小道,2号路口到3号路口为大道,3号路口到4号路口为大道,4号路口到5号路口为小道,相邻路口之间的距离都是2公里。如果小明从1号路口到5号路口,则总疲劳值为22+2+2+22=4+2+2+4=12。
  现在小芳拿到了地图,请帮助她规划一个开车的路线,使得按这个路线开车小明的疲劳度最小。
  
【输入形式】输入的第一行包含两个整数n, m,分别表示路口的数量和道路的数量。路口由1至n编号,小明需要开车从1号路口到n号路口。
接下来m行描述道路,每行包含四个整数t,a,b,c,表示一条类型为t,连接a与b两个路口,长度为c公里的双向道路。其中t为0表示大道,t为1表示小道。保证1号路口和n号路口是连通的。

【输出形式】输出一个整数,表示最优路线下小明的疲劳度。

【样例输入】6 7
1 1 2 3
0 2 3 2
0 1 3 30
0 3 4 20
0 4 5 30
1 3 5 6
0 5 6 1

【样例输出】48

【数据规模与约定】对于25%的评测用例,不存在小道;
对于所有评测用例,1≤n≤8,1≤m≤10,1≤a,b≤n,t是
0或1,c≤100。保证答案不超过100。

【实验代码及评分】源代码提交工程压缩包,压缩包至少包含以下三个文件:
1)XXX.h:图ADT的定义和声明
2)XXX.h:图ADT的实现
3)XXXXX.cpp:主程序

分析并确定要处理的对象(数据)是什么
我们要根据t的数值确定道路的类型,并且根据道路的类型计算得到小明开车经过这条路的疲劳值,将疲劳值作为这条边上的权值,计算出从起点到终点的最短疲劳值的路径。
根据以上分析可知,需要处理的对象是带有权值的图。

分析并确定要实现的功能是什么
根据题目意思,我们根据道路的类型计算出经过这条道路对应的疲劳值,再找到从起点开始到终点,找到最短路径。

分析并确定处理后的结果如何显示
根据题目意思,直接输出一个最小疲劳值即可。

二、数据结构和算法设计
抽象数据类型设计
考虑到数据之间多对多的关系,可以采用图的数据结构储存。该图可以通过邻接矩阵的实现方法进行实现。

物理数据对象设计(不用给出基本操作的实现)
采用邻接矩阵实现数据之间的关系储存。通过整型的**matrix存储结点之间的关系,即结点间的权值,其初始值为0。
用两个整型变量numvertex,numedge,分别存储图的结点数和边数。
输入边权值的同时,将权值存储在矩阵中,即可完成图关系的建立。

算法思想的设计
本题要找图中的最短路径,可以考虑采用Dijkstra算法。

【步骤1.】可将街道之间的关系存储在了邻接矩阵中,根据道路的类型计算出此条道路对应的疲劳值,作为这条边的权值。建立图的边权时要注意生成无向图。

【步骤2.】采用Dijkstra算法,从点集S中任取顶点U,都有一条从顶点S到顶点U的路径,再从顶点U到顶点X的边,计算出所有顶点U产生的路径中的最小值。

【步骤3.】输出最后一个结点(即终点)的最终路径值,即为到达终点的最小疲劳值。

请用题目中样例,基于所设计的算法,详细给出样例求解过程。
【样例输入】6 7
1 1 2 3
0 2 3 2
0 1 3 30
0 3 4 20
0 4 5 30
1 3 5 6
0 5 6 1
【样例输出】48

【样例分析】
1.将所有结点的关系存储在矩阵中,矩阵如图所示:
在这里插入图片描述

2.根据Dijkstra算法,计算出到达各个结点的最短路径
在这里插入图片描述

3.输出最后一个结点的路径值,即为小明到达终点的最小疲劳值
关键功能的算法步骤(不能用源码)

【步骤2】Dijkstra算法,伪代码如下:
void Dijkstra(graph* G,int *D,int s)
{
int i,v,w;
for(i = 0 ; i < G->n() ; i++)
D[i] = INFINITY;
D[0] = 0;
for(i = 0; in() ; i++)
{
v = minVertex(G,D);
if(D[v] == INFINITY) return ;
G->setmark(v,VISITED);
for(w = G->first(v); w < G->n(); w = G->next(v,w))
if(D[w] > D[v] + G->weight(v,w))
D[w] = D[v] + G->weight(v,w);
}
cout << D[G->n()-1] << endl;//输出最后一个结点的路径值
}

三、算法性能分析。
1.将|E|个结点存储在矩阵中,时间复杂度T=|E|

2.步骤2中Dijkstra算法采用了一个双重for循环,时间复杂度T=在这里插入图片描述

3.总的时间复杂度T=|E|+,化简后时间复杂度为O(|E|+)在这里插入图片描述)

4.算法过程中开辟了一个大小为|E|的一维数组存储道路的类型,时间复杂度为O(|E|)

四、拓展
原题中小路与小路是可以联通的,因此难点在于连续走小路L1,L2时,产生的疲劳值是 (L1 + L2)(L1 + L2) 而不是 L1L1 + L2*L2.
解决思路是把大路和小路分开在两张图考虑。由于小路的疲劳值是连续计算再平方的,所以首先用一个Floyd算法计算小路图的点对点最短路。如果直接用最普通的4行Floyd法O(n3)会超时,可以利用小路图矩阵的对称性优化一下常数,就不会超时了。
而合并成一张图的话,两点(u,v)之间的距离应当是一个确定的值,如果大路图和小路图中u,v都是连通的,那么直觉上应该取大路图中uv长度和小路图中uv长度的最小值,然后额外再用一个二维布尔数组记录每对点之间的距离是来自大路还是小路。 但是这是不正确的。因为经过Floyd归化后,相当于对小路图添加了约束,即不能连续走两段小路。uv两点之间小路的距离比大路短,但uv选取了小路之后,从v出发到下一个点(比方说w)只能走大路了,即便“uv走大路+vw走小路”比“uv走小路+vw走大路”综合起来要短。
计算最短路的时候,Dijkstra算法及其变形(堆优化)还是Bellman-Ford算法及其变形(SPFA),都有一个d数组表示每个点到源的距离。此时因为走完大路可以再走大路或小路,但走完小路只能走大路(连续走小路的情形已被Floyd算法过程归化),因此要开两个数组db和ds分别表示最后一步走大路/小路时各点到源的距离,并按照上述原则在松弛操作中更新db和ds. 由于每个点i到源对应有两个距离db[i]和ds[i],db[i]成为全局最小后ds[i]仍有可能被更新变成全局最小,同理ds[i]成为全局最小后db[i]仍有可能被更新得到全局最小,因此基于全局最小距离进行松弛操作更新d数组的Dijkstra算法要修改适应本问题就比较困难。而Bellman-Ford算法及SPFA只用了相邻节点之间的路径信息,并不需要求全局最小,就可以适用于本问题。

代码实现部分:
graph.h

#ifndef GRAPH
#define GRAPH
#include <string>
using namespace std;
class Graph{
private:
	void operator=(const Graph&) {}
  	Graph(const Graph&) {}
public:
 	Graph(){}                                 //默认构造函数
 	virtual~Graph() {}                        //析构函数
  	virtual void Init(int n) =0;              //初始化一有n个顶点的图
  	virtual int n() =0;                       //返回图的顶点数
  	virtual int e() =0;                       //返回图的边数
  	virtual int first(int v) =0;              //返回顶点v的第一个邻居
  	virtual int next(int v, int w) =0;        //返回在w点之后的邻居
  	virtual void setType(bool flag)=0;        //设置图的类型(有向图或无向图)
	virtual bool getType()=0;                 //获取图的类型
  	virtual int locateVex(string u) =0;       //找到(包含实际信息的)顶点在图中的位置
  	virtual string getVex(int v)=0;           //返回某个顶点的值(实际信息)
	virtual void putVex(int v,int value) =0;  //给某个顶点赋值
  	virtual void setEdge(int v1, int v2, int wght) =0;   //为边(v1,v2)设置权值
  	virtual void delEdge(int v1, int v2) =0;  //删除边(v1,v2)
  	virtual bool isEdge(int i, int j) =0;     //判断边(i,j)是否在图中
  	virtual int weight(int v1, int v2) =0;    //返回边的权值
  	virtual int getMark(int v) =0;            //取得和设置顶点的标志位
  	virtual void setMark(int v, int val) =0;
  	virtual void unMark()=0;
};
#endif

graphm.h
#include
#include
#include
#include <time.h>
#include “graph.h”
#ifndef GRAPHM_H
#define _GRAPHM_H
#define MAX_VERTEX_NUM 40
#define UNVISITED 0
#define VISITED 1
using namespace std;

template //comp的模板函数
inline bool Comp(T t1,T t2)
{
if(t1==t2)
return true;
return false;
}
template<> //模板特殊化
bool Comp(string s1,string s2)
{
if(strcmp(s1.c_str(),s2.c_str())==0)
return true;
return false;
}
void Assert(bool val, string s)
{
if (!val) //检查失败,关闭程序
{
cout << "AssertionFailed: " << s << endl;
exit(-1);
}
}

class Graphm :public Graph
{
private:
int numVertex,numEdge; //顶点数和边数
bool undirected; // true表示无向图 false表示有向图
string vexs[MAX_VERTEX_NUM]; //存储顶点信息
intmatrix; //指向邻接矩阵matrix
int*mark; //指向mark数组
public:
Graphm(int numVert) //构造函数
{
Init(numVert);
}
~Graphm() //析构函数
{
delete []mark; //回收动态分配内存
for (int i=0; i<numVertex; i++)
delete []matrix[i];
delete []matrix;
}
void Init(int n) //初始化图
{
int i;
numVertex =n;
numEdge = 0;
mark = new int[n]; //初始化mark数组
for (i=0;i<numVertex; i++)
mark[i] =UNVISITED;
matrix =(int
) new int*[numVertex]; //初始化邻接矩阵
for (i=0;i<numVertex; i++)
matrix[i]= new int[numVertex];
for (i=0;i< numVertex; i++) //初始化权值为0
for (int j=0; j<numVertex; j++)
matrix[i][j] = 0;
}
int n() {return numVertex; } //返回节点数
int e() {return numEdge; } //返回边数
int first(int v) //返回v的第一个邻居
{
for (int i=0; i<numVertex; i++)
if(matrix[v][i] != 0)
return i;
return numVertex; //如果没有邻居返回节点数
}
int next(int v, int w)
{
for(int i=w+1; i<numVertex; i++)
if(matrix[v][i] != 0)
return i;
return numVertex; //如果没有邻居返回节点数
}
void setType(bool flag) //设置图的类型(有向图或无向图)
{
undirected=flag;
}
bool getType() //获取图的类型
{
return undirected;
}
/返回顶点在图中的位置/
int locateVex(string u) //Comp模板函数写在book.h中
{
for(int i=0;i<numVertex;i++)
{
if(Comp(u,vexs[i]))
return i;
}
return-1;
}
/**返回某个顶点的值(实际信息) **/
string getVex(int v)
{
return vexs[v];
}
/给某个顶点赋值/
void putVex(int v,int value)
{
vexs[v]=value;
}
void setEdge(int v1, int v2, int wt) //设置边(v1,v2)的权值为wt
{
if(matrix[v1][v2] == 0)
numEdge++;
matrix[v1][v2] = wt;
if(undirected)
{
matrix[v2][v1] = wt;
}
}
void delEdge(int v1, int v2) //删除边(v1,v2)
{
if(matrix[v1][v2] != 0)
{
numEdge–;
matrix[v1][v2] = 0;
if(undirected)
{
matrix[v2][v1] = 0;
}
}
}
bool isEdge(int i, int j) //判断边(i,j)是否为图中的一条边
{
return matrix[i][j] != 0;
}
int weight(int v1, int v2)
{
return matrix[v1][v2];
}
int getMark(int v)
{
return mark[v];
}
void setMark(int v, int val)
{
mark[v] = val;
}
void unMark()
{
for(int i=0;i<numVertex;i++)
mark[i]=0;
}
};

#endif
main.c
#include <bits/stdc++.h>
#include “graphm.h”
using namespace std;
int minVertex(Graphm& mymap, int *D) //找最小路径代价节点
{
int j,v=-1; //初始化V为某些未访问过的节点
for (j=0;j<mymap.n(); j++)
if(mymap.getMark(j) == UNVISITED)
{
v = j;
break;
}
for (j++;j<mymap.n(); j++) //找到最小D值
if((mymap.getMark(j) == UNVISITED) && (D[j]<D[v]))
v = j;
return v;
}
void Dijkstra(Graphm& mymap,int D,int s)
{
int i, v, w;
for (i=0;i<mymap.n(); i++) //处理节点
{
v=minVertex(mymap,D);
if (D[v] 7777) return; //不可达节点
mymap.setMark(v, VISITED);
for (w=mymap.first(v); w<mymap.n(); w =mymap.next(v,w))
if (D[w]> (D[v] + mymap.weight(v, w)))
D[w] =D[v] + mymap.weight(v, w);
}
}
int main()
{
int n,m;
cin>>n>>m;
Graphm mymap(n);
mymap.setType(true);
int D[n];
for(int i=0;i<n;i++)
{
mymap.putVex(i,i+1);
}
for(int i=0;i<m;i++)
{
int t,x,y,z;
cin>>t>>x>>y>>z;
if(t
1) z=z
z;
mymap.setEdge(x-1,y-1,z);
}
memset(D,7777,sizeof(D));
D[0] =0;
Dijkstra(mymap,D,0);
cout<<D[n-1]<<endl;
return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值