深搜与广搜(同步博客内容)

DFS_and_BFS

跑断腿的下级---------深度优先搜索

总是有忙不完的事?懂不懂深度优先搜索的含金量啊?能推就推,推不了就交给别人


最好的入门例子-------全排列

好嘛,现在有人交给你3张牌,想拜托你想个办法,告诉他这三张牌所有的组合,牌面是1,2,3

将这个问题形象化,你有三张牌,和三个盒子,每个盒子里面只能有一张牌,你准备怎么放,才能让所有的情况都包含在内。

现在面对着第一个盒子,你想出了一个方法,既然所有的情况都要考虑到,那我就按顺序来放吧,毕竟有序是一个美好的事情,所以你将1放到了第一个盒子,现在走到第二个盒子,你决定遵从你的内心,依然按照顺序放牌,所以你想放1进去,很不幸,1现在幸福的躺在盒子1的怀抱里,按照顺序,现在只能放2,走到第三个盒子面前,你依然按照顺序放牌,1,2,你心里默数着,他两都没了,现在只剩下3了,所以你将3放了进去,现在走到第四个盒子里,你还想按照顺序,不过并没有第四个盒子,你失望而归。

现在开始返回,你收回了3盒子里的3,按照约定,1,2,3,你都给过他们机会,只不过1,2不在,所以第三个盒子你已经数过一遍了,是时候回到第二个盒子了,你又收回了2盒子里的2,按照约定,2盒子面前,你只给过1,2机会,不能放下3不管,所以该给3机会了。2盒子放了3。第三个盒子面前你又去折腾一番,模拟刚才的过程,整个牌的全排列就会实现

这么复杂的过程,代码实现却很简单,为了节省你睡觉的时间,撰稿人决定不多费口舌,直接打出代码不解释

int a[10], book[10],n;//用book数组来记录,数字为1代表已经使用,为0代表未使用
void dfs(int step)
{
	int i;
	if (step == n + 1)//n+1的盒子面前,也就是没牌的时候,输入前面几个盒子里面的牌
	{
		for (i = 1; i <= n; i++)
		{
			printf("%d ", a[i]);
		}
		printf("\n");
		return;
	}

	for (i = 1; i <= n; i++)
	{
		if (book[i] == 0)
		{
			a[step] = i;
			book[i] = 1;

			dfs(step + 1);//每执行一次,就进入下一个盒子内
			book[i] = 0;
		}
	}
	return;
}

int main(void)
{
	scanf("%d", &n);
	dfs(1);
	return 0;
}

代码长度很短,却包含了DFS的核心思想。现在怎么做的所有情况列出来,每执行一次,就让他人开始执行所有情况

当然不要忘了基线条件

寻找宝藏

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-utzHtPql-1643454043425)(微信图片_20211127205538.jpg)]

利用深度优先搜索的逻辑,先列出来当前情况下的所有情况,每执行一次就交给下一步,直到到达基线条件,然后再慢慢回溯寻找最优解。

所有的情况,也就是可以走的方向,也就是上下左右,这里我们采用顺时针的走法,右、下、左、上。毕竟从图上来看宝藏毕竟还是在右下角的,将这两个方向放在前面肯定没坏处。

我知道你在想什么,万一走着走着围成一个死胡同怎么办,万一走着走着原地转圈怎么办,这都是不可能的,为了避免走重复的路这种情况,我们需要记录哪些路是走过的,可以通过一个二维数组记录,数组的下标代表地图的坐标,里面的数字可以代表是否到达过,因为全局变量会被初始化为0,所以这个二维数组定义在全局变量里可以省去初始化的步骤。

移动方向怎么办?因为地图本质就是坐标,所以移动的本质也就是在坐标上的加加减减。可以定义一个方向数组,移动的方向,同时也解决了遍历每种方向的问题

	int next[4][2] =
	{
		{0,1},//右
		{1,0},//下
		{0,-1},//左
		{-1,0}//上
	};

当然为了避免我们上述提到的例如走到死胡同原地打转、走出地图这种滑稽的场面,我们在每次遍历的时候需要进行大量的判断的工作

for (k = 0; k < 4; k++)//遍历每个方向的走法
	{
		tx = x + next[k][0];
		ty = y + next[k][1];

		if (tx<1 || tx>n || ty<1 || ty>n)
			continue;
		if (a[tx][ty] == 0 && book[tx][ty] == 0)
		{
			book[tx][ty] = 1;
			dfs(tx, ty, step + 1);
			book[tx][ty] = 0;
		}
	}

完整代码如下,试着根据逻辑自己重新写出来,当然了,重要的思想,而不是代码。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int n, m, p, q, min = 99999999;

int a[51][51], book[51][51];//a用来存储地图,book用来标记哪些地方是被我们走过的地方
void dfs(int x, int y, int step)//DFS,x,y是上一步所处的坐标,step是走过的步数
{
	int next[4][2] =
	{
		{0,1},
		{1,0},
		{0,-1},
		{-1,0}
	};
	int tx, ty, k;
	if (x == p && y == q)//基线条件,找到宝藏
	{
		if (step < min)
			min = step;//更新最小步数
		return;
	}
	for (k = 0; k < 4; k++)
	{
		tx = x + next[k][0];
		ty = y + next[k][1];//tx,ty是走过一步以后,所处的位置坐标

		if (tx<1 || tx>n || ty<1 || ty>n)//为了避免出现滑稽的情况,我们要进行许多判断
			continue;
		if (a[tx][ty] == 0 && book[tx][ty] == 0)
		{
			book[tx][ty] = 1;
			dfs(tx, ty, step + 1);
			book[tx][ty] = 0;
		}
	}
	return;
}

int main(void)
{
	int i, j, startx, starty;
	scanf("%d %d", &n, &m);//输入地图
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= m; j++)
		{
			scanf("%d", &a[i][j]);
		}
	}
	scanf("%d %d %d %d", &startx, &starty, &p, &q);//开始所处的位置和宝藏位于的地方
	book[startx][starty] = 0;
	dfs(startx, starty, 0);//当前还没走,step自然就是0了
	printf("%d", min);
	
	return 0;
}

至少学了这个算法,哪怕不会使用图形库,c语言也有了美丽的画面

齐头并进------------广度优先搜索

最好的入门例子--------芒果商人

学习此种算法以后,你可以解决两类问题:

1.从节点A出发,右前往节点B的路径吗?

2.从节点A出发,前往节点B的那条路径最短?

如果不知道什么是图,也不影响我们了解BFS

A想找一个人买芒果,因为买的比较多,所以他希望能够通过交际圈找到一个卖芒果的人,直接进货。所以他开始从自己身边的朋友开始寻找。B、C都没有。没关系,托关系还是很好办事的,他让B帮忙寻找一个芒果商人,B开始寻找,找了A1、B1、C1。很不辛也没有,接下来该寻找谁?A1的朋友吗?不是的,因为你的二度关系还没有寻找完毕,也就是你的朋友C的朋友,顺着逻辑往下,遍历离你关系最近的人,如果他不是一个芒果经销商,那么就将他的朋友加入需要寻找的目录。队列可以实现上述这个看似复杂的过程,如果不是芒果经销商,那么就将他出队,同时将他的朋友入队。最终,找到的芒果经销商,一定是离你关系最近的那个人

用BFS寻找宝藏

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct note
{
	int x;
	int y;
	int s;
};
int main(void)
{
	struct note que[2501];
	int a[51][51] = { 0 }, book[51][51] = { 0 };
	//定义一个用于表示走的方向的数组
	int next[4][2] =
	{
		{0,1},
		{1,0},
		{0,-1},
		{-1,0}
	};
	int head, tail;
	int i, j, k, n, m, startx, starty, p, q, tx, ty, flag;
	scanf("%d %d", &n, &m);
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= m; j++)
		{
			scanf("%d", &a[i][j]);
		}
	}
	scanf("%d %d %d %d", &startx, &starty, &p, &q);
	//初始化队列
	head = 1;
	tail = 1;
	//往队列插入迷宫入口
	que[tail].x = startx;
	que[tail].y = starty;
	que[tail].s = 0;
	tail++;
	book[startx][starty] = 1;
	flag = 0;//用来表示是否到达目标点,0表示暂时还没有到达,1表示到达

	//当队列不为空的时候循环
	while (head < tail)
	{
		//枚举4个方向
		for (k = 0; k <= 3; k++)
		{
			tx = que[head].x + next[k][0];
			ty = que[head].y + next[k][1];
			//判断是否越界
			if (tx<1 || tx>n || ty<1 || ty>m)
				continue;
			//判断是否是障碍物或者已经在路径中
			if (a[tx][ty] == 0 && book[tx][ty] == 0)
			{
				//把这点标记为走过
				book[tx][ty] = 1;
				que[tail].x = tx;
				que[tail].y = ty;
				que[tail].s = que[head].s + 1;
				tail++;
			}
			if (tx == p && ty == q)
			{
				flag = 1;
				break;
			}

		}
		if (1 == flag)
			break;
		head++;//当一个点扩展完毕以后,才能对队列中的下一个点扩展
	}
	/*
	打印队列中末尾最后一个点的步数
	因为tail是指向队尾的下一个位置的,所以需要tail-1
	*/
	printf("%d", que[tail - 1].s);
	return 0;
}

期待

就目前而言,DFS用于一些穷举类问题,BFS用于解决这类迷宫或者面积之类的问题,但根据书中关于DFS和BFS的描述,感觉这东西在图里可能会有大用,满怀期待的等到学到图的那一天。也感谢这两个算法让c语言有了“图像”有了“参与感”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值