递归——全排列、N皇后

递归很适合实现分治思想,递归逻辑有两个重要概念:

  1. 递归边界
  2. 递归式(递归调用)

用例题来理解递归

全排列问题

全排列指n个整数能形成的所有排列。

比如123
就能形成(123) (132) (213) (231) (312) (321)6个全排列

现在用递归解决全排列
题目“输出1 ~ n这n个整数的全排列”可以分成若干子问题
“以1开头的全排列”、“以2开头的全排列”…

思路:设定一个数组arr用来存放当前的排列;再设定一个散列表hash,当整数x已经在数组arr中时将hash[x]置为true。
按顺序往arr的第1到n位填入数字,当已经填好arr[1]到arr[index-1]正要填入arr[index]时,枚举1~n,如果当前数字x还没被填入arr中时就可以填入arr[index],同时将hash[x]置为true。接着用递归arr的第index+1位。
当递归完成时候再将hash[x]还原为false以便arr[index]填下一个数字。
递归边界:当index到达n+1时说明前面1~n都填好了,此时输出整个数组,表明已经生成了一个排列,然后return。

n代表要全排列的数的个数
void generate_arr(int index)
{
	if (index == n + 1) //递归边界
	{
		for (int i = 1; i <= 5; i++)
			cout << arr[i];
		cout << endl;
		return;
	}
	for (int x = 1; x <= n; x++)
	{
		if (hash[x] == false)
		{
			arr[index] = x;
			hash[x] = true;
			generate_arr(index + 1);
			hash[x] = false; //重置为false
		}
	}
}

举个例子便于理解:
假设数组123

刚开始index为1, hash[1]false,所以列入数组,递归index+1
index为2,hash[1]true,所以把2列入数组,递归index+1
index为3,hash[1],[2]都是true,将3列入数组,递归触发边界,输出123
return回来hash[3]重置为false,此时因为已经到3,所以退出循环,接着退出函数
执行index为2的时候,将hash[2]置为false,循环到3,此时3false,列入数组,放入数组中index的位置,即2
以此类推.....

n皇后问题

在n*n的棋盘上放置n个皇后,使得n个皇后均不在同一行、同一列、同一条对角线上,求合法的方案数
如果采用组合数的方式枚举每一种情况将需要枚举Cnn*n次。很有可能超时。
换个思路,如果每行只放置一个皇后、每列也只放置一个皇后,把n列皇后所在的行号依次写出来。就是一个1~n的排列。
8皇后问题
比如图上的序列就是84136275,这就只需枚举1~n的所有排列,然后查看每个排列对应的方案是否合法,统计合法方案即可。
时间复杂度:O(n!)
时间复杂度很高,一般把这种不使用优化,非常朴素的算法叫做暴力法

int count = 0;
void generate_arr(int index)
{
	if (index == n + 1)
	{
		bool flag = true;
		for (int i = 1; i <= n; i++)
		{
			for (int j = i + 1; j <= n; j++)
				if (abs(i - j) == abs(arr[i] - arr[j]))
				//在对角线说明两皇后连线和水平线呈45°,所以x差值和y差值相同
					flag = false;
		}
	if (flag)
		count++;
	return;
	}
	for (int x = 1; x <= n; x++)
	{
		if (hash[x] == false)
		{
			arr[index] = x;
			hash[x] = true;
			generate_arr(index + 1);
			hash[x] = false;
		}
	}
}
//这里其实就是全排列后找其中符合要求的排列。i和j是列下标

可以优化一下,当已经放置一部分皇后时(即生成了一部分排列时),可能剩余的皇后无论放哪都不合法,此时就没必要递归了,直接放回上层即可,这样能减少很多计算。
如果在到达递归边界前的某层,由于一些事实导致已经不需要往任何一个子问题递归,就可以直接返回上层。一般这种做法叫做回溯法。

void generate_arr(int index)
{
	if (index == n + 1) //到这一层说明肯定合法,count自增
	{
		count++;
		return;
	}
	for (int x = 1; x <= n; x++)
	{
		if (hash[x] == false) //false表明该行还没有皇后
		{
			bool flag = true;
			for (int pre = 1; pre < index; pre++) //遍历之前的皇后
			{ //第index列皇后行号为x,第pre列皇后的行号为P[pre]
				if (abs(index - pre) == abs(x - arr[pre]))
				{ //说明冲突
					flag = false;
					break;
				}
			}
			if (flag) //falg为true表示当前皇后和之前的不冲突
			{
				arr[index] = x;
				hash[x] = true;
				generate_arr(index + 1);
				hash[x] = false;
			}
		}
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值