算法入门之递归(《算法笔记》)

分治

【分治】分而治之(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行为未占用 
			}
		}
	}
} 

【注】回溯法: 在到达递归边界前由于某些事实可以直接返回上一层。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值