(四)万能的搜索 —— 1. 深度优先搜索

1. 深度优先搜索

问题:输入一个数n,输出1~n的全排列。

举个例子,假如有编号为1、2、3的3张扑克牌和编号为1、2、3的3个盒子。

现在需要将这3张扑克牌分别放到3个盒子里面,并且每个盒子有且只能放一张扑克牌。

那么一共有多少种不同的放法呢?


解决最基本的问题:如何往小盒子中放扑克牌。

每一个小盒子都可能放1号、2号或者3号扑克牌,这需要一一去尝试,这里一个for循环就可以解决:

for (i=1; i<=n; i++) {
    
    
	a[step] = i;//将i号扑克牌放入到第step个盒子中
}

这里数组a是用来表示小盒子的,变量step表示当前正处在第step个小盒子面前。


如果一张扑克牌已经放到别的小盒子中了,那么此时就不能再放入同样的扑克牌到当前小盒子中,因为此时手中已经没有这张扑克牌了。

因此还需要一个数组book来标记哪些牌已经使用了。

for (i=1; i<=n; i++) {
    
    
	if (book[i]==0) //book[i] 等于0表示i号扑克牌仍然在手上
	{
    
    
		a[step] = i;//将i号扑克牌放入到第step个盒子中
		book[i] = 1;//将book[i] 设为1,表示i号扑克牌已经不在手上
	}
}

现在已经处理完第step个小盒子了,接下来需要继续处理第step+1个小盒子。

方法是,把刚才的处理第step个小盒子的代码封装为一个函数dfs,如下:

void dfs (int step) //step表示现在站在第几个盒子面前
{
    
    
	for (i=1; i<=n; i++) {
    
    
		//判断扑克牌i是否还在手上
		if (book[i]==0) //book[i] 等于0表示i号扑克牌仍然在手上
		{
    
    
			a[step] = i;//将i号扑克牌放入到第step个盒子中
			book[i] = 1;//将book[i] 设为1, 表示i号扑克牌已经不在手上
		}
	}
}

处理第step+1和小盒子的方法就是dfs(step+1):

void dfs (int step) //step表示现在站在第几个盒子面前
{
    
    
	for (i=1; i<=n; i++) {
    
    
		//判断扑克牌i是否还在手上
		if (book[i]==0) //book[i] 等于0表示i号扑克牌仍然在手上
		{
    
    
			a[step] = i;//将i号扑克牌放入到第step个盒子中
			book[i] = 1;//将book[i] 设为1, 表示i号扑克牌已经不在手上
			dfs(step+1);//这里通过函数的递归调用来实现(自己调用自己)
			book[i]=0;//这是非常重要的一步, 一定要将刚才尝试的扑克牌收回,才能进行下一次尝试
		}
	}
}

什么时候该输出一个满足要求的序列呢?

其实当处理到第n+1个小盒子的时候(即step等于n+1),说明前n个盒子都已经放好扑克牌了,这里就将1~n个小盒子中的扑克牌编号打印出来就可以了,如下:

注意!打印完毕一定要立即return。

void dfs (int step) //step表示现在站在第几个盒子面前
{
    
    
	if (step==n+1) //如果站在第n+1个盒子面前,则表示前n个盒子已经放好扑克牌
	{
    
    
		for (i=1; i<=n; i++) 
		{
    
    
			printf("%d\n", a[i]);
		}
		printf("\n");
		return;//返回之前的一步(最近一次调用dfs函数的地方)
	}

	//输出一种排列(1~n号盒子中的扑克牌编号)
	for (i=1; i<=n; i++) {
    
    
		//判断扑克牌i是否还在手上
		if (book[i]==0) //book[i] 等于0表示i号扑克牌仍然在手上
		{
    
    
			a[step] = i;//将i号扑克牌放入到第step个盒子中
			book[i] = 1;//将book[i] 设为1, 表示i号扑克牌已经不在手上
			dfs(step+1);//这里通过函数的递归调用来实现(自己调用自己)
			book[i]=0;//这是非常重要的一步, 一定要将刚才尝试的扑克牌收回,才能进行下一次尝试
		}
	}
}

完整代码如下:

#include <stdio.h>

int a[101], book[101], n;

void dfs(int step) {
    
    
	int i;

	if (step==n+1) //如果站在第n+1个盒子面前,则表示前n个盒子已经放好扑克牌
	{
    
    
		for (i=1; i<=n; i++) 
		{
    
    
			printf("%d\n", a[i]);
		}
		printf("\n");
		return;//返回之前的一步(最近一次调用dfs函数的地方)
	}

	//输出一种排列(1~n号盒子中的扑克牌编号)
	for (i=1; i<=n; i++) {
    
    
		//判断扑克牌i是否还在手上
		if (book[i]==0) //book[i] 等于0表示i号扑克牌仍然在手上
		{
    
    
			a[step] = i;//将i号扑克牌放入到第step个盒子中
			book[i] = 1;//将book[i] 设为1, 表示i号扑克牌已经不在手上
			dfs(step+1);//这里通过函数的递归调用来实现(自己调用自己)
			book[i]=0;//这是非常重要的一步, 一定要将刚才尝试的扑克牌收回,才能进行下一次尝试
		}
	}
	return;
}

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

这个例子,饱含深度优先搜索(Depth First Search, DFS)的基本模型。

理解深度优先搜索的关键在于解决“当下该如何做”。至于“下一步如何做”,则与“当下该如何做”是一样的。

通常的方法就是把每一种可能都去尝试一遍(一般使用for循环来遍历) 。当前这一步解决后便进入下一步dfs(step+1) 。

下一步的解决方法和当前这步的解决方法是完全一样的。

下面的代码就是深度优先搜索的基本模型:



手中有编号为1~9的九张扑克牌,然后将这九张扑克牌放到九个盒子中,并使得 □□□+□□□=□□□ 成立。其实就是判断一下a[1]*100+a[2]*10+a[3]+a[4]*100+a[5]*10+a[6]==a[7]*100+a[8]*10+a[9]这个等式是否成立。

#include <stdio.h>

int a[10], book[10], total=0;

void dfs(int step) {
    
    
	int i;
	if (step==10) {
    
    
		if (a[1]*100+a[2]*10+a[3]+a[4]*100+a[5]*10+a[6]==a[7]*100+a[8]*10+a[9]) {
    
    
			total++;
			printf("%d%d%d+%d%d%d=%d%d%d\n", a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]);
		}
		return;
	}

	for (i=1; i<=9; i++) {
    
    
		if (book[i]==0) {
    
    
			a[step] = i;
			book[i] = 1;
			dfs(step+1);
			book[i] = 0;
		}
	}
	return;
}

int main() {
    
    
	dfs(1);
	printf("total=%d\n", total/2);
	getchar();getchar();
	return 0;
}




参考

《啊哈!算法》 —— 第4章 万能的搜索

猜你喜欢

转载自blog.csdn.net/m0_38111466/article/details/119896320