旅游咨询系统的寻路算法(数据结构实验)

旅游资讯系统的寻路算法(数据结构实验)

问题描述

设计与实现南普陀、胡里山炮台、曾厝埯和厦门大学思明校区主要景点(如上弦场、芙蓉湖等),还有翔安理工大学的旅游咨询系统,为游客提供游程最短的最优决策方案。

需求分析

对问题进行抽象:在一张给定的连通分支为一的每条边含有正权值的无向图中,选择若干个点,要求寻找出包含这若干个点且权值和最小的通路。

算法设计

在我看来实际上这个问题也是对图的遍历,只是遍历结束的条件由经过所有点变成了经过所有选中的点,所以相对于正常的图的遍历,我们只需要把结束的判定条件改一下就可以了。

bool judge(int des[],int n)//判断是否走完了全部目标 
{
	bool *flags=new bool[n_v];
	memset(flags, false,n_v);//初始化
	for(int i=0;rec[i]!=-1;i++)
		flags[rec[i]]=true;
    for(int i=0;i<n;i++)
    	if(flags[des[i]]==false)
    	    return false;
	return true;
}

修改过的检验代码如上,大致就是判断des(destination记录目标点的数组)里面的元素是不是都被记录在rec(走过点的数组)里,也即是否每个目标点都被访问过了,是则返回true,否则返回false。

而确定了这个问题本质上依旧是对图的遍历之后,之前图的遍历里面用到的深度优先搜索(dfs)和广度优先搜索(bfs)也就可以照搬过来,我这里选择的是dfs,不过理论上bfs应该也是可行的。

void find_way(int des[],int n,int x,int cou=0)//n是目标个数,x是起点,cou是已经走过点的数量 
{
	if(judge(des,n))//如果达成一种结果 
	{
		int sum=0;//用于记录该结果的总长度 
		for(int i=1;rec[i]!=-1;i++)
		    sum+=a[rec[i-1]][rec[i]];
		if(sum<len)
		{
			cout<<"恭喜!找到了一条路:";
			len=sum;
			for(int i=0;rec[i]!=-1;i++)
			{
			    res[i]=rec[i];//储存当前最优解 
			    cout<<res[i]+1<<' ';
		    }
		    cout<<"这条路的长度是:"<<len<<endl;
	    }
	    return;//返回
	}
	
	
	for(int y=0;y<n_v;y++)
	{
		if(a[x][y]>0 && times[x][y]<2)//如果可以走 
		    {
		    	//cout<<"One step forward"<<y+1<<endl;
		    	times[x][y]++;
		    	times[y][x]++;
		    	rec[cou]=x;
		    	cou++;//留痕 
		    	
		    	find_way(des,n,y,cou);//递归
		    	
		    	times[x][y]--;
		    	times[y][x]--;
                rec[cou]=-1;
                cou--;//恢复现场
                //cout<<"One step back"<<x+1<<endl;
			}
	}
}

以上是修改过的dfs代码,细心的读者不难发现,代码的整体框架和dfs是一样的(事实上我就是用下面的dfs代码修改写出的find_way)。

void dfs(int x,int y=0)
{
	if(x==n_v)//如果达成一种结果,不过在这里是多余的
    {
    	return;//返回
	}

	//cout<<x+1<<' ';
	for(;y<n_v;y++)
	{
		if(a[x][y]==1 && !check[y])//如果可以走
		    {
		        check[x]=true;//留下痕迹
		    	dfs(y,0);//递归
		    	//这里应该要恢复现场,不过这个dfs只遍历一次,所以不是必须的
			}
	}
}

可以看到,二者代码实际上有着很高的对应程度,所以说dfs是本算法的核心思想。不过不同的点也有:

(1)因为要进行多次遍历而不是找到一组解就可以结束,所以这里使用了rec数组去动态记录目前走过的路的records,而当找到一条满足的且更短的通路时就把它转移到res数组里面作为result存起来,同时把总长度存到全局变量len(gth)里面😃。(不会吧,不会有人不认识英语单词吧)

(2)第二点也是最重要的一点,就是修改了判断下一步能不能走的条件。原来dfs是只要一个点被走过一次就不能再走第二次了,但是在这道题里可能存在多次经过同一顶点的情况(自行脑补),而边也是,一条边也可以被走超过一次(自行脑补),这么看来是不是似乎没有什么规律可循,感觉这道题目写不出来决定摆烂了呢😭。

我本来也是这么想的,但是已经躺平在床上的我灵光乍现想到对于最短的路径一条边最多只能经过两次,所以引入了times数组用来记录次数,所以判定一条边能不能走的条件也相应地变成了a[x][y]>0 && times[x][y]<2,然后在走过一条边和回溯的时候对相应边的次数上加减一就可以了。而结果证明我的猜想应该是正确的,但是如何证明呢?

乍看似乎毫无头绪,但其实这个问题可以描述为:在一每条边都存在反平行边且无平行边的连通分支为一的有向图中,选定若干个顶点一定存在一条经过所有选中点的简单通路。到这里我只能说学过离散数学的一目了然😄,没学过离散数学的解释了也白解释,诸君请自便😜。

全部代码

#include <bits/stdc++.h> 
using namespace std;
#define N 20
int a[N][N],times[N][N];//a是临接矩阵,times用于记录每条边被走过的次数  
int len=1000000,rec[N*N],res[N*N];//len用于记录总路长,rec用于记录走过点的顺序 ,res用于记录可行的结果 
int n_v=0,n_e=0;//分别是点数和边数 

void display()//展示矩阵 
{
	for(int i=0;i<n_v;i++)
	{
		for(int j=0;j<n_v;j++)
		{
			if(a[i][j]>0)
			    cout<<a[i][j]<<' ';
			else if(a[i][j]==-1)
			    cout<<"* ";
		}
		cout<<endl;
	}
}

bool judge(int des[],int n)//判断是否走完了全部目标 
{
	bool *flags=new bool[n_v];
	memset(flags, false,n_v);//初始化
	for(int i=0;rec[i]!=-1;i++)
		flags[rec[i]]=true;
    for(int i=0;i<n;i++)
    	if(flags[des[i]]==false)
    	    return false;
	return true;
}

void find_way(int des[],int n,int x,int cou=0)//n是目标个数,x是起点,cou是已经走过点的数量 
{
	if(judge(des,n))//如果达成一种结果 
	{
		int sum=0;//用于记录该结果的总长度 
		for(int i=1;rec[i]!=-1;i++)
		    sum+=a[rec[i-1]][rec[i]];
		if(sum<len)
		{
			cout<<"恭喜!找到了一条路:";
			len=sum;
			for(int i=0;rec[i]!=-1;i++)
			{
			    res[i]=rec[i];//储存当前最优解 
			    cout<<res[i]+1<<' ';
		    }
		    cout<<"这条路的长度是:"<<len<<endl;
	    }
	    return;
	}
	
	
	for(int y=0;y<n_v;y++)
	{
		if(a[x][y]>0 && times[x][y]<2)//如果可以走 
		    {
		    	//cout<<"One step forward"<<y+1<<endl;
		    	times[x][y]++;
		    	times[y][x]++;
		    	rec[cou]=x;
		    	cou++;//留痕 
		    	
		    	find_way(des,n,y,cou);
		    	
		    	times[x][y]--;
		    	times[y][x]--;
                rec[cou]=-1;
                cou--;//恢复现场
                //cout<<"One step back"<<x+1<<endl;
			}
	}
}

int main()
{
	cout<<"请输入节点个数(20以内):";
	cin>>n_v;
	cout<<"请输入关系个数:";
	cin>>n_e;
	for(int i=0;i<N*N;i++)
	rec[i]=res[i]=-1;
	
	for(int i=0;i<n_v;i++)//初始化 
	{
	    for(int j=0;j<n_v;j++)
	    {
	        a[i][j]=-1;
	        times[i][j]=0;
	    }
	}
	
	cout<<"请成对输入二元关系以及权值:"<<endl; 
	for(int i=0;i<n_e;i++)
	{
		int t1,t2,weight;
		cin>>t1>>t2>>weight;	
		a[t1-1][t2-1]=a[t2-1][t1-1]=weight;
	}
	display(); 
	cout<<"请输入起点"<<endl;
	int ori;
	cin>>ori;
	ori--; 
	cout<<"请输入要经历的点的个数"<<endl;
	int n;
	cin>>n; 
	cout<<"请输入要经历的点的序号"<<endl;
	int *des=new int[n];
	for(int i=0;i<n;i++)
	{
		cin>>des[i];
		des[i]--;
	}
	
	cout<<"查询中"<<endl;
	find_way(des,n,ori);
	cout<<"最短的路程是:"<<endl;
	for(int i=0;res[i]!=-1;i++)
	cout<<res[i]+1<<' ';
	cout<<"最短的路程为:"<<len<<endl;
	return 0;
}

/*
9
9
1 2 1
2 3 1
3 4 1
4 5 1
5 6 1
6 2 1
6 7 2
6 8 3
8 9 4
2
4
1 4 7 9
*/

测试分析

附在代码末端的测试数据得到的结果如下,经检验是正确的。不过将步数一步步打印出来(代码中标为注释的两个cout)会发现实际上存在着不少的至少在我看来无意义的尝试,所以应该有一定的剪枝优化空间。不过期末临近,学业繁忙,就交由诸为聪明的读者去研究了😆。
在这里插入图片描述
(之前的代码我是用devcpp写的,好像有一点小错误,用vs跑的话会有问题,现在改了一下就好了)

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值