分治(divide and conquer)
全称“分而治之”, 分治法作为一种算法思想,既可以使用递归的手段去实现,也可以通过非递归的手段去实现。
递归
递归边界和递归式构成。
//求n的阶乘
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int f(int n)
{
if(n==0) return 1; //当到达递归边界f(0)时,返回f(0)==1
else return f(n-1) * n;
}
int main()
{
int n;
scanf("%d",&n);
printf("%d\n",f(n));
return 0;
}
//递归实现斐波那契数列
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int f(int n)
{
if(n==0 || n==1) return 1; //递归边界
else return f(n-1) + f(n-2); //递归式
}
int main()
{
int n;
scanf("%d",&n);
printf("%d\n",f(n));
return 0;
}
1. 递归求全排列
之前一直纠结程序是如何执行的,堆栈是怎么变化的。今天借助debug手写模拟一遍,差不多弄懂了这个全排列计算机是如何执行的。附上手写执行过程。
//递归实现求1~n的全排列
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn = 11;
int n ; //n为全局变量
int p[maxn]; //p为当前排列,
int hashTable[maxn] = {false}; //hashTable记录整数x是否已经在p中
//当前处理排列的第index号位
void generateP(int index){
if(index == n + 1){ //递归边界,已经处理完排列的1~n位
for(int i=1; i<=n; i++){
printf("%d", p[i]); //输出当前排列
}
printf("\n");
return;
}
for(int x=1; x<=n; x++){ //枚举1~n,试图将x填入p[index]
if(hashTable[x] == false){ //如果x不在p[0]~p[index-1]中
p[index] = x; //把x加入当前排列
hashTable[x] = true; //记x已在p中
generateP(index+1); //处理排列的第index+1号位
hashTable[x] = false; //已处理完p[index]为x的子问题,还原状态
}
}
}
int main()
{
n = 3; //欲输出1~3的全排列
generateP(1); //从P[1]开始填
return 0;
}
递归执行调用过程图:
![](https://i-blog.csdnimg.cn/blog_migrate/a5055cfa4947b6b049793a142b26ea84.jpeg)
2. n皇后问题
(1)暴力法
/*
n皇后问题(枚举---暴力法)
指在一个n*n的国际象棋棋盘上放置n个皇后,使得这n个皇后两两均不在同一行,
同一列,同一条对角线上,求合法的方案数。
全排列的思路:只看对应的列号1~n的全排列(显然不在同一行和同一列),
递归边界判断在其内部是否为合法的方案,即遍历每两个皇后,判断他们是否在同一条对角线上;
*/
#include<stdio.h>
#include<math.h>
#include<algorithm>
int count = 0;
int visit[100010] = {false}; //标记数组
int p[100010] ;
int n;
void generateP(int index){
if(index == n+1){ //递归边界,生成一个排列
bool flag = true; //flag为true表示当前位一个合法方案
for(int i=1; i<=n; i++) //遍历任意两个皇后
{
for(int j=i+1; j<=n; j++){
if(abs(i-j) == abs(p[i] - p[j])){ //如果在一条对角线上
flag = false; //不合法
}
}
}
if(flag == true) count++; //若当前方案合法,令count加1
return;
}
for(int x=1; x<=n; x++){
if(visit[x] == false){
p[index] = x;
visit[x] = true;
generateP(index + 1);
visit[x] = false;
}
}
}
int main(){
n = 8;
generateP(1);
printf("%d\n",count);
return 0;
}
//运行结果 92
(2)回溯优化
/*
回溯法:
一般来说,如果在到达递归边界前的某层,由于一些事实导致已经不需要
往任何一个子问题递归,就可以直接返回上一层。一般这种做法称为递归。
*/
#include<stdio.h>
#include<math.h>
#include<algorithm>
int count = 0;
int visit[100010] = {false}; //标记数组
int p[100010] ;
int n;
void generateP(int index){
if(index == n+1){ //递归边界,生成一个排列
count++; //能到达这里的一定是合法的
return;
}
for(int x=1; x<=n; x++){ //第x行
if(visit[x] == false){ //第x行还没有皇后
bool flag = true; //flag为true表示当前皇后不会和之前的皇后冲突
for(int pre=1; pre<index; pre++){//遍历之前的皇后
//前一个皇后的坐标 (pre, p[pre]
//当前皇后的坐标(index, x)
if(abs(pre-index) == abs(p[pre]-x)) { //与之前的皇后冲突
flag = false;
break;
}
}
//与全排列代码相同
if(flag==true){ //如果可以把皇后放在第x行
p[index] = x;
visit[x] = true;
generateP(index+1);
visit[x] = false;
}
}
}
}
int main(){
n = 8;
generateP(1);
printf("%d\n",count);
return 0;
}
//运行结果 92