[算法笔记]递归

1.分治

分治全称为分而治之,即将原问题或分成若干个规模结构较小而结构与原问题相同或相似的子问题,然后分别解决这些子问题,最后合并子问题,即可以得到原问题的解。
分治法的三个步骤:
1)分解:将原问题分解成文若干和原问题相同或相似结构的子问题。
2)解决:递归求解所有问题。如果存在子问题规模较小的可以直接解决,就直接解决它。
3)合并:将子问题的解合并成原问题的解。
子问题的解应当是相互独立,没有交叉。
分治所谓一种算法思想,可以使用递归来解决,也可以使用非递归。

2.递归

递归在于反复调用自身函数,每次把问题缩小,知道范围缩小到可以直接得到边界数据的结果,然后在返回的路上求出对应的解。
递归的两个重要概念:1)递归边界,2)递归式(递归调用)
以下是几个递归的经典例子

eg1:求n的阶乘

#include<cstdio>
using namespace std;
int func(int n) {
	if (n == 0)return 1;
	else return func(n - 1) * n;
}
int main() {
	int n;
	(void)scanf("%d", &n);
	printf("%d", func(n));
	return 0;
}

eg2:求第n项Fibonacci数列

#include<cstdio>
using namespace std;
int func(int n) {
	if (n == 0 || n == 1)return 1;
	else return func(n - 1) + func(n - 2);
}
int main() {
	int n;
	(void)scanf("%d", &n);
	printf("%d", func(n));
	return 0;
}

eg3:求1-n这n个数的全排列
可以将问题分成若干个子问题,:输出以1开头的全排列,输出以2开头的全排列…输出以n开头的全排列…。不妨设定一个数组P,用来存放当前的存放,在设定一个散列数组HashTable,其中HashTable[x]当整数x已经在数组P中时为true。
现在按顺序往P的第1位到第n位中填入数字。不妨假设已经填好了P[1]-P[index-1],正准备填P[index]。需要枚举1-n,如果当前枚举的数字还没有在P[1]-P[index-1]中,即HashTable[x]==false.
就把x填入P[index],同时将hashTable[x]置为true。递归完成时,将hashTable[x]置为false,以便让P[index]填下一个数字。
当index到达n+1时,说明P的1-n位都已经填好了,此时可以把P数组输出,表示生成了一个排列。

#include<cstdio>
const int maxn = 11;
int n, P[maxn], HashTable[maxn] = { false };
void generateP(int index) {//当前处理排列的第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++) {
		if (HashTable[x] == false) {
			P[index] = x;//令排列的第index位为x
			HashTable[x] = true;
			generateP(index + 1);//处理下一位
			HashTable[x] = false;//还原状态
		}
	}
}
int main() {
	n = 3;
	generateP(1);
	return 0;
}

eg4:n皇后问题
在n*n的国际象棋棋盘上放置n和皇后,使得这n个皇后两两均不在同一行,同一列,同一条对角线上,求合法的方案数。
考虑每行只能放一个皇后,每列也只能放一个皇后,那么将n列皇后所在的行号依次写出,那么就会是1-n的一个排列,于是只需要枚举出1-n的所有排列,查看每个方案的防止是否合法,统计其中合法的方案即可。
于是可以在全排列的基础上进行求解。由于到达递归边界时生成了一个排列,所以需要在其内部判断是否为合法方案,即遍历两个皇后后,判断它们是否在同一条对角线上(肯定不是同行同列),若不是,则累计计数变量即可。
在实际放置的过程中,可能会出现这种情况,当已经放置了一部分皇后时(生成了排列的一部分),剩下的无论怎么防止,都不可能合法,此时就没必要向下递归了,直接返回上层即可,这样可以减少计算量
**回溯法:**在到达递归边界之前的某层,由于一些事实已经不需要任何一个子问题递归,就可以直接返回上一层,一般把这种做法称为回溯法。

int n, count = 0, P[maxn], HashTable[maxn] = { false };
void generateP(int index) {
	if (index == n + 1) {
		count++;//到达这里说明是合法的
		return;
	}
	for (int x = 1; x <= n; x++) {
		if (HashTable[x] == false) {//第x行还没有皇后
			bool flag = true;
			for (int pre = 1; pre < index; pre++) {//遍历之前的皇后
				//index列的皇后的行号为x,第pre列的皇后的行号为P[pre]
				if (abs(index - pre) == abs(x - P[pre])) {
					//如果相等说明之前有两个皇后已经冲突,放弃之后的操作,直接下一步
					flag == false;//与之前的皇后在同一个对角线
					break;
				}
			}
			if (flag == true) {//可以把皇后放在第x行
				P[index] = x;
				HashTable[x] == true;
				generateP(index + 1);
				HashTable[x] = false;
			}

		}
	}
}
发布了101 篇原创文章 · 获赞 1 · 访问量 2971

猜你喜欢

转载自blog.csdn.net/weixin_44699689/article/details/104205925