递归很适合实现分治思想,递归逻辑有两个重要概念:
- 递归边界
- 递归式(递归调用)
用例题来理解递归
全排列问题
全排列指n个整数能形成的所有排列。
比如1、2、3
就能形成(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,此时3是false,列入数组,放入数组中index的位置,即2
以此类推.....
n皇后问题
在n*n的棋盘上放置n个皇后,使得n个皇后均不在同一行、同一列、同一条对角线上,求合法的方案数
如果采用组合数的方式枚举每一种情况将需要枚举Cnn*n次。很有可能超时。
换个思路,如果每行只放置一个皇后、每列也只放置一个皇后,把n列皇后所在的行号依次写出来。就是一个1~n的排列。
比如图上的序列就是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;
}
}
}
}