Dijkstra算法和A*算法学习


前言

图搜索算法:图是由节点和边组成,分为有向图、无向图、权重图等,图搜索的基本流程是:维护一个容器,从容器中访问一个节点,扩展该节点的邻居节点,将扩展的新节点加入容器,结束循环的条件是容器为空,若图有回环,则新建一个容器,加入已经访问过的节点,之后不再访问该容器中的节点。


一、图遍历算法:BFS和DFS

假定每条边的权重一样

  • BFS广度优先搜索:使用的队列,先进先出
  • DFS深度优先搜索:使用的栈,后进先出

1.DFS

弹出树状结构中层级最深的节点
先找到初始节点加入堆栈,然后将初始节点弹出,查找周围相邻节点,将相邻节点按照一定的规则放入堆栈,弹出栈顶元素依次查找,直到找到目标节点或者容器为空。
在这里插入图片描述
以有向图为例进行分析:S是初始点,G位目标点,从S->G利用DFS找到一条路径。
首先初始化栈容器,S入栈,弹出S,访问S的子节点有d,e,p,按照p、e、d的顺序依次入栈,之后弹出层级最高的节点d,此时容器中的元素为e、p;扩展d的子节点b、c、e,按照e、c、b的顺序入栈,此时容器中元素为b c e p,弹出b,扩展b的子节点a,a入栈,弹出a,a没有子节点,弹出c,扩展c的子节点a,a入栈,弹出a,没有子节点,此时容器中剩下e p,弹出e,扩展e的子节点h r,按照r h 依次入栈,弹出h,扩展h的子节点p q,按照q,p依次入栈,此时容器内元素 q r p,弹出q,q没有子节点,弹出q,此时容器内r p,弹出r,扩展r的子节点f,入栈,栈内元素f p,弹出f,扩展f的子节点c G,此时栈内元素c G p,弹出c,扩展c,找到G。整个路径就是:S-d-e-r-f-G。在二维地图中,只走直线,不一定是最佳的路径,上图中可以看到S-e-r-f-G为最佳路径,而DFS寻路的结果是S-d-e-r-f-G。
实际应用中需要确定一个方向只能走一次,按照哪种探索方式进行探索,同时要将已经走过的路径进行标记,不能再走了。
准备工作:要有一张地图【1(不能走)、0(可以走)】,定义探索方向枚举类型(上、右、下、左 or 上 左 下 右 )、同时还需要辅助地图、回退的点结构。

//定义地图大小
#define COLS 8
#define ROWS 8
//探索方向枚举类型
enum direct
{
	p_up,p_right,p_down,p_left
};
//辅助地图
struct pathNode
{
	int val;        //地图中每个格子的值 0 ,1
	direct dir;    //试探方向
	bool isFind;    //是否走过
}
//回退点结构
struct MyPoint
{
	int row;
	int col;
}
int main()
{
	//构建地图
	int map[ROWS][COLS]={
		{1,1,1,1,1,1,1,1},
		{1,0,1,0,0,0,0,1},
		{1,0,1,0,1,0,1,1},
		{1,0,1,0,1,0,1,1},
		{1,0,1,0,1,0,0,1},
		{1,0,1,0,1,0,1,1},
		{1,0,0,0,1,0,0,1},
		{1,1,1,1,1,1,1,1}
	};
	//辅助地图
	pathNode pathMap[ROWS][COLS] = { 0};
	for(int i=0;i<ROWS;i++)
	{
		for(int j=0;j<COLS;j++)
		{
			pathMap[i][j].val = map[i][j];
		}
	}
	//定义起点、终点
	MyPoint beginPos;
	MyPoint endPos;
	cout << "请输入起点(x,y):";
	cin >> beginPos.col;
	cin >> beginPos.row;
	cout << "请输入终点(x,y):";
	cin >> endPos.col;
	cin >> endPos.row;
	//准备一个栈
	stack<MyPoint> st;
	//标记起点走过
	pathMap[beginPos.row][beginPos.col].isFind = true;
	//起点入栈
	st.push(beginPos);
//开始寻路:准备变量存储当前位置,准备试探点
	MyPoint currentPos = beginPos;
	//试探点
	MyPoint searchPos;
	//是否找到终点
	bool isFindEnd = false;
	while(1){
		searchPos = currentPos; //试探点从当前点开始探索
		switch(pathMap[currentPos.row][currentPos.col].dir)
		{
		//上
			case p_up:
				searchPos.row--;   //试探点更改
				//因为试探点能不能走都需要改变试探方向,所以就先改变试探方向
				pathMap[currentPos.row][currentPos.col].dir = p_right;
				//试探点开始试探,能不能走:能走(没有走过且没有障碍物)
				if(pathMap[searchPos.row][searchPos.col].isFind !=true && pathMap[searchPos.row][searchPos.col].val !=1)
				{
					//能走,更新当前点
					currentPos = searchPos;
					//已经走了,标记当前点已经走过
					pathMap[currentPos.row][currentPos.col].isFind = true;
					//当前点入栈
					st.push(currentPos);
				}
				else{
					//不能走,改变当前试探方向,前面已经改变啦
				}
				break;
			//同理,右下类似
			case p_right: ... break;
			case p_down: ... break;
			case p_left:
				searchPos.col--;
				//已经全部试探完毕。不需要改试探方向
				if (pathMap[searchPos.row][searchPos.col].isFind != true &&pathMap[searchPos.row][searchPos.col].val != 1)
				{
					//能走
					currentPos = searchPos;
					//因为已经走了,标记当前点已经走过
					pathMap[currentPos.row][currentPos.col].isFind = true;
					//当前点入栈
					st.push(currentPos);
				}
				else{
					//不能走 --- 回退
					//删除栈顶元素
					st.pop();
					//将栈顶元素作为当前点
					currentPos = st.top();
				}
				break;
		}
		if(currentPos.row == endPos.row && currentPos.col == endPos.col)
		{
			isFindEnd = true;
			break;   //找到终点,跳出循环
		}
		if(st.empty())
		{
			break;   //栈为空,跳出循环
		}
	}
	//打印路径
	if(isFindEnd)
	{
		while(!st.empty())
		{
			cout<<st.top().row <<","<<st.top().col<<endl;
			st.pop();
		}
	}
	return 0;
}

2.BFS

弹出树状结构中层级最浅的节点
先找到初始节点加入队列,然后将初始节点弹出,查找周围相邻节点,将相邻节点按照一定的规则放入队列,队头元素出队列,直到找到目标节点或者容器为空。
在这里插入图片描述
S是初始点,G位目标点,从S->G利用DFS找到一条路径。S进入队列,S出队列,S的子节点d e p, 依次进入队列,此时队列中元素p e d,弹出p,p的子节点q,进入队列(e d q),弹出e,e的子节点进入队列(d q r h),弹出d,d的子节点进入队列(q r h c b)弹出q,q没有子节点,弹出r, r的子节点进入队列(h c b f)弹出h,h的子节点走过,弹出c,子节点进入队列(b f a),弹出b ,b的子节点已在队列中,弹出f,子节点进入队列(a c G),弹出a ,子节点为空,弹出c,c的子节点a走过,弹出G找到终点。

二、启发式搜索

贪心策略,启发式搜索是一个猜测,启发式函数应该朝正确的扩展方向,且在搜索中是容易计算的。

1.Dijkstra

使用优先级队列存储元素,弹出代价g最小的节点。
初始化,加入起始节点Xs,代价为g(Xs) = 0,其它节点初始g(n) 为无穷;
若容器为空,返回false,搜索失败
从优先级队列open list中弹出g值最小的节点n,标记为已经扩展过,放入另外一个容器close list,判断n是否是目标节点(是—返回true,跳出循环),n不是目标节点,扩展n周围的没有扩展的节点m,若g(m)为无穷大,表示之前没有发现该节点,更新g(m) = g(n)+cost(n->m)当前n节点的代价加上n到m节点的代价,同时将m节点加入优先级队列;若g(m)有值不是无穷,g(m) >g(n)+cost(n->m),则更新g(m) = g(n)+cost(n->m)表示m节点在n节点前已经被其他节点发现,m节点已经在优先级队列中,但此时m的g值是其他节点到m的值,同时比n节点到m节点的g值大,就保留最小的g(m);循环下去,直到找到终点。
在这里插入图片描述
初始化优先级队列,S进入队列,g(S) =0;
S的节点d e p进入容器,S到相邻结节点的代价
g(d) = g(S)+ cost(S->d)=3,
g(e) = g(S)+ cost(S->e)=9,
g(p,)= g(S)+ cost(S->p)=1 ,
优先级队列按照g值进行排序(p d e),弹出容器中g值最小的元素p,扩展p,p的节点q,
g(q) = g(p,)+cost(p->q) = 16,
容器中元素(d e q)弹出d,扩展d,d的节点b c e,
g(b) = g(d)+cost(d->b) = 4,
g(c,) = g(d)+cost(d->c)=11,
g(e)=g(d)+cost(d->e)=5
更新g(e)此时容器中元素(b e c q),弹出b扩展b的节点a,
g(a)=g(b)+cost(b->a)=6,
容器元素更新(e a c q),弹出e扩展e的节点h r,
g(h)=g(e)+cost(e->h)=13,
g(r,)=g(e)+cost(e->r)=6,
容器元素更新(a r c h q)弹出a,扩展a没有子节点,弹出r,扩展r,r的子节点f,
g(f)=g(r,)+cost(r->f)=7,
容器元素(f c h q),弹出f扩展f节点c G,
g(c,) =g(f)+cost(f->c)=11,
g(G)=g(f)+cost(g->G) = 9
容器中元素(G c h q),找到终点。

2.A*

和Dijkstra算法类似,除了维护代价g以外,还维护了一个h值,按照f=g+h值进行优先级队列排序,h取0则会退化Dijkstra算法。
在这里插入图片描述
优先级队列初始化,S进入容器,
f(S)=g(S)+h(S)=6,
弹出S,S的子节点a 进入容器,
f(a)=g(a)+h(a)=g(S)+cost(S->a)+h(a)=6,
队列中元素a,弹出a,扩展a的子节点 b d e
f(b)=g(b)+h(b)=g(a)+cost(a->b)+h(b)=1+1+6=8;
f(d)=g(d)+h(d)=6;
f(e)=7,
按照f值较小的入队,容器中元素(d e b),弹出d,扩展d的节点G,f(G)=6,进入容器,元素更新(G e b),弹出G找到终点。
但是A*的结果不是最优的,要想保持最优性:估计的代价h要小于等于最终达到目标的真实代价h

权重A*:f= g+ah;a是权重。
a=0—退化为Dikjkstra; a=1—A*;a=无穷—忽略g,贪心

  • 对角线启发式函数:
    dx = abs(node.x-goal.x)
    dy = abs(node.y-goal.y)
    h = (dx+dy)+(sqrt(2)-2 )*min(dx,dy)

  • 打破对称性:
    h = h*(1+p),p<一步最小的cost/期望最长路径的cost
    对相同的f值的节点打破对称性 ,有一个倾向性(原本对f排序,f相同,对h排序,谁的h小,谁放在前面);
    加上倾向性(沿着起点到终点直线的路径代价),计算任何一个节点,额外计算一个代价cross
    dx1 = abs(node.x-goal.x)
    dy1 = abs(node.y-goal.y)
    dx2 = abs(start.x-goal.x)
    dy2 = abs(start.y-goal.y)
    cross = abs(dx1dy2- dx2dy1)
    h=h+cross*0.001

总结

本文介绍了图搜索中深度优先搜索和广度优先搜索的流程,也介绍了Dijkstra和A*算法的过程,存在许多的不足,欢迎指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值