(1小时数据结构)数据结构c++描述(十三)--- 队列应用(电路布线/ 迷宫最短路径)代码详细解析

首先有关队列的定义在上节中已经定义:链表描述

本章的应用使用的是队列的链表描述,若大家不想使用自己定义的,可以将代码使用STL中的queue来代替。

电路布线/迷宫最短路径

           如图,我们想从a到b,要如何到达b呢,产生一个最短路径就是今天教大家的这个用法,方法采用了:深度优先探索法,与回溯法。

     听着方法很高端,其实很简单的.我们在初始位置的上,下,左,右,分别加1,遇到黑色的障碍不加,有数的不加,然后从下个位置开使重复的动作,知道遇到终点,就是最短路径了,这就是深度优先探索法。

     找到终点后,从最后的位置,往回找数,依次找比上个数小1的数,让后把路径记录下来,这就是回溯法。

     是不是非常简单呢,话不多说开始上代码吧:

最短路径FindPath :

/*
数组结构中的 队列应用  电路布线

对应书中代码:数据结构算法与应用c++描述

程序编写:比卡丘不皮

编写时间: 2020年7月16日 13:37:58
*/
/***
说明,书中说要定义Position class, 为了方便我才用stl 中的东西代替。
队列使用的是书中编写的程序。
主要对应的应用
***/
#pragma once
#include <iostream>
#include "LinkedQueue.h"
#include <vector>
#include <utility>
/*
start : 格子的起始位置
finish: 要到达的位置
length: 走的长度
path:  走的路径点
Mapin:  加入地图
*/
bool FindPath(pair<int, int>& start, pair<int, int>& finish,
	int & length, vector<pair<int,int> >& path, vector<vector<int> > & Mapin)
{
	if (Mapin.empty())
	{
		cout << "地图为空" << endl;
		return false;
	}
	//判断开始与到达的位置是不是同一位置
	if (start.first == finish.first && start.second == finish.second)
	{
		length = 0;
		return true;
	}

	//初始化迷宫外面的障碍墙,这是为了处理边界位置时比较方便
	int ncol = Mapin[0].size();//获取列数
	vector<int> vtemp(ncol +2,1);
	int nrow = Mapin.size();//获取行数
	vector<vector <int> > newMap(nrow + 2, vtemp);
	//把迷宫重行建造 加障碍
	for (int i =1; i <nrow; i++)
	{
		for (int j =1; j < ncol; j++)
		{
			newMap[i][j] = Mapin[i - 1][j - 1];
		}
	}

	//方向设置
	vector<pair<int, int> > offset;
	offset.push_back(make_pair(0,1)); //第一种写法
	//offset[1] = make_pair(1,0); //第二种写法
	offset.push_back(make_pair(1, 0));
	offset.push_back(make_pair(0, -1));
	offset.push_back(make_pair(-1, 0));

	int NumOfNbrs = 4; //一个网格位置的相邻位置数
	pair<int, int> here = start;
	//1表示障碍,0表示可以通过,大于1表示相距起始点的距离(0+2)
	newMap[here.first][here.second] = 2; //封锁
	//标记可到达的网格位置
	LinkedQueue<pair<int, int> > Q;
	pair<int, int> nbr;
	do 
	{
		for (int i = 0; i<NumOfNbrs; i++)
		{
			nbr.first = here.first + offset[i].first;
			nbr.second = here.second + offset[i].second;
			if (newMap[nbr.first][nbr.second] == 0)
			{
				newMap[nbr.first][nbr.second] = newMap[here.first][here.second] + 1;
				if (nbr.first == finish.first && nbr.second == finish.second)
				{
					break;
				}
				Q.Add(nbr);
			}
		}

		if (nbr.first == finish.first && nbr.second == finish.second)
		{
			break;
		}

		if (Q.IsEmpty())
		{
			return false;
		}
		Q.Delete(here); //队列删除头,并把数据给here

	} while (true);
	
	length = newMap[finish.first][finish.second] - 2; //获得的长度
	//回溯追踪最短路径经过的点
	path.clear();
	path.resize(length);
	here = finish; //从终点追踪
	for (int j = length-1; j>=0; j--)
	{
		path[j] = here;
		for (int i = 0; i<NumOfNbrs; i++)
		{
			nbr.first = here.first + offset[i].first;
			nbr.second = here.second + offset[i].second;

			if (newMap[nbr.first][nbr.second] == j + 2) //下个位置与here位置差1
			{
				break; //找到回去的位置
			}
			
		}
		here = nbr;
	 }  //一直找到原来位置,path 记录行走的路径
	return true;

}

其中#include "LinkedQueue.h" 为上一章记录的代码,连接在开头。

测试函数:

void FindPathQueue()
{
	//地图与书中的一样 1代表障碍物,0代表可走路径
	vector<vector<int> > mapIn{ { 0,0,1,0,0,0,0 },
	                            { 0,0,1,1,0,0,0 },
				    { 0,0,0,0,1,0,0 },
				    { 0,0,0,1,1,0,0 },
				    { 1,0,0,0,1,0,0 },
				    { 1,1,1,0,0,0,0 },
				    { 1,1,1,0,0,0,0 }
	};
	pair<int, int> start(3, 2);//代表第3行第2列,新图中会对应书中的位置
	pair<int, int> finish(4, 6);
	vector<pair<int, int> > path;
	int length = -1; //路径长度
	if (FindPath(start,finish,length, path, mapIn)) //如果找到路径
	{
		cout << "路径长度为:" << length << endl;
		for (auto pa : path)
		{
			cout << pa.first << " " << pa.second << endl;
		}
	}
	else
	{
		cout << "没有找到路径" << endl;
	}
}

输出结果:

代码详细解析:

if (Mapin.empty())
	{
		cout << "地图为空" << endl;
		return false;
	}

这里是用来判断加载的地图是不是空的,如果是空的话,输出地图为空,返回false

//判断开始与到达的位置是不是同一位置
	if (start.first == finish.first && start.second == finish.second)
	{
		length = 0;
		return true;
	}

这里判断的是初始位置与最终的位置是不是同一个位置,如果是就返回长度为0;

//初始化迷宫外面的障碍墙,这是为了处理边界位置时比较方便
	int ncol = Mapin[0].size();//获取列数
	vector<int> vtemp(ncol +2,1);
	int nrow = Mapin.size();//获取行数
	vector<vector <int> > newMap(nrow + 2, vtemp);
	//把迷宫重行建造 加障碍
	for (int i =1; i <nrow; i++)
	{
		for (int j =1; j < ncol; j++)
		{
			newMap[i][j] = Mapin[i - 1][j - 1];
		}
	}

       这段代码是创建一个新的地图,就是让输入的地图四周变1,相当于障碍物,为了后面寻找方便,这里说明一下,地图中0,代表可以走的格子,1代表障碍物. 大于1的数为记录的数据。

于是地图变成这个样子:

     

其中的黑色就是1的代表空白为0;

	//方向设置
	vector<pair<int, int> > offset;
	offset.push_back(make_pair(0,1)); //第一种写法
	//offset[1] = make_pair(1,0); //第二种写法
	offset.push_back(make_pair(1, 0));
	offset.push_back(make_pair(0, -1));
	offset.push_back(make_pair(-1, 0));

设置方向,采用了右,下,左,上的4个顺序,为后面探索与回溯需要用。

int NumOfNbrs = 4; //一个网格位置的相邻位置数
	pair<int, int> here = start;
	//1表示障碍,0表示可以通过,大于1表示相距起始点的距离(0+2)
	newMap[here.first][here.second] = 2; //封锁
	//标记可到达的网格位置
	LinkedQueue<pair<int, int> > Q;
	pair<int, int> nbr;
	do 
	{
		for (int i = 0; i<NumOfNbrs; i++)
		{
			nbr.first = here.first + offset[i].first;
			nbr.second = here.second + offset[i].second;
			if (newMap[nbr.first][nbr.second] == 0)
			{
				newMap[nbr.first][nbr.second] = newMap[here.first][here.second] + 1;
				if (nbr.first == finish.first && nbr.second == finish.second)
				{
					break;
				}
				Q.Add(nbr);
			}
		}

		if (nbr.first == finish.first && nbr.second == finish.second)
		{
			break;
		}

		if (Q.IsEmpty())
		{
			return false;
		}
		Q.Delete(here); //队列删除头,并把数据给here

	} while (true);

这里一部分就是探索法了,NumOfNbrs  为4 代表方向数,这里我们让初始位置的值变成 2,Q是记录探索的记录。

nbr 记录了4个周边的数据位置,然后判断当前周围情况,当为0的时候,这个位置是上个位置 + 1的值,若是1不变,如果是终点就跳出循环,表示找到了。

如下图:

循环一次的do语句对应的图:

在算的同时,把初始点的右,下,左,上的位置放进队列中了:Q.Add(nbr);

然后开始从队列取出位置,并且赋值给here。Q.Delete(here);

这个时候here代表的是开始位置的右边的位置,然后开始查找它的四周然后赋值:

同样加入到刚才的队列中间去,然后在取队列的数,这时候为A 的下边的数,然后操作:

 

以此类推最终找到终点位置:

 找到后,我们要采用回溯法,来找到那条路径:


	length = newMap[finish.first][finish.second] - 2; //获得的长度
	//回溯追踪最短路径经过的点
	path.clear();
	path.resize(length);
	here = finish; //从终点追踪

首先我们要找到最短的长度,让结果值 - 2,就是最短路径的长度了(不算初始位置,算结束位置)如上图 11 - 2 = 9,

9就是最短路径的长度了。然后path 是为记录路径的过程点。 这里here记录了finish位置。

	for (int j = length-1; j>=0; j--)
	{
		path[j] = here;
		for (int i = 0; i<NumOfNbrs; i++)
		{
			nbr.first = here.first + offset[i].first;
			nbr.second = here.second + offset[i].second;

			if (newMap[nbr.first][nbr.second] == j + 2) //下个位置与here位置差1
			{
				break; //找到回去的位置
			}
			
		}
		here = nbr;
	 }  //一直找到原来位置,path 记录行走的路径
	return true;

回溯就非常简单了,还是寻找右,下,左,上的位置,寻找比本身值少1的位置,就想11,下个就是找10的值,找到一个添加到对应位置path中,一直找到起始位置。这样就记录路径的全部的点了。于是就是:

 

是不是非常的简单,喜欢我的博客,可以关注一下,需要源码可以私信我,一起加油吧。

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值