分治
【分治】分而治之(divide and conquer)
👉分解(子问题)→解决→合并
其中,子问题规模小,结构与原问题类似,且各子问题相互独立、没有交叉。
👉一般用递归的方法实现。
递归
1.递归的两个重要概念:
【递归边界】分解的尽头,用来返回最简单底层的结果(可以直接计算得到)。
【递归式】将原问题分解为若干子问题的手段,用来减小问题规模并向下一层递归。
2.理解递归的辅助:画递归图
👇两个例子
全排列(Full Permutation)
要求: 实现按字典序从小到大的顺序输出1~n的全排列。
分析: ①分解子问题:“输出以1开头的全排列”、“输出以2开头的全排列”……
②设定数组P来存放当前的排列,设定散列数组hashTable记录整数x是否已经在P中。
③按顺序往P的第1位到第n位中填入数字。不妨设当前已经填好了P[1]~P[index-1],正准备填P[index],则需枚举1-n,用hashTable判断当前枚举的数字x是否在P中,若不在则填入P[index],修改hashTable,接着处理P的第index+1位(递归);而完成递归后,再还原hashTable[x]为false,以边让P[index]填下一个数字。
④递归边界:当index达到n+1时,说明P的第1-n位都已经填好,可以输出P。
抄一个代码
#include <iostream>
using namespace std;
const int maxn = 11;
int n, P[maxn], hashTable[maxn] = {false}; //P为当前排列,hashTable记录整数x是否已经在P中
//当前处理排列的第index号位
void generateP(int index){
if ( index==n+1 ){ //递归边界,之前已经处理完排列的1~n位
for ( int i=1; i<=n; i++ ){
cout << P[i]; //输出当前排列
}
cout << endl;
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; //令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;
}
说个题外话,用hashTable对x进行标记true后标记false这里,好像OS的互斥访问信号量上锁解锁啊(。・∀・)ノ
n皇后问题
问题描述: n皇后问题是指在一个n*n的国际象棋棋盘上放置n个皇后,使得这n个皇后两两均不在同一行、同一列、同一对角线上,求合法的方案数。
问题分析: ①考虑算法规模:如果采用组合数的方式来枚举每一种情况(即从n2个位置中选择n个位置),n稍大一些枚举量就会难以承受。
②由于每行、每列都只能放一个皇后,则可以枚举n列皇后所在行号,即全排列(这使得每两个皇后必然不在同一行/列);而后,当全排列到达递归边界时再遍历每两个皇后,判断她们是否在同一条对角线上。
代码搬运工
int cnt = 0;
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 ) cnt++; //若当前方案合法,令cnt加1
return;
}
for ( int x=1; x<=n; x++ ){
if ( hashTable[x]==false ){
P[index] = x; //P数组记录行号,下标为列号
hashTable[x]= true;
generateP(index+1);
hashTable[x] = false;
}
}
}
【注】一般把类似这样不使用优化算法、直接用朴素算法来解决问题的做法称为暴力法。
算法改进: 当已经放置了一部分皇后时(对应于生成了排列的一部分),可能剩余的皇后无论怎样放置都不可能合法,这时就没必要再往下递归了,直接返回上层即可。
搬运个改进的代码***
void generateP(int index){
if ( index==n+1 ){ //递归边界,生成一个合法方案
cnt++; //能到达这里的一定是合法的
return;
}
for ( int x=1; x<=n; x++ ){ //第x行
if ( hashTable[x]==false ){ //第x行还没有皇后
bool flag = true; //flag为true表示当前皇后不会和之前的皇后冲突
for ( int pre=1; pre<index; pre++ ){ //遍历之前的皇后
if ( abs(index-pre)==abs(x-P[pre]) ){
//第index列皇后的行号为x,第pre列皇后的行号为P[pre]
flag = false; //与之前的皇后在一条对角线,冲突
break;
}
}
if ( flag ){ //如果可以把皇后放在第x行
P[index] = x; //令第index列皇后的行号为x
hashTable[x] = true; //第x行已被占用
generateP(index+1); //递归处理第index+1行皇后
hashTable[x] = false; //递归完毕,还原第x行为未占用
}
}
}
}
【注】回溯法: 在到达递归边界前由于某些事实可以直接返回上一层。