《数据结构与算法》课程笔记 第三章 递归与分治

1. 递归

1 递归的定义

  • 子程序(或函数)直接调用自己或通过一系列调用语句间接调用自己,称为递归。
  • 递归是一种描述问题和解决问题的基本方法。 

递归的基本思想:问题分解

当问题比较大,比较复杂,但如果问题本身不变,数据量很小时很容易解决。大问题可以分解为小问题,每个大问题与小问题之间又具备相似性。当小问题很小,小到能够直接解决,然后用小问题来解大问题。这就是递归方法。 

递归的要素:

  • 递归边界条件:确定递归到何时终止,也称为递归出口
  • 递归模式:大问题是如何分解为小问题的,也称为递归体

2 递归实例

1) 阶乘函数

int fact(int n)
{
	if(n == 0)
		return 1;
	else
		return n * fact(n-1);
}

求解过程:

递归过程与递归工作栈:

  • 递归过程在实现时,需要自己调用自己。
  • 层层向下递归,返回次序正好相反。

  • 递归算法的效率较低;因为返回次序相反,可用栈或循环的非递归方法来实现递归。
//阶乘函数:循环,非递归
int fact1(int n)
{
	if(n == 0)
		return 1;
	else
	{
		int f = n;
		while(--n >= 1)
			f *= n;
		return f;
	}
}

 2) Fabonacci 数列求解

int fabonacci(int n)
{
	if(n <= 1)
		return n;
	else
		return (fabonacci(n-1)+fabonacci(n-2));
}

递归关系只在 return 时递归,是尾递归,可以改为循环语句。 

//Fabonacci 数列:循环,非递归
int fabo(int n)
{
	int pre, now, next, j;
	if(n <= 1)
		return n;
	else
	{
		pre = 0;
		now = 1;
		for(j=2;j<=n;j++)
		{
			next = pre + now;
			pre = now;
			now = next;
		}
		return next;
	}
}

3) 回文串

bool palindrome(string &s, unsigned h, unsigned t)
{
	if(h >= t)
		return 1; //空串或长度为1
	if(tolower(s[h]) == tolower(s[t]))
		return palindrome(s,h+1,t-1);
	else
		return 0;
}

递归关系只在 return 时递归,是尾递归,可以改为循环语句。 

//回文串:循环,非递归
bool palin(string &s)
{
	int h = 0;
	int t = strlen(s)-1;
	while(h <= t)
	{
		if(tolower(s[h]) != tolower(s[t]))
			return 0;
		h++;
		t--;
	}
	return 1;
}

2. 分治递归

1 算法的基本思想

  • 将要求解的较大规模的问题分割成 k 个更小规模的子问题。

  • 对这 k 个子问题分别求解。如果子问题的规模仍然不够小,则再划分为 k 个子问题,如此递归的进行下去,直到问题规模足够小,很容易求出其解为止。 
  • 将求出的小规模的问题的解合并为一个更大规模的问题的解,自底向上逐步求出原来问题的解。

2 分治法的适用条件

  • 该问题的规模缩小到一定的程度就可以容易的解决。
  • 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
  • 利用该问题分解出的子问题的解可以合并为该问题的解(完全合并)。(能否利用分治法完全取决于问题是否具有这条特征,如果具备了前两条特征,而不具备第三条特征,则可以考虑贪心算法或动态规划。)
  • 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。(这条特征涉及到分治法的效率,如果各子问题是不独立的,一般用动态规划较好。)

3 分治法的基本步骤

  • 平衡子问题。子问题的规模大致相等时算法的效率最高。 

4 分治法的复杂性分析

5 实例

1) 打印 n 个数的全排列

算法思想:

#include <iostream>
using namespace std;

void Swap(int &a, int &b)
{
	int x = a;
	a = b;
	b = x;
}

void PrintPermutation(int a[], int k, int n)
{
	if (k == n) //排完n个数则输出
	{
		for(int i=1;i<=n;++i)
		{
			cout<<a[i]<<" ";
		}
		cout<<endl;
		return;
	}
	else
	{
		for(int i=k;i<=n;++i)
		{
			Swap(a[k],a[i]);
			PrintPermutation(a,k+1,n);
			Swap(a[k],a[i]); //类似回溯法,返回到上一步
		}
	}
}

int main()
{
	int *a, len;
	cout<<"len:"<<endl;
	cin>>len;
	a = new int[len+1];
	if(!a)
		return -1;
	for(int i=1;i<=len;i++)
		a[i] = i;
	PrintPermutation(a,1,len);

	system("pause");
	return 0;
}
  • 时间复杂度:O(n!)

2) 棋盘覆盖

                                                

  •  时间复杂度:O(n)

3) 买票问题

题目:假设有2n个人买票,票价50元/张,其中有n个人拿50元,n个人拿100元。请问怎么排队,才能够使得卖票处始终有钱找?能够找出所有的有钱找的排队方案吗?

猜你喜欢

转载自blog.csdn.net/sinat_35483329/article/details/86624087