C++算法程序整理——递归算法

♀尐吖头ヾ 提交于 2020-02-10 20:47:31

递归算法

递归,定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归。

  • 一般来说,能够用递归解决的问题应该满足以下三个条件:

    • 需要解决的问题可以转化为一个或多个同构(或同样性质的)子问题来求解,而这些子问题的求解方法与原问题完全相同,只是在数量规模上不同。
    • 递归调用的次数必须是有限的。
    • 必须有结束递归的条件来终止递归。
  • 什么情况下使用递归:

    • 定义是递归的,如n!,Fibonacci数列等,求解这类问题可将其递归定义直接转换成对应的递归算法。
    • 数据结构(如树,单链表等)是递归的。结构体中的指针指向了自身的类型,是一种递归的数据结构。对于这类递归结构,采用递归的方法编写算法既方便,又高效。
    • 问题的求解方法是递归的, 如 Hanoi 问题的求解。
  • 递归模型,递归模型是递归算法的抽象,它反映了一个递归问题的递归结构。以求解n!为例,其递归模型如下:

    	fun(1) = 1                    (1)
    	fun(n) = n*fun(n-1)    n > 1  (2)
    

递归模型一般由递归出口递归体组成。递归出口给出了递归的终止条件,递归体给出了fun(n)的值与fun(n-1)的值之间的联系。

  • 递归算法的执行过程,
    • 一个正确的递归程序虽然每次调用的是相同的子程序,但它的参量、输入数据等均有变化。
    • 在正常的情况下,随着调用的不断深入,必定会出现调用到某一层的函数时,不再执行递归调用而终止函数的执行,遇到递归出口便是这种情况。
    • 递归调用是函数嵌套调用的一种特殊情况,即它是调用自身代码。也可以把每一次递归调用理解成调用自身代码的一个复制件。由于每次调用时,它的参量和局部变量均不相同,因而也就保证了各个复制件执行时的独立性。
    • 系统为每一次调用开辟一组存储单元,用来存放本次调用的返回地址以及被中断的函数的参量值。这些单元以系统栈的形式存放,每调用一次进栈一次,当返回时执行出栈操作,把当前栈顶保留的值送回相应的参量中进行恢复,并按栈顶中的返回地址,从断点继续执行。
    • 每递归调用一次,就需进栈一次,最多的进栈元素个数称为递归深度,当n越大,递归深度越深,开辟的栈空间也越大。每当遇到递归出口或完成本次执行时,需退栈一次,并恢复参量值,当全部执行完毕时,栈应为空。
      在这里插入图片描述
    • 在递归函数执行时,形参会随着递归调用发生变化,但每次调用后会恢复为调用前的形参,将递归函数的非引用型形参的取值称为状态。递归函数的引用型形参在执行后会回传给实参,有时类似全局变量,不作为状态的一部分,在调用过程中状态会发生变化,而一次调用后会自动恢复为调用前的状态。

归纳起来,递归调用的实现是分两步进行的,第一步是分解过程,即用递归体将“大问题”分解成“小问题”,直到递归出口为止,然后进行第二步的求值过程,即已知“小问题”,计算“大问题”。

  • 递归算法设计,获取递归模型:
    • 对原问题f(sn)f(s_n)进行分析,抽象出合理的“小问题”f(sn1)f(s_{n-1})(与数学归纳法中假设n=k-1时等式成立相似);
    • 假设f(sn1)f(s_{n-1})是可解的,在此基础上确定f(sn)f(s_n)的解,即给出f(sn)f(s_n)f(sn1)f(s_{n-1})之间的关系(与数学归纳法中求证n=k时等式成立的过程相似);
    • 确定一个特定情况(如f(1)f(1)f(0)f(0))的解,由此作为递归出口(与数学归纳法中求证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;
}

结果显示:
在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!