本文目录
0 前言
递归和回溯不是一个数据结构,但是它们是很经典很实用的经典算法,使用递归和回溯可以更加简洁高效的解决我们的问题。
1 递归
1.1 什么是递归
任何调用自身的函数称为递归。用递归方法求解问题,要点在于递归函数调用自身去解决一个规模比原始问题小一些的问题。这个过程称为递归步骤。递归步骤会导致更多的递归调用。因此,保证递归能够终止是很重要的。每次函数都会用笔原问题规模更小的问题来调用自身。问题随着规模不断变小必须能最终收敛到基本情形。
1.2 为什么要用递归
在程序中,递归代码通常比迭代代码更加简洁易懂。一般来说,在编译或解释时,循环会转化为递归函数。当任务能够被相似的子任务定义时,采用递归处理十分有效。例如,排序,搜索和遍历等问题往往有简洁的递归解决方案。
1.3 递归函数的格式
递归函数在执行一个任务时,需要调用函数自身来完成一些子任务。在某些时候,函数不需要继续调用函数自身就可以完成当前子任务。函数不再递归的情况称作基本情形。而函数调用自身来执行子任务的情况就称作递归情形。递归的格式如下:
if(判断是否为基本情形){
return 该基本情形时函数的值;
}else if(判断是否为另一种基本情形){
return 该基本情形时函数的值;
}else{
//递归情形
return (执行某些工作并递归调用);
}
1.4 递归和迭代
递归通过类似镜像的方式来解决问题。当问题没有明显的答案是,递归方法通过简化问题来解决它。但是,每次递归调用都会增加开销(栈空间开销)。递归和迭代对比描述如下:
1.4.1 递归
(1)当到达基本情形时,递归终止;
(2)每次递归调用都需要额外的空间用于栈帧(内存)开销;
(3)如果出现无穷递归,程序可能会耗尽内存,并出现栈溢出;
(4)某些问题采用递归方法更容易解决。
1.4.2 迭代
(1)当循环条件为假时,迭代终止;
(2)每次迭代不需要任何额外的空间开销;
(3)由于没有额外的空间开销,所有若出现死循环,则程序会一直循环执行;
(4)采用迭代求解问题可能没有递归解决方案那样显而易见。
1.5 递归算法的经典用例
常常需要用到递归来辅助解决问题有以下方面:
(1)斐波那契数列、阶乘;
(2)归并排序、快速排序;
(3)二分查找;
(4)树的遍历和许多树问题:中序遍历、前序遍历、后序遍历;
(5)图的遍历:深度优先搜索、广度优先搜索;
(6)动态规划例子;
(7)分治算法;
(8)汉诺塔;
(9)回溯算法。
2 回溯
2.1 什么是回溯
回溯是一种采用分治策略进行穷举搜索的方法。
(1)有时求解一个问题的最好算法是尝试所有的可能性;
(2)这种方法通常很慢,但有标准工具能够辅助该过程;
(3)工具:生成基本对象的算法,例如二进制串、排序、组合、一般字符串等;
(4)通过剪枝回溯可以加速的穷举搜索。
2.2 回溯算法的经典用例
回溯算法经典用例有如下:
(1)二进制串:产生所有的二进制串;
(2)生成k进制串;
(3)揹包问题;
(4)广义字符串;
(5)哈密顿回路;
(6)图着色问题。