摘自《Java数据结构和算法》:
递归和栈之间有一种紧密的联系。事实上,大部分的编译器都是使用栈来实现递归的。正如我们曾提到过的,当调用一个方法的时候,编译器会把这个方法的所有参数及其返回地址(这个方法返回时到达的地方)都压入栈中,然后把控制转移给这个方法。当这个方法返回的时候,这些值退栈。参数消失了,并且控制权重新回到返回地址处。
大家应该都知道高斯的故事,说是有一天他的老师比特纳说:“今天给大家出一道算术题,谁算完,就可以先回家吃饭!”说完,他在黑板上写了一道算数题,题是这样的:“1+2+3+4+5+……+100=?”同学们都低头做题,老师开始看起了小说,可没等他看上两页,就听见小高斯说:“报告老师,我做完了。”比特纳头也没抬,就说:“这么快就做完了,肯定不对,回去重做。”高斯却说:“不会错的,肯定是5050。”老师听到这个答案非常惊讶,因为答案的确是5050。小高斯解释道:“我发现这许多数中,一头一尾两个数相加的和都是一样的,1加100是101;2加99是101;3加98是101……50加51也是101,就是说一共有50个101,因此很容易就能算出答案是5050。”
现在我们知道除了用这种方法,我们还可以尝试用递归来完成,当然在实际应用中简便简便,高斯的算法效率是非常高的,我们不想也没有必要去把问题搞复杂,但今天我们有人专门用递归来做,是为了给大家引入一个递归的例子,更好的去理解递归,为了再深入理解递归到底是怎么实现的,我们就来模拟一下这道数学题目的递归过程。
我们以累加4+3+2+1为例:
我们前面讲了递归与栈是因为我们在模拟递归的时候往往用到栈,所以在前面做一个铺垫。我们在用递归解4+3+2+1的时候,我们是不是一直先回溯到1,然后再递推到4?没错,递归就是一个回溯然后递推的过程,当我们回溯到1的时候我们返回,回溯到4的时候,进行累加以后程序整个递归过程也就结束了。我们给每一个值,比如在这里是4,3,2和1都另附上一个地址,由于递归到4的时候整个递归过程也就结束了,所以它显得有点特殊,特殊的我们当然要把它区分出来,“嘿,你是不是歧视我”,“嘿,我就是歧视你”,所以我们把4附上一个地址6(你也可以附上其它杂七杂八的数字,我这里赋为6),然后为它安家,也就是入栈,然后将其余的不特殊的数字也就是1,2,3,每次到这个数字,我们都给他们附上一个地址4,没错,地址和数字他们是一起的,生是它的字,死是它的魂,是一个整体,然后给他们安家,也就是入栈。
但是!好景不长!拆迁队来了,说是他们每个人的房子都是违建的!由于4比较特殊,家住的比较远,住的6号区,所以拆迁队暂时不去拆它家,一看好几个都住在4号区,立马火速敢去拆迁,1,2,3哭天喊地也没用,4在那边由于自己特殊,得意洋洋,却不知马上要向它下手,“因为咋是拆迁队啊!”拆迁队如此说道,拆迁队从人数最少的开始拆,所以从1开始拆,不过政府还是够意思的,给了点钱,1就走了,也就是出栈,拆迁队还要负责干一个活,统计走的人数,刚开始拆迁队出了点错误,默认走的人数初始值为1,所以这次不用拿笔写在名为“theAnswer”的纸上,然后又到了2,拆迁队怕出错,所以这次就在给钱拆迁之前,在1后面写上了+2,然后同理拆到3,在2后面写上+3,拆完4号区以后,就跑去6号区拆房,那4个人什么都没说啊,什么都没拿,突然在拆迁队的面前消失了,拆迁队吓得赶紧在3后面写上+4,自此以后,拆迁队再也不敢拆迁(递归结束)。
看完故事再看一下全过程:
Enter a number:4
现栈:
top→ n-->4 地址-->6
现栈:
top→ n-->3 地址-->4
n-->4 地址-->6
现栈:
top→ n-->2 地址-->4
n-->3 地址-->4
n-->4 地址-->6
现栈:
top→ n-->1 地址-->4
n-->2 地址-->4
n-->3 地址-->4
n-->4 地址-->6
现程序回溯到了1,弹出栈顶,theAnswer赋值为1
栈顶现在指向的是: 2
将栈顶元素的值加到theAnswer中,累加:
现在theAnswer的值为: 1+2=3
栈顶现在指向的是: 3
将栈顶元素的值加到theAnswer中,累加:
现在theAnswer的值为: 3+3=6
栈顶现在指向的是: 4
将栈顶元素的值加到theAnswer中,累加:
现在theAnswer的值为: 6+4=10
所以最终累计的结果就是:10
将数字 跟 地址附在一起然后安家的theNumber类:
/**
* 带n和地址的家
* @author Administrator
*
*/
public class Number {
public int n;
public int returnAddress;
public Number(int nn,int ra){
n = nn;
returnAddress = ra;
}
}
安家当然得先有个家———
定义一个theHouse类,其实就是个栈,本质是个数组:
/**
* House类栈
* @author Administrator
*
*/
public class House {
private Number[] theStack;
private int maxSize;
private int top;
public House(int m) {
maxSize = m;
theStack = new Number[maxSize];
top=-1;
}
//入栈
public void push(Number p) {
theStack[++top] = p;
}
//出栈
public Number pop() {
return theStack[top--];
}
//获取栈顶元素
public Number peek() {
if(top!=-1)
return theStack[top];
else return null;
}
public void display() {
int i=top;
System.out.println("现栈:");
if(i==top) {
System.out.print("top→ ");
System.out.println("n-->"+theStack[i].n+" 地址-->"+theStack[i].returnAddress);
}
for(i=top-1;i>=0;i--) {
System.out.println(" n-->"+theStack[i].n+" 地址-->"+theStack[i].returnAddress);
}
System.out.println();
}
}
拆迁队要来拆迁了!
import java.io.*;
public class StackTriangleApp {
static int theFirst;//输入值
static int theAnswer;//答案
static int codePart;//第几个步骤
static House theHouse; //创建一个栈
static Number theNumber;//保留特定n值和其地址
public static void main(String[] args) throws IOException{
System.out.print("Enter a number:");
theFirst = getInt();
accumulate();
System.out.println("所以最终累计的结果就是:"+theAnswer);
}
public static void accumulate() {
theHouse = new House(1000);
codePart = 1 ;
while(step()==false);
}
//实现步骤
public static boolean step() {
switch(codePart) {
case 1:
theNumber = new Number(theFirst,6);//将第一个元素入栈,地址设为6,当地址为6时,完成所需
theHouse.push(theNumber);//入栈
codePart = 2;//跳到2判断其是否为1(我们有可能输入的是1)
break;
case 2:
theNumber = theHouse.peek();
theHouse.display();
if(theNumber.n==1) {//如果是栈顶值为1,则将theAnswer赋为1,跳到步骤5
System.out.println("现程序回溯到了1,弹出栈顶,theAnswer赋值为1\n");
theAnswer = 1;
codePart = 5;
}else
codePart = 3;//跳到步骤3入栈
break;
case 3:
Number newParams = new Number(theNumber.n-1,4);//地址设为4
theHouse.push(newParams);//入栈
codePart = 2;//回到步骤2,判断栈顶是否到1
break;
//先加后出栈
case 4:
theNumber = theHouse.peek();//获取栈顶元素
System.out.println("将栈顶元素的值加到theAnswer中,累加:");
System.out.print("现在theAnswer的值为: "+theAnswer+"+"+theNumber.n+"=");
theAnswer = theAnswer+theNumber.n;//将值累加到theAnswer中
System.out.println(theAnswer+"\n");
codePart = 5;//跳到步骤5,出栈
break;
case 5:
theNumber = theHouse.peek();//获取栈顶元素
codePart = theNumber.returnAddress;//获取地址,除了第一个元素地址为6(为6的时候结束运行),其余的元素我们都放在地址4里头
theHouse.pop(); //出栈
if(theHouse.peek()!=null)
System.out.println("栈顶现在指向的是: "+theHouse.peek().n);
break;
case 6:
return true;
}
return false;
}
//输入
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);
}
}
结合代码对照上面的全过程,会容易理解得多。
搞了那么一大圈,还不是就是用栈搞定,所以下面是简化版本(可以更简化,但是要用到栈,我们的目的是说明递归的过程)
import java.io.*;
public class StackTriangleApp {
static int theNumber;//输入值
static int theAnswer;//答案
static StackX theStack; //创建一个栈
public static void main(String[] args) throws IOException{
System.out.print("Enter a number:");
theNumber = getInt();
accumulate();
System.out.println("所以最终累计的结果就是:"+theAnswer);
}
public static void accumulate() {
StackX theStack = new StackX(1000);
while(theNumber>0)
theStack.push(theNumber--);
while(!theStack.isEmpty())
theAnswer+=theStack.pop();
}
//输入
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);
}
}