数据结构实验6图的应用-行车路线问题

需求分析

0.问题描述

小明和小芳出去乡村玩,小明负责开车,小芳来导航。
  小芳将可能的道路分为大道和小道。大道比较好走,每走1公里小明会增加1的疲劳度。小道不好走,如果连续走小道,小明的疲劳值会快速增加,连续走s公里小明会增加s^2的疲劳度。
  例如:有5个路口,1号路口到2号路口为小道,2号路口到3号路口为小道,3号路口到4号路口为大道,4号路口到5号路口为小道,相邻路口之间的距离都是2公里。如果小明从1号路口到5号路口,则总疲劳值为〖(2+2)〗2+2+22=16+2+4=22。
  现在小芳拿到了地图,请帮助她规划一个开车的路线,使得按这个路线开车小明的疲劳度最小。

1.问题分析

要实现的功能:
1.通过键盘输入路口数量、道路数量以及道路信息(类型、长度、位置);
2.设计算法找到一条路线使疲劳值最小;
3.通过屏幕输出疲劳值。
疲劳值计算方法:
1).大路:疲劳值=公里数;
2).小路:疲劳值=连续的公里数的平方。

2.输入数据

输入的第一行包含两个整数n, m,分别表示路口的数量和道路的数量。路口由1至n 编号,小明需要开车从1号路口到n号路口。

接下来m行描述道路,每行包含四个整数t, a, b, c,表示一条类型为t,连接a与b 两个路口,长度为c公里的双向道路。其中t为0表示大道,t为1表示小道。保证1 号路口和n号路口是连通的。

2 ≤ n ≤ 8,1 ≤ m ≤ 10,1 ≤ a, b ≤ n,t是0或1,c ≤10^5 保证答案不超过 10^6。

3.输出数据

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

4.测试样例设计

样例一:不存在小道
输入:5 7
0 1 2 10
0 1 4 20
0 2 4 5
0 1 3 3
0 2 3 2
0 3 5 15
0 4 5 11
输出:18
样例二:所有的小道不相交
输入:6 7
0 1 2 4
1 2 3 2
0 1 4 2
1 1 5 3
0 4 5 2
0 5 6 10
0 3 6 8
输出:14
样例三:有多条连续的小道相连
输入:6 7
1 1 2 3
1 2 3 2
0 1 3 30
0 3 4 20
0 4 5 30
1 3 5 6
1 5 6 1
输出:76
样例四:边界条件最小值
输入:2 1
0 1 2 15
输出:15
样例五:全为小路
输入:5 7
1 1 2 10
1 1 4 20
1 2 4 5
1 1 3 3
1 2 3 2
1 3 5 15
1 4 5 11
输出:324

二、概要设计

1.抽象数据类型

每一个路口相当于一个顶点,每一条道路相当于一条边,每个路口可以和多个其他路口 关联成双向道路,因此符合网状逻辑结构的特征,故可以利用图(无向图)这种数据结 构。

数据对象:所有的路口以及道路
数据关系:所有路口之间都可以以不同的双向道路相连从而构成的带权无向图
基本操作:
1.准备能储存这组数据的空间
2.设置边的权重
3.获得边的权重
ADT Graph{
数据对象D:
顶点集V={vi|vi∈整数,i=1,2,… ,n,n∈整数}
弧集:E={e_ij| i,j∈V}
Graph=(V,E)
数据关系R:
VR={<v,w>| v,w∈V且P(v,w)∈E}
基本操作:
Graphm(int v); // 构造函数
~Graphm();//析构函数
void setEdge(int v1, int v2, long long wght);// 为边(v1,v2)设置权值为wght
long long weight(int v1, int v2);// 返回边(v1,v2)的权值
}

2.算法的基本思想

1.用两个图将大路和小路分别储存下来;
2.用floyd算法处理小路构成的图,找到只走小路时每两个路口之间的最短距离;
3.通过spfa算法记录大路和小路到达i点时的最小疲劳值;
4.如果到达i点的路是大路,那么它可以由大路转移过来,也可以由小路转移过来;
5.如果到达i点的路是小路,就只能由大路转移过来;
6.最终大路和小路到达i点最小疲劳值中的最小值就是所有路线中的最小疲劳值。
3.程序的流程
1.初始化模块:初始化两个图
2.归并模块:用FLoyd算法将走小路进行归并
3.spfa处理模块:用spfa算法分别记录大路和小路到达每个顶点时的最小疲劳值
4.比较模块:找到大路和小路到达终点最小疲劳值中的最小值,并通过屏幕显示结果

三、详细设计

1.物理数据类型

物理数据类型:顶点信息还有边权的数据类型都为整形int。
物理数据结构:因为要频繁的访问两个顶点间(边)的信息,所以用邻接矩阵(二维数 组)实现图,在图信息的存储和图遍历及图信息的读取上会带来很大的方便。

#define inf 0x3f3f3f3f
Graphm(int v) // 构造函数 
{
	int i, j;
	numVertex = v;
	numEdge = 0;
	mark = new int[v];
	for (i=0; i<numVertex; i++)
		mark[i] = UNVISIT;
	undirected = true;//初始化为无向图 
	//初始化邻接矩阵 
	matrix = (long long**) new long long*[numVertex];
	for (i=0; i<numVertex; i++)
	{
		matrix[i] = new long long[numVertex];
	} 
	for (i=0; i<numVertex; i++)
		for (j=0; j<numVertex; j++)
			matrix[i][j] = inf;//初始化权值为无穷大 
}

~Graphm()//析构函数
{
	delete []mark; //回收动态分配内存
    for (int i=0; i<numVertex; i++)
      		delete []matrix[i];
   	delete []matrix;
}
void setEdge(int v1, int v2, long long wt)// 为边(v1,v2)设置权值为wt
{
	if(matrix[v1][v2] == inf)
		numEdge++;
   	matrix[v1][v2] = wt;

   if(undirected)//无向图
        matrix[v2][v1] = wt;
}
long long weight(int v1, int v2)// 返回边(v1,v2)的权值
{
	return matrix[v1][v2];
}

2.输入和输出的格式

输入格式:
输入的第一行包含两个整数n, m,分别表示路口的数量和道路的数量。路口 由 1至n编号,小明需要开车从1号路口到n号路口。
  接下来m行描述道路,每行包含四个整数t, a, b, c,表示一条类型为t,连接a与b 两个路口,长度为c公里的双向道路。其中t为0表示大道,t为1表示小道。保证1 号路口和n号路口是连通的。

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

3.算法的具体步骤

初始化模块:

                       void creat( )
                       {
//n为路口数,m为道路数	cin >> n >> m;   
//创建n+1个顶点的图     Graphm *Gx = new Graphm(n+1);//储存小路构成的图 
 使图的顶点i为路口i 	Graphm *Gd = new Graphm(n+1);//储存大路构成的图 
//初始化图为无向图
	                    for (int i=0; i<m; i++){
//输入路口信息和道路	       int a, b, c, d;
 信息               	   cin >> a >> b >> c >> d;
	     	               if (a==1 && Gx->weight(b,c) > d) {
//给储存小路的图的边		      Gx->setEdge(b,c,d); 
 赋权值					      Gx->setEdge(c,b,d); 
		                     }
		                  else if (a==0 && Gd->weight(b,c) > d){
//给储存大路的图的边			  Gd->setEdge(b,c,d); 
 赋权值				          Gd->setEdge(c,b,d); 
		                     } 
	                      }   
                        }

归并模块:

                   void floyd(Graphm **G)//对路预处理
                   {
//遍历整个邻接矩阵    for(int k=1;k<=n;k++)
	                  for(int i=1;i<=n;i++)
	                   for(int j=1;j<=n;j++){
//如果i到j的边的权重大于if( (*G)->weight(i,j) > (*G)->weight(i,k) + (*G)->weight(k,j)
边ik的权重加边kj的权重    &&(*G)->weight(i,k)!=inf&&(*G)->weight(k,j)!=inf )  {
//令边ij权重等于边ik的权   (*G)->setEdge(i,j, (*G)->weight(i,k) + (*G)->weight(k,j));
 重加边kj的权重		       }
	                    }
                    }

spfa处理模块:

//dis1[i]和dis[2]分别来储存大    void SPFA( ){
路和小路到达i的最小疲劳值      long long dis1[100],dis2[100];
//vis[i]标记i是否在队列中         bool jl[100],vis[100];
//将dis1和dis2数组初值设	        memset(dis1,inf,sizeof(dis1));
 为无穷大	                    memset(dis2,inf,sizeof(dis2));

//队列q用于保存待优化结点	    queue<int>q;
//将dis[源点]设为0 	            dis1[1]=dis2[1]=0;
								q.push(1); vis[1]=1;
//实现spfa算法	                while(!q.empty()){
								int now=q.front();
								q.pop();
								vis[now]=0;
								for(int i=1;i<=n;i++){
								long long v = Gd->weight(now,i);
//大路转移到大路		              if(dis1[i]>dis1[now]+v){
//松弛dis1[i]							dis1[i] = dis1[now] + v;
									if(vis[i]) continue;
									vis[i] = 1;
									q.push(i);
								 }
//小路转移到大路		              if(dis1[i] > dis2[now] + v){
//松弛dis1[i]			                dis1[i] = dis2[now] + v;
				                    if(vis[i]) continue;
				                    vis[i] = 1;
				                    q.push(i);
			                      }
			                   if(Gx->weight(now,i) < 1e10){
//计算小路的疲劳值			     v = Gx->weight(now,i) * Gx->weight(now,i);
//大路转移到小路                	 if(dis2[i] > dis1[now] + v) 
//松弛dis2[i]		  				{	dis2[i] = dis1[now] + v;
									if (vis[i]) continue;
									vis[i] = 1;
									q.push(i);         }
							  }
							}}
						}

比较模块:

//获取最小疲劳值               long long getResult( )
//大路和小路到达i点最小疲劳值 {    return min(dis1[n],dis2[n]);   }
中的最小值就是所有路线中的最小疲劳值。

4.算法的时空分析

初始化模块
图的顶点和边权值的输入时空复杂度都为O(n)
除此之外对每个图信息输入的操作的时空复杂度都为O(1)
图边权值的设置时空复杂度为O(1)
spfa处理模块
图边权获取时空复杂度为O(1)
由于每个顶点都要入队和出队,所以时间复杂度是O(n)
比较模块: 调用函数输出时空复杂度为 O(1)

四、调试分析

1.调试方案设计

调试目的:查看出错的地方在哪里
样例:6 7
1 1 2 3
1 2 3 2
0 1 3 30
0 3 4 20
0 4 5 30
1 3 5 6
1 5 6 1
调试计划:
1)设置断点
在这里插入图片描述
在这里插入图片描述
2)添加查看
在这里插入图片描述
3)增加cout
在这里插入图片描述
4)编译调试

2.调试过程和结果,及分析

调试结果:
在这里插入图片描述
在这里插入图片描述
分析:调试结果显示指针越界,显示屏显示到for循环结束就不再输出了,所以可推断出是for循环里面越界了,检查后发现是获取设置图中边的时候没有将下标-1,最后给图扩容+1就可以运行成功了。

五、测试结果

样例一:
在这里插入图片描述
分析:从大道1走到大道3,再走到大道5, 疲劳值为3+15=18。
样例二:
在这里插入图片描述
分析:走1到4的大路、4到5的大路、5到6的大路,最终疲劳值是2+2+10=14。

样例三:
在这里插入图片描述
分析:从1走小道到2,再走小道到3,疲劳度为5^2=25;然后从3走大道经过4到达 5,疲劳度为20+30=50;最后从5走小道到6,疲劳度为1。总共为76。

样例四:
在这里插入图片描述

样例五:
在这里插入图片描述
结果正确

六、实验日志(选做)

2018-12-03:去计算机职业资格认证官网查看17年3次考试中的第四题,最终确认题目为行车路线。

2018-12-04:去csdn上搜索代码,寻找思路。

2018-12-05:开始写实验报告的需求分析及概要设计。

2018-12-08:用图的ADT改写代码,出现了一些问题:1.在用实验5写的图的ADT的时候,构造函数中没有对图的类型进行初始化,建图的时候我也没有设置图的类型,导致在设置边权时出现了一些未知错误。2.我是通过邻接矩阵来储存图,初始化顶点距离为无穷远,但是没改变类中边权的返回类型以及一些函数的参数类型,编译器报错。3.没注意下标起始为0,指针越界,编译后结果不能输出。

2018-12-09:完善预习报告的剩余部

附代码:

Graphm.h

#ifndef _GRAPHM_H
#define _GRAPHM_H 
#define inf 0x3f3f3f3f
#define VertexType int
#define M 100
#define UNVISIT 0
#define VISIT 1
class Graphm
{
	private:
		int numVertex, numEdge;
		bool undirected;//true表示无向图 
		long long **matrix;
		int *mark;	
		VertexType vexs[M];
	public:
		Graphm(int v); // 构造函数 
		
		~Graphm();//析构函数
		
		int n();//返回节点数
		
		int e();//返回边数
		
		int first(int v);// 返回v的第一个邻居
		
		int next(int v,int w);// 返回v的在w后的邻居
		
		void setType(bool flag);//设置图的类型
		
		bool getType(); //获取图的类型
		
		int locateVex(VertexType u);//找到(包含实际信息的)顶点在图中的位置
		
		VertexType getVex(int v);//返回某个顶点的值(实际信息)
		
		void putVex(int v,VertexType value);//给某个顶点赋值

  		void setEdge(int v1, int v2, long long wght);// 为边(v1,v2)设置权值

  		void delEdge(int v1, int v2);//删除边(v1,v2)

 		bool isEdge(int i, int j);// 判断边(i,j)是否在图中

		long long weight(int v1, int v2);// 返回边的权值

		int getMark(int v) ;//取得顶点的标志位

		void setMark(int v, int val);//设置顶点的标志位
};
#endif 

Graphm.cpp

#include "Graphm.h"
#include <iostream>
#include <string.h> 
using namespace std;

Graphm::Graphm(int v) // 构造函数 
{
	int i, j;
	numVertex = v;
	numEdge = 0;
	mark = new int[v];
	for (i=0; i<numVertex; i++)
		mark[i] = UNVISIT;
	undirected = true;//初始化为无向图 
	//初始化邻接矩阵 
	matrix = (long long**) new long long*[numVertex];
	for (i=0; i<numVertex; i++)
	{
		matrix[i] = new long long[numVertex];
	} 
	for (i=0; i<numVertex; i++)
		for (j=0; j<numVertex; j++)
			matrix[i][j] = inf;//初始化权值为无穷大 
}

Graphm::~Graphm()//析构函数
{
	delete []mark; //回收动态分配内存
    for (int i=0; i<numVertex; i++)
      		delete []matrix[i];
   	delete []matrix;
}

int Graphm::n()//返回节点数
{
	return numVertex;
}

int Graphm::e()//返回边数
{
	return numEdge;
} 

int Graphm::first(int v)// 返回v的第一个邻居
{
	for (int i=0; i<numVertex; i++)
	{
		if (matrix[v][i]!=inf) return i;
	}
	return numVertex;//如果没有邻居,返回节点数 
} 

int Graphm::next(int v,int w)// 返回v的在w后的邻居
{
	for (int i=w+1; i<numVertex; i++)
	{
		if (matrix[v][i]!=inf) return i;
	}
	return numVertex;//如果没有,返回节点数 
} 

void Graphm::setType(bool flag)//设置图的类型
{
	undirected = flag;
} 

bool Graphm::getType() //获取图的类型
{
	return undirected;
}
		
int Graphm::locateVex(VertexType u)//找到(包含实际信息的)顶点在图中的位置
{
	for (int i=0; i<numVertex; i++)
	{
		if (u == vexs[i])
		return i;
	}
	return -1;
}
		
VertexType Graphm::getVex(int v)//返回某个顶点的值(实际信息)
{
	return vexs[v];
}
		
void Graphm::putVex(int v,VertexType value)//给某个顶点赋值
{
	vexs[v]=value;
}

void Graphm::setEdge(int v1, int v2, long long wt)// 为边(v1,v2)设置权值
{
	if(matrix[v1][v2] == inf)
		numEdge++;
   	matrix[v1][v2] = wt;

   if(undirected)
        matrix[v2][v1] = wt;
}

void Graphm::delEdge(int v1, int v2)//删除边(v1,v2)
{
	if(matrix[v1][v2] != 0)
		numEdge++;
   	matrix[v1][v2] = 0;

   if(undirected)
        matrix[v2][v1] = 0;
}

bool Graphm::isEdge(int i, int j)// 判断边(i,j)是否在图中
{
	return matrix[i][j] != inf;
}

long long Graphm::weight(int v1, int v2)// 返回边的权值
{
	return matrix[v1][v2];
}

int Graphm::getMark(int v)//取得顶点的标志位
{
	 return mark[v];
}

void Graphm::setMark(int v, int val)//设置顶点的标志位
{
	 mark[v] = val;
}
		

main.cpp

#include "Graphm.h"
#include <iostream>
#include <cstring>
#include <string>
#include <queue>
#include <stack>
#include <vector>
#include <cmath>
using namespace std;
int n,m;	
void floyd(Graphm **G)//对路预处理
{
	for(int k=1;k<=n;k++)
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		if((*G)->weight(i,j)>(*G)->weight(i,k)+(*G)->weight(k,j)&&(*G)->weight(i,k)!=inf&&(*G)->weight(k,j)!=inf)
		{
			(*G)->setEdge(i,j, (*G)->weight(i,k) + (*G)->weight(k,j));
		}
	}
 } 
int main()
{
	cin >> n >> m;   
	//初始化图为无向图 
	Graphm *Gx = new Graphm(n+1);//储存小路构成的图 
	Graphm *Gd = new Graphm(n+1);//储存大路构成的图 
	for (int i=0; i<m; i++)
	{
		int a, b, c, d;
		cin >> a >> b >> c >> d;
		if (a==1 && Gx->weight(b,c) > d)
		{
			Gx->setEdge(b,c,d); 
		//	Gx->setEdge(c,b,d); 
		}
		else if (a==0 && Gd->weight(b,c) > d)
		{
			Gd->setEdge(b,c,d); 
			//Gd->setEdge(c,b,d); 
		} 
	}   
	floyd(&Gx);	 
	long long dis1[100],dis2[100];
	bool jl[100],vis[100];
	memset(dis1,inf,sizeof(dis1));
	memset(dis2,inf,sizeof(dis2));

	queue<int>q;
	dis1[1]=dis2[1]=0;
	q.push(1);
	vis[1]=1;
	while(!q.empty())
	{
		int now=q.front();
		q.pop();
		vis[now]=0;
		for(int i=1;i<=n;i++)
		{
			long long v = Gd->weight(now,i);
			if(dis1[i]>dis1[now]+v)//大路加大路
			{
				dis1[i] = dis1[now] + v;
				if(vis[i]) continue;
				vis[i] = 1;
				q.push(i);
			}
			if(dis1[i] > dis2[now] + v)//大路加小路
			{
				dis1[i] = dis2[now] + v;
				if(vis[i]) continue;
				vis[i] = 1;
				q.push(i);
			}
			if(Gx->weight(now,i) < 1e10)
			{
				v = Gx->weight(now,i) * Gx->weight(now,i);
				if(dis2[i] > dis1[now] + v)//小路加大路
				{
					dis2[i] = dis1[now] + v;
					if (vis[i]) continue;
					vis[i] = 1;
					q.push(i);
				}
			}
		}
	}
	cout  << min(dis1[n],dis2[n]) << endl;
	return 0;
} 
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值