由于本人对于递归、迭代、回溯、DFS、迭代加深搜索等算法概念的模糊不清,导致每次做题时都没有一个清晰的思路,亦或是受到这种模糊概念的影响,导致做类似题目时,代码混乱、不清晰,所以在此,本人专门总结一下相关的内容,以来是理清自己的思路,二来也是给于我有相似困扰的朋友一个帮助。
这篇文章针对的是对上述易混淆知识点已经有了一定的功底的算法竞赛爱好者,是一片总结助记性质的文章而非一篇入门性质的文章。
因鄙人才疏学浅,本文错误及疏漏之处在所难免,还望各位前辈指正。
可以说,递归几乎是各类暴力搜索法的基础。所以,我们以递归来展开这一文。
一个正式定义是这样的:递归指由一种(或多种)简单的基本情况定义的一类对象或方法,并规定其他所有情况都能被还原为其基本情况。
我们通过一个例子来理解递归:
例如:你怎样才能移动100个箱子?
答案:
1.你移动一个箱子,并记下它移动到的位置。
2.再去解决较小的问题:你怎样才能移动99个箱子?
3.最终,你的问题将变为怎样移动一个箱子,而这是你已经知道该怎么做的。
在这个例子中,基本情况是怎样移动一个箱子,其他情况是怎样移动100个箱子、怎样移动99个箱子以此类推。显然,所有其他情况均可以被还原为基本情况,这是完全符合定义的。
有上面的讨论我们知道,掌握递归,只要明确递归的两个内容:
1.基本情况;
2.可被还原为基本情况的其他情况。
一个函数在它的函数体内调用自身称为递归调用。这种函数称为递归函数。
递归函数的重点就是在于简化工作。
其实,看待问题的眼光放的大一些,我们发现,任何事情都是可以究其根源的。抛开事物出现的早晚来看,我们今天要讨论的问题似乎都是以递归思想为根源产生的。
接下来我们更进一步的了解其他知识点,首先是与递归思想及其相似的迭代。
迭代是重复反馈过程的活动,其目的是为了接近并到达所需的目标或结果。每一次对过程的重复被称为一次“迭代”,而每一次迭代得到的结果会被用来作为下一次迭代的初始值。
通过这个定义,我们很难看出他和递归究竟有什么区别,但是,稍加留意,我们会发现粗体字所传达给我们的信息,即迭代是程序中对一组指令(或一定步骤)的重复,它更像是一种循环。
迭代的一个很显著的特征就是——可变状态,就是说,对于某一任务来说,任务的状态随着每一次重复都会做出改变,当状态不再改变,也就说明任务被解决。
下面举一个简单的例子来说明:
int i,a=0;
for(int i=0;i<3;i++){
a=a+i;
}
对于这段代码来说,他就是一个迭代。他的目标是计算a+0+1+2,为了接近这个目标,我们对a=a+i这个过程进行重复,并每次改变迭代的状态i。
从程序设计的角度上看,递归是对自身的重复调用,而迭代是对某一步骤的重复调用。
这一点在二分法的不同实现上得到了很好的体现。
我们来看:(再此我们不过多的纠结程序的具体实现目的以及它语法的正确性)
1.递归实现
int f(int L,int R) {
int MID=(L+R)>>1;
if(check(MID)==0) return MID;
else if(check(MID)==1) return f(L,MID);
else if(check(MID)==2) return f(MID,R);
}
我们顺便回顾一下,这里的基本情况是:check(MID)==0,我们的解决方法是 return MID;;其他情况是check(MID)!=0,我们的解决方法是将他还原为基本情况。
2.迭代实现
while(L>1;
if(check(MID)==0) answer=MID;
else if(check(MID)==1) R=MID;
else if(check(MID)==2) L=MID;
}
他的目标是计算当check(MID)==0时的answer,为了接近这个目标,我们要对 MID=(L+R)>>1;这个过程进行重复,并每次改变迭代的状态L和R。
我们举这个例子的目的主要是为了让大家区分开迭代和递归的特征:
迭代:步骤的重复调用。
递归:自身的重复调用。
递归思想是一种强大而有力的工具,由递归而延伸出来了(并不能说是由递归创造出来的)一系列的算法:
(I)在暴力求解中:生成排列、生成子集、回溯算法;
(II)在高效算法设计中:排序、检索、分治。
对于迭代思想,他在计算机科学领域更多的是同递归思想相结合,亦或是提高递归思想的实现效率。比如,迭代加深搜索就是两者结合的产物,深度的重复改变过程是迭代,搜索的过程是递归;又比如,上面提到的二分的两种实现,当|R-L|十分大的时候,计算机便无法承受递归所带来的系统开销,那么此时就可以使用迭代的思想实现二分法来提高算法的实现效率。
接下来我们仅介绍两个递归性质十分明显算法:回溯算法和深度优先搜索算法,他们都是穷举法(暴力搜索法)的其中一种。
对于以递归思想构造的暴力搜索方法来说,我们总是在递归的过程中不断的生成所有可能的解,并可以不断的去检查解的正确性,生成和检查两者的有机结合减少了暴力搜索中不必要的枚举步骤。
回溯算法从某种程度上可以看作是递归枚举。因为,对于某个问题,当我们把问题分成若干步骤并进行递归求解的时,如果当前的步骤没有合法的选择,也就是说,检查到了不必要的枚举步骤,此时递归函数将返回上一级递归函数,此时这种现象就是回溯,这种算法就是回溯算法。
从狭义上讲,DFS算法是图论的一种搜索算法。
而从广义上讲,DFS算法就是回溯算法在图论上的应用。
DFS是沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所有边都己被搜寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。