【蓝桥杯】【啊哈!算法】深度优先搜索——全排列

【啊哈!算法】系列文章目录



需求介绍

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

思路分析

  这里我们先将这个问题形象化, 举个例子,假如有编号为1 、2、3 的3 张扑克牌和编号为1 、2、3 的3 个盒子。现在需要将这3 张扑克牌分别放到3 个盒子里面, 并且每个盒子有且只能放一张扑克牌。那么一共有多少种不同的放法呢?

  好了, 现在轮到小哼出马。小哼手拿3 张扑克牌, 首先走到了1 号盒子面前。此时小哼心里想; 我是先放1号扑克牌, 还是先放2号扑克牌, 还是先放3 号扑克牌呢? 现在要生成的是全排列, 很显然这三种情况都需要去尝试。小哼说那我们约定一个顺序吧: 每次到一个盒子面前时, 都先放1 号, 再放2 号, 最后放3 号扑克牌。说完小哼走到了1 号盒子前, 将1 号扑克牌放到第1个盒子中。

  放好之后小哼往后走一步, 来到了2 号盒子面前。本来按照之前约定的规则, 每到一个新的盒子面前, 要按照1 号、2 号、3 号扑克牌的顺序来放。但是现在小哼手中只剩下2 号和3 号扑克牌了,于是小哼将2 号扑克牌放入了2 号盒子中。放好之后小哼再往后走一步,来到了3 号盒子面前。

  现在小哼已经来到了3号盒子面前,按照之前约定的顺序,还是应该按照1号、2号、3号扑克牌的顺序来放, 但是小哼手中只有3 号扑克牌了, 于是只能往3 号盒子里面放3 号扑克牌。放好后, 小哼再往后走一步, 来到了4 号盒子面前。咦! 没有第4 个盒子, 其实我们并不需要第 4 个盒子, 因为手中的扑克牌已经放完了。

  我们发现当小哼走到第4 个盒子的时候, 已经完成了一种排列, 这个排列就是前面3 个盒子中的扑克牌号码, 即“ 1 2 3 ”。

  是不是到此就结束了呢? 肯定没有! 产生了一种排列之后小哼需要立即返回。现在小哼需要退一步重新回到3 号盒子面前。

  好! 现在小哼已经回到了3 号盒子面前,需要取回之前放在3 号盒子中的扑克牌, 再去尝试看看还能否放别的扑克牌, 从而产生一个新的排列。于是小哼取回了3 号扑克牌。当小哼再想往3 号盒子放别的扑克牌的时候, 却发现手中仍然只有3 号扑克牌, 没有别的选择。于是小哼不得不再往回退一步, 回到 2 号盒子面前。

  小哼回到2 号盒子后, 收回了2 号扑克牌。现在小哼手里面有两张扑克牌了, 分别是2号和3 号扑克牌。按照之前约定的顺序, 现在需要往2 号盒子中放3 号扑克牌( 上一次放的是2 号扑克牌) 。放好之后小哼又向后走一步, 再次来到了3 号盒子面前。

  小哼再次来到3 号盒子后, 将手中仅剩的2 号扑克牌放入了3 号盒子。又来到4 号盒子面前。当然了, 这里并没有4 号盒子。此时又产生了一个新的排列“ 1 3 2”。

  接下来按照刚才的步骤去模拟, 便会依次生成所有排列。

  说了半天, 这么复杂的过程如何用程序实现呢? 我们现在来解决最基本的问题: 如何往小盒子中放扑克牌。每一个小盒子都可能放1 号、2 号或者3 号扑克牌, 这需要一一去尝试,这里一个for循环就可以解决。

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

  这里数组a 是用来表示小盒子的, 变量step 表示当前正处在第step 个小盒子面前。a[step]=i 就是将第i 号扑克牌放入到第 step 个盒子中。这里有一个问题那就是, 如果一张扑克牌已经放到别的小盒子中了, 那么此时就不能再放入同样的扑克牌到当前小盒子中, 因为此时手中已经没有这张扑克牌了。因此还需要一个数组book 来标记哪些牌已经使用了。

在这里插入图片描述
  OK, 现在已经处理完第 step 个小盒子了, 接下来需要往下走一步, 继续处理第 step+1 个小盒子。那么如何处理第 step+1 个小盒子呢? 处理方法其实和我们刚刚处理第 step 个小盒子的方法是相同的。因此就很容易想到( 如果这个词伤害了您, 我表示深深的歉意^_^),把刚才的处理第 step 个小盒子的代码封装为—个函数, 我们为这个函数起个名字, 就叫做 dfs 吧, 如下。

在这里插入图片描述
  把这个过程写成函数后, 刚才的问题就好办了。在处理完第 step 个小盒子之后, 紧接着处理第 step+1 个小盒子, 处理第step+1 和小盒子的方法就是dfs(step+1), 请注意下面代码中加粗的语句。

在这里插入图片描述在这里插入图片描述
  上面代码中的 book[i]=0 这条语句非常重要, 这句话的作用是将小盒子中的扑克牌收回,因为在一次摆放尝试结束返回的时候, 如果不把刚才放入小盒子中的扑克牌收回, 那将无法再进行下一次摆放。还剩下一个问题, 就是什么时候该输出一个满足要求的序列呢。其实当我们处理到第 n+1 个小盒子的时候( 即step 等于n+1 ) , 那么说明前n 个盒子都已经放好扑克牌了, 这里就将 1~n 个小盒子中的扑克牌编号打印出来就可以了, 如下。注意! 打印完毕一定要立即 return, 不然这个程序就会永无止境地运行下去了, 想一想为什么吧。
在这里插入图片描述
在这里插入图片描述

C语言代码

#include <stdio.h>

int data[10] = {1,2,3,4,5};
int a[10], book[10], n=5; //此处特别说明一下: C语言的全局变量在没有赋值以前默认为0, 因此这里的book数组无需全部再次赋初始值0

void dfs(int step) {  //step表示现在站在第几个盒子面前
	int i;
	if(step==n) {
		//输出一种排列(l~n号盒子中的扑克牌编号)
		for(i=0;i<n;i++) {
			printf("%d ",a[i]);
		}
		printf("\n");
		return;  //返回之前的一步(最近一次调用dfs函数的地方)
	}
	
	
	//此时站在第step个盒子面前, 应该放那张牌呢?
	//按照1 、2 、3... n的顺序一一尝试
	for(i=0;i<n;i++) {
		//判断扑克牌i是否还在手上
		if(book[i]==0) {  //book[i]等于0表示i号扑克牌在手上
			//开始尝试使用扑克牌i
			a[step] = data[i];  //将i号扑克牌放入到第step个盒子中
			book[i] = 1;  //将book[i]设为1, 表示i号扑克牌已经不在手上
			
			dfs(step+1);  //第step个盒子已经放好扑克牌, 接下来需要走到下一个盒子面前
			book[i] = 0;  //这是非常重要的一步,一定要将刚才尝试的扑克牌收回, 才能进行下一次尝试
		}
	}
	return;
} 


int main() {
	dfs(0);
	return 0;
}

总结

  这个简单的例子, 核心代码不超过20 行, 却饱含深度优先搜索( Depth First Search , DFS )的基本模型。理解深度优先搜索的关键在于解决“ 当下该如何做 ”。至于“下一步如何做”则与 “当下该如何做” 是一样的。比如我们这里写的dfs(step)函数的主要功能就是解决当你在第step个盒子的时候你该怎么办。通常的方法就是把每一种可能都去尝试一遍( 一般使用 for 循环来遍历)。当前这一步解决后便进入下一步dfs(step+1)。下一步的解决方法和当前这步的解决方法是完全一样的。下面的代码就是深度优先搜索的基本模型。

void dfs(int step)
{
	判断边界
	尝试每一种可能 for(i=0; i<n; i++)
	{
		继续下一步 dfs(step+1);
	}
	返回
}

  每一种尝试就是一种“扩展”。每次站在一个盒子面前的时候, 其实都有 n 种扩展方法,但是并不是每种扩展都能够扩展成功。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_43964993

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值