递归
递归原理
递归,很多语言都支持递归,它的本质就是利用栈去实现的。这里讨论用栈去实现递归,就是为了更深刻的理解递归的本质,同时也更深的理解了栈等数据结构的用法。
1 递归效率
递归VS循环
循环可以实现递归 但是循环效率更高
递归概念上更易理解
2 递归和栈
递归===栈=来实现===>非递归
可相互转化的三类解决方案
栈 循环 递归
原理例子1
public class Test {
public static void main(String[] args) throws IOException {
System.out.println("Please input a value which is bigger than 1:");
int i = getInt();
method(i);
}
public static String getString()throws IOException{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s =br.readLine();
return s;
}
public static int getInt() throws IOException{
String s = getString();
return Integer.parseInt(s);
}
public static void method(int i){
if(i==1) return ;
else{
System.out.println("Before :"+i);
method(i-1);
System.out.println("After :"+i);
}
}
}
输出结果
总结
简单的说,就是在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。
原理例子2:<归并排序>
代码:
public void recMergeSort(long[] workSpace, int lowerBound, int upperBound) {
System.out.println("low: "+lowerBound +" upper: "+upperBound);
// base case
// if range is one do not need to be sorted
if (lowerBound == upperBound)
return;
else {
int mid = (lowerBound + upperBound) / 2;
System.out.println("mid1: "+mid);
recMergeSort(workSpace, lowerBound, mid);
System.out.println("mid2: "+mid);
System.out.println("upper1: "+upperBound);
recMergeSort(workSpace, mid+1, upperBound);
System.out.println("upper2: "+upperBound);
}
}
递归与栈:
程序输出:
low |
0 |
upper |
2 |
<m1> |
mid1 |
1 |
|
|
|
low |
0 |
upper |
1 |
<m0> |
mid1 |
0 |
|
|
|
low |
0 |
upper |
0 |
|
mid2 |
0 |
|
|
|
upper1 |
1 |
|
|
|
low |
1 |
upper |
1 |
<m1> |
upper2 |
1 |
|
|
|
mid2 |
1 |
|
|
|
upper1 |
2 |
|
|
|
low |
2 |
upper |
2 |
<m2> |
upper2 |
2 |
|
|
|
递归方法的特征
递归实例
三角数字
数字序列
1 3 6 10 15 21。。。。
用循环查找第N项的值
public long method(int n){
int total = 0;
while(n>0){
total = total +n;
n--;
}
return total;
}
用递归查找第N项的值
第N项的值可以被看作是两部分的和:
到底发生了什么?
阶乘
变位字[A1]
变位字原理
选定 一个字符占第一位的位置.所有其他的字母全排列<递归>,轮换单调N次,以给每一个字母一个排在单词开头的机会.<rotate()>
变位字步骤
1 全排列最右边N-1个字母
2 轮换所有N个字母<轮换意味着所有字母左移一位,最左边的字母轮换到最右边字母后面.见图6.6>
3 重复上面步骤N次
变位字局部方法 Rotate()
变位字例子:
对于cat这个单词只有三个字母.轮换剩下两个字母只是简单的交换他们.
单词 |
是否显示单词 |
首字母 |
剩余字母 |
操作 |
Cat |
Y |
C |
At |
轮换AT |
Cta |
Y |
C |
TA |
轮换TA |
CAT |
N |
C |
AT |
轮换CAT |
Atc |
Y |
A |
Tc |
轮换TC |
ACT |
Y |
A |
CT |
轮换ct |
ATC |
N |
A |
TC |
轮换atc |
TCA |
Y |
T |
CA |
轮换CA |
TAC |
Y |
T |
AC |
轮换AC |
TCA |
N |
T |
CA |
轮换TCA |
CAT |
N |
C |
AT |
DONE |
如何来全排列最右边的n-1个字母?
通过调用自己。递归的doAnagram()方法把要被排列的单词大小作为这个方法的唯一参数.
这个单词被看成是这个完整单词的最右边的n个字母.
doAnagram()方法每调用自己一次.doAnagram()都会使这个词的字母比上一次少一个.
当被全排列的这个词的大小只剩下一个字母的时候,即出现基本情况终止条件发生,没有办法重新排列一个字母,所以方法立即返回.否则,方法把给定词的除了第一个字母外的所有字母进行全排列.然后轮换整个单词.单词大小为N时,这两个操作都执行N次.
核心代码:
图解:
递归的二分查找
二分查找原理
循环的二分查找方法
public int find(long key){
int low=0;
int high= nElems ;
int mid = (high+low)/2;
while(true){
//find the target value
if(arr[mid]==key) return mid;
//could not find the target value
else if (low>high) throw new RuntimeException("Could not find the target value");
//in the finding processing
else{
if(arr[mid]<key){
low = mid+1;
mid = (high+low)/2;
System.out.println("This is me 1");[A2]
}else{
high = mid-1;
mid = (high+low)/2;
System.out.println("This is me 2");[A3]
}
}
}
}
递归取代循环
汉诺塔
所有小盘子都必须先放在一个中介的塔座上,这个中介的塔座上自然形成了一颗子树.
递归的算法
归并排序
归并排序的缺点:
需要在储存器中有另一个大小等于被排序的数据项数目的数组.
如果初始数组几乎占满整个存储器.那么归并排序将不能工作.但是如果有足够的空间.
归并排序会是一个很好的选择.
归并排序的重要工具 :归并两个有序数组:
归并两个已经有序的数组.归并两个有序数组A和B,就生成了第三个数组C.
public void merge(
int[] arrA,int sizeA,
int[] arrB,int sizeB,
int[] arrC){
//define 3 indicators
int indexA=0,indexB=0,indexC=0;
//when both A & B are not empty
while(indexA!=arrA.length
&& indexB!=arrB.length){
if(arrA[indexA]<arrB[indexB])
arrC[indexC++] =arrA[indexA++];
else{
arrC[indexC++] =arrB[indexB++];
}
}
// A is not empty but B is aleardy empty
while(indexA!=arrA.length){
arrC[indexC++] =arrA[indexA++];
}
while(indexB!=arrB.length){
arrC[indexC++] =arrB[indexB++];
}
}
通过归并进行排序:递归负责不断减半割据数组,merge()负责归并被割据的两个数组.
核心思想:归并排序的思想是把一个数组分成两半.排序每一半.然后用merge()
把数组的两半归并成一个有序的数组.
递归的作用:把数组的每一半分成两个四分之一,把每一个四分之一的数组分成八分之一.对每一个八分之一的数组分成十六分之一.依次类推反复割据数组.直到得到的子数组只含有一个数据项<基值条件> .设定只有一个数据项的数组是有序的.
递归方法在每次调用自身方法的时候,通常某个参数的大小就会减小.并且方法每次返回时候参数又恢复到以前.
在mergeSort()方法中,每一次这个方法调用自身的时候排列都会被分成两部分.并且每一次返回时候,把两个较小的排列合并成一个更大的排列.
归并排序中的归并数组
起始条件:
lowPtr = lowerBound
highPtr = upperBound
mid = highPtr=1;
效率O[N*logN]
复制次数:N*logN
N个数据项有N-1 个步骤
对于一个包含8个数据项的数组,上图表明需要有24次<7个步骤>复制来进行排序.
.
比较次数
归并两个有序数组的比较次数
假设数据个数是2 的乘方.
Max比较次数= N-1
Avg比较次数= N/2
归并算法的比较次数
递归练习
背包问题:
假设背包精准的承重20 磅.并且有5个可以选入的数据项.以下组合就是可选的数据项:
8 磅,7磅,5磅.
如果需要计算机来解决这个问题,就需要给计算机更详细的指令.算法如下:
1 如果在这个过程中的任何时刻,选择的数据项的总和符合目标重量.-- 边界条件1
2 从选择第一个数据项开始.剩余的数据项的加和必须符合背包的目标重量减去第一个数据项的重量.
-à递归的新输入参数.
3 逐个地试每种剩余数据项的可能性.只要数据项的和大于目标重量的时候就停止添加数据项—边界条件2
4 如果没有组合合适的话,放弃第一个数据项,并且从第二个数据项开始再重复一遍整个过程.
à又一个递归的开始
5 继续从第三个数据项开始.如此下去直到你已经试过所有的组合.这时候知道有没有解决答案.
组合选择一支队伍:
组合数,从n个中取m个: n!/[(n-m)!m!]
A B C三个元素选取其中一个元素.
A B C D 四个元素选取其中一个
递归异常
Exception in thread "main" java.lang.StackOverflowError
at com.cici.recurrence.Power.method(Power.java:16)
at com.cici.recurrence.Power.method(Power.java:21)
at com.cici.recurrence.Power.method(Power.java:21)
原始代码
public long method(int x,int y){
if(y==1) {
System.out.println("Base Case "+y);
return x;
}
else{
return x*method(x,y--[A4] );
}
}