递归算法
递归,定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归。
-
一般来说,能够用递归解决的问题应该满足以下三个条件:
- 需要解决的问题可以转化为一个或多个同构(或同样性质的)子问题来求解,而这些子问题的求解方法与原问题完全相同,只是在数量规模上不同。
- 递归调用的次数必须是有限的。
- 必须有结束递归的条件来终止递归。
-
什么情况下使用递归:
- 定义是递归的,如n!,Fibonacci数列等,求解这类问题可将其递归定义直接转换成对应的递归算法。
- 数据结构(如树,单链表等)是递归的。结构体中的指针指向了自身的类型,是一种递归的数据结构。对于这类递归结构,采用递归的方法编写算法既方便,又高效。
- 问题的求解方法是递归的, 如 Hanoi 问题的求解。
-
递归模型,递归模型是递归算法的抽象,它反映了一个递归问题的递归结构。以求解n!为例,其递归模型如下:
fun(1) = 1 (1) fun(n) = n*fun(n-1) n > 1 (2)
递归模型一般由递归出口和递归体组成。递归出口给出了递归的终止条件,递归体给出了fun(n)
的值与fun(n-1)
的值之间的联系。
- 递归算法的执行过程,
- 一个正确的递归程序虽然每次调用的是相同的子程序,但它的参量、输入数据等均有变化。
- 在正常的情况下,随着调用的不断深入,必定会出现调用到某一层的函数时,不再执行递归调用而终止函数的执行,遇到递归出口便是这种情况。
- 递归调用是函数嵌套调用的一种特殊情况,即它是调用自身代码。也可以把每一次递归调用理解成调用自身代码的一个复制件。由于每次调用时,它的参量和局部变量均不相同,因而也就保证了各个复制件执行时的独立性。
- 系统为每一次调用开辟一组存储单元,用来存放本次调用的返回地址以及被中断的函数的参量值。这些单元以系统栈的形式存放,每调用一次进栈一次,当返回时执行出栈操作,把当前栈顶保留的值送回相应的参量中进行恢复,并按栈顶中的返回地址,从断点继续执行。
- 每递归调用一次,就需进栈一次,最多的进栈元素个数称为递归深度,当n越大,递归深度越深,开辟的栈空间也越大。每当遇到递归出口或完成本次执行时,需退栈一次,并恢复参量值,当全部执行完毕时,栈应为空。
- 在递归函数执行时,形参会随着递归调用发生变化,但每次调用后会恢复为调用前的形参,将递归函数的非引用型形参的取值称为状态。递归函数的引用型形参在执行后会回传给实参,有时类似全局变量,不作为状态的一部分,在调用过程中状态会发生变化,而一次调用后会自动恢复为调用前的状态。
归纳起来,递归调用的实现是分两步进行的,第一步是分解过程,即用递归体将“大问题”分解成“小问题”,直到递归出口为止,然后进行第二步的求值过程,即已知“小问题”,计算“大问题”。
- 递归算法设计,获取递归模型:
- 对原问题进行分析,抽象出合理的“小问题”(与数学归纳法中假设n=k-1时等式成立相似);
- 假设是可解的,在此基础上确定的解,即给出与之间的关系(与数学归纳法中求证n=k时等式成立的过程相似);
- 确定一个特定情况(如或)的解,由此作为递归出口(与数学归纳法中求证n=1或n=0时等式成立相似)。
例1: 在n×n的方格棋盘上,放置n个皇后,要求每个皇后不同行、不同列、不同左右对角线。
问题分析:若它们同列,则有q[k]==j
;对角线有两条,若它们在任一条对角线上,则构成一个等边直角三角形,即|q[k]-j|==|i-k|
。设queen(i,n)
是在1~i-1
列上已经放好了i-1
个皇后,用于在i~n
行放置n-i+1
个皇后,则queen(i+1,n)
表示在1~i
行上已经放好了i
个皇后,用于在i+1~n
行放置n-i
个皇后。queen(i+1,n)
比queen(i,n)
少放置一个皇后,所以queen(i+1,n)
是“小问题”,queen(i,n)
是“大问题”。因此,
递归模型:
当 i>n 时,
queen(i,n) 表示n个皇后放置完毕。
否则,
queen(i,n) 表示在第 i 行的合适的位置(i,j)在其上放置一个皇后;
queen(i+1,n);
n皇后问题示例代码:
#include <iostream>
const int n = 6; // n 等于几,就表示几皇后问题
int q[n+1] = { 0 };
void dispasolution(int n)
{
for (int i = 1; i <= n; ++i)
std::cout << "(" << i << ", " << q[i] << ")\t";
std::cout << std::endl;
}
bool place(int i, int j) //测试(i,j)位置能否摆放皇后
{
if (i == 1) return true; //第一个皇后总是可以放置
int k = 1;
while (k < i) //k=1~i-1是已放置了皇后的行
{
if ((q[k] == j) || (abs(q[k] - j) == abs(i - k)))
return false;
k++;
}
return true;
}
void queen(int i, int n) //放置1~i的皇后
{
if (i > n)
dispasolution(n); //所有皇后放置结束
else
{
for (int j = 1; j <= n; j++) //在第i行上试探每一个列j
if (place(i, j)) //在第i行上找到一个合适位置(i,j)
{
q[i] = j;
queen(i + 1, n);
}
}
}
int main()
{
std::cout << n << " 皇后问题可行解:\n";
queen(1, n);
system("pause");
return 0;
}
结果显示:
例2:对于给定的含有n个元素的数组a,分别采用简单选择排序和冒泡排序方法对其按元素值递增排序。
问题分析:设f(a,n,i)
用于对a[i..n-1]
元素序列(共n-i
个元素)进行冒泡排序,是“大问题”,则f(a,n,i+1)
用于对a[i+1..n-1]
元素序列(共n-i-1
个元素)进行冒泡排序,是“小问题”。当i=n-1
时所有元素有序,算法结束。
2.1,选择排序递归模型:
当 i=n-1 时,
f(a,n,i) 不做任何事情,算法结束。
否则,
f(a,n,i) 通过简单比较挑选 a[i…n-1] 中的最小元素 a[k] 放在 a[i] 处;
f(a,n,i+1);
2.2,冒泡排序递归模型:
当 i=n-1 时,
f(a,n,i) 不做任何事情,算法结束。
否则,
f(a,n,i) 从a[n-1]开始对,a[i…n-1]元素序列进行相邻元素比较,若相邻两元素反序,则将两者交换。
上面循环过程执行完成后,若没有发生过交换,
返回;
否则,
f(a,n,i+1);
选择,冒泡排序示例代码:
#include <iostream>
#include <string>
void print(std::string str, int a[], int n)
{
std::cout << str << std::endl;
for (int i = 0; i < n; ++i)
std::cout << a[i] << " ";
std::cout << std::endl;
}
void swap(int& a, int& b)
{
int tmp = a; a = b; b = tmp;
}
// 基于递归的选择排序:
void SelectSort(int a[], int n, int i)
{
int j, k;
if (i == n - 1)
return; // 满足递归出口条件
else
{
k = i; // k记录a[i..n-1]中最小元素的下标
for (j = i + 1; j < n; j++) // 在a[i..n-1]中找最小元素
if (a[j] < a[k])
k = j;
if (k != i) // 若最小元素不是a[i]
swap(a[i], a[k]); // a[i]和a[k]交换
SelectSort(a, n, i + 1);
}
}
// 基于递归的冒泡排序:
void BubbleSort(int a[], int n, int i)
{
int j;
bool exchange;
if (i == n - 1)
return; //满足递归出口条件
else
{
exchange = false; //置exchange为false
for (j = n - 1; j > i; j--)
if (a[j] < a[j - 1]) //当相邻元素反序时
{
swap(a[j], a[j - 1]);
exchange = true; //发生交换置exchange为true
}
if (exchange == false) //未发生交换时直接返回
return;
else //发生交换时继续递归调用
BubbleSort(a, n, i + 1);
}
}
int main()
{
int a[15] = { 1, 3, 5, 7, 9, 8, 6, 4, 2, 23, 2, 0, 10, 4, 8 };
int b[15] = { 1, 3, 5, 7, 9, 8, 6, 4, 2, 23, 2, 0, 10, 4, 8 };
print("排序前:", a, 15);
SelectSort(a, 15, 0);
print("选择排序后:", a, 15);
BubbleSort(b, 15, 0);
print("冒泡排序后:", b, 15);
system("pause");
return 0;
}
结果显示:
来源:CSDN
作者:sunqin_csdn
链接:https://blog.csdn.net/sunqin_csdn/article/details/104194453