递归的概念

  栈的一个典型应用是程序设计中的递归过程的设计与实现,用栈来保存调用过程中的参数和返回地址。

递归过程(或函数)就是子程序或函数中直接或间接的调用自己。

  递归定义实例:

  1.某人祖先的递归定义:

某人的双亲是他的祖先[基本情况]

某人祖先的双亲同样是某人的祖先[递归步骤]

  2.阶乘函数

       

   3.斐波那契数列

 

 

   一般递归分为直接递归间接递归

 

                     

 

 

 

 

  4.Ackerman函数

  当两个连续函数都趋近于无穷时,我们常用洛必达法则来比较它们趋向无穷的快慢。函数的阶越高,

它趋向无穷的速度就越快。定义在正整数域上的函数中,n!趋向于正无穷的速度非常快,所以在算法设计中

如果出现这样的时间复杂度就非常糟糕。logn趋向无穷的速度则非常慢。

   而Ackerman函数可以比n!的增长速度快得多,比logn的增长速度慢的多。同时,并不是所有的

递归函数都有通项公式,Ackerman函数就是一个例子,它是双向递归函数,有两个自变量(独立),定义如下:

  

              Ack(0,n)=n+1 , n>=0;

             

              Ack(m,0)=Ack(m-1,1) , m>0;

             

              Ack(m,n)=Ack(Ack(m-1,n),n-1) , n,m>0

 1 #include <stdio.h>
 2 
 3 #include <stdlib.h>
 4 
 5 int   ack(int  m, int   n)
 6 
 7 {
 8 
 9     int    z;
10 
11     if (m == 0)
12 
13          z = n + 1; //出口
14 
15     else if (n == 0)
16 
17          z = ack(m - 1, 1);  //形参m降阶
18 
19     else
20 
21          z = ack(m - 1, ack(m, n - 1)); //对形参m,n降阶
22 
23     return z;
24 
25 }
26 
27         
28 
29 void main() {
30 
31      int  m, n;
32 
33     scanf("%d%d", &m, &n);
34 
35     printf("Ack(%d,%d)=%d\n",m,n, ack(m, n));
36 
37  
38 
39 } 

 

 

  递归过程的内部实现

  从递归调用的过程可知,它们刚好符合后进先出的原则。因此,利用栈实现递归过程再合适不过。

下面以求n!为例,介绍它的递归实现过程。

  递归算法为

  

 1 int fact(int N)
 2 
 3 {
 4 
 5 int result;
 6 
 7 if(N == 0)
 8 
 9 result = 1;
10 
11 else
12 
13 result = fact(n-1)*n  //递归调用自身
14 
15 return result;
16 
17 }

 

   在递归执行过程中,需要一个栈LS保存递归调用前的参数及递归的返回地址。参数为fact中的现行值,返回地址为递归语句的下一语句入口地址。设第1次调用的返回地址为p1,第2次调用的返回地址为p2, … 如图1所示

 具体实现为

(1)遇递归调用时,将当前参数与返回地址保存在LS中;

(2)遇递归返回语句时,将LS栈顶的参数及返回地址一并弹出,按当前返回地址继续执行

 

 

  分析递归程序的运行机制,递归用需要1-7的过程,而非递归只需要4-7过程,可见递归程序的运行效率并不高,

即并不能节约运行时间,相反,由于保存大量的递归调用的参数和返回地址,还要消耗一定的时间,再加上空间上的消耗,

同非递归递归函数相比毫无优势而言。但是尽管递归在时空方面不占优势,但有编程方便,结构清楚,逻辑结构严谨等优势。

递归的致命缺点是时空性能不好,但是我们可以用非递归方法来解决递归问题,从而改善程序的时空性能。

递归消除

       递归的消除有两种:简单递归消除和基于栈的递归消除

  1.简单递归消除

  尾递归是指递归调用用语句只有一个, 且处于算法的最后,尾递归是单向递归的特例. n!递归算法是尾递归的一个典型例子.

  对于尾递归,当递归返回时, 返回到上一层递归调用语句的下一语句时已到程序的末尾, 所以不需要栈LS保存返回地址。

  2.基于栈的递归消除

  具体方法:将递归算法中的递归调用语句改成压入操作;将递归返回语句改为弹出操作。

sqstack.h:

https://www.cnblogs.com/mwq1024/p/10146943.html

//计算n!不需要保存返回地址 原因上面说了

 1 #include <stdio.h>
 2 
 3 #include <stdlib.h>
 4 
 5 #include "sqstack.h" //引入顺序栈储存结构及其基本操作
 6 
 7  
 8 
 9 int fact(int N) {
10 
11     int result = 1;
12 
13     SqStack s,*S;
14 
15     S = &s;
16 
17     StackType *e;
18 
19     e = (StackType*)malloc(sizeof(StackType));
20 
21     InitStack(S);
22 
23     while (N)
24 
25     {
26 
27          Push(S, N);
28 
29          N--;
30 
31     }
32 
33     while (!EmptyStack(S))
34 
35     {
36 
37          result *= *(S->top - 1);
38 
39          Pop(S, e);
40 
41     }
42 
43     return result;
44 
45    
46 
47 }
48 
49 void main()
50 
51 {
52 
53     int n;
54 
55     printf("请输入n的阶数:");
56 
57     scanf("%d", &n);
58 
59     printf("result is %d\n", fact(n));
60 
61 }

 

    利用栈解决汉诺塔问题

    把三个塔座看成三个栈,入栈和出栈就相当于移动盘子。

汉诺塔算法的非递归实现C++源代码

  

  1 #include <iostream>
  2 
  3 using namespace std;
  4 
  5 //圆盘的个数最多为64
  6 
  7 const int MAX = 64;
  8 
  9 //用来表示每根柱子的信息
 10 
 11 struct st{
 12 
 13 int s[MAX]; //柱子上的圆盘存储情况
 14 
 15 int top; //栈顶,用来最上面的圆盘
 16 
 17 char name; //柱子的名字,可以是A,B,C中的一个
 18 
 19  
 20 
 21 int Top()//取栈顶元素
 22 
 23 {
 24 
 25 return s[top];
 26 
 27 }
 28 
 29 int Pop()//出栈
 30 
 31 {
 32 
 33 return s[top--];
 34 
 35 }
 36 
 37 void Push(int x)//入栈
 38 
 39 {
 40 
 41 s[++top] = x;
 42 
 43 }
 44 
 45 } ;
 46 
 47 long Pow(int x, int y); //计算x^y
 48 
 49 void Creat(st ta[], int n); //给结构数组设置初值
 50 
 51 void Hannuota(st ta[], long max); //移动汉诺塔的主要函数
 52 
 53 int main(void)
 54 
 55 {
 56 
 57 int n;
 58 
 59 cin >> n; //输入圆盘的个数
 60 
 61 st ta[3]; //三根柱子的信息用结构数组存储
 62 
 63 Creat(ta, n); //给结构数组设置初值
 64 
 65 long max = Pow(2, n) - 1;//动的次数应等于2^n - 1
 66 
 67 Hannuota(ta, max);//移动汉诺塔的主要函数
 68 
 69 system("pause");
 70 
 71 return 0;
 72 
 73 }
 74 
 75 void Creat(st ta[], int n)
 76 
 77 {
 78 
 79 ta[0].name = 'A';
 80 
 81 ta[0].top = n-1;
 82 
 83 //把所有的圆盘按从大到小的顺序放在柱子A上
 84 
 85 for (int i=0; i<n; i++)
 86 
 87 ta[0].s[i] = n - i;
 88 
 89 //柱子B,C上开始没有没有圆盘
 90 
 91 ta[1].top = ta[2].top = 0;
 92 
 93 for (int i=0; i<n; i++)
 94 
 95 ta[1].s[i] = ta[2].s[i] = 0;
 96 
 97 //若n为偶数,按顺时针方向依次摆放 A B C
 98 
 99 if (n%2 == 0)
100 
101 {
102 
103 ta[1].name = 'B';
104 
105 ta[2].name = 'C';
106 
107 }
108 
109 else //若n为奇数,按顺时针方向依次摆放 A C B
110 
111 {
112 
113 ta[1].name = 'C';
114 
115 ta[2].name = 'B';
116 
117 }
118 
119 }
120 
121 long Pow(int x, int y)
122 
123 {
124 
125 long sum = 1;
126 
127 for (int i=0; i<y; i++)
128 
129 sum *= x;
130 
131 return sum;
132 
133 }
134 
135 void Hannuota(st ta[], long max)
136 
137 {
138 
139 int k = 0; //累计移动的次数
140 
141 int i = 0;
142 
143 int ch;
144 
145 while (k < max)
146 
147 {
148 
149 //按顺时针方向把圆盘1从现在的柱子移动到下一根柱子
150 
151 ch = ta[i%3].Pop();
152 
153 ta[(i+1)%3].Push(ch);
154 
155 cout << ++k << ": " <<
156 
157 "Move disk " << ch << " from " << ta[i%3].name <<
158 
159 " to " << ta[(i+1)%3].name << endl;
160 
161 i++;
162 
163 //把另外两根柱子上可以移动的圆盘移动到新的柱子上
164 
165 if (k < max)
166 
167 { //把非空柱子上的圆盘移动到空柱子上,当两根柱子都为空时,移动较小的圆盘
168 
169 if (ta[(i+1)%3].Top() == 0 ||
170 
171 ta[(i-1)%3].Top() > 0 &&
172 
173 ta[(i+1)%3].Top() > ta[(i-1)%3].Top())
174 
175 {
176 
177 ch = ta[(i-1)%3].Pop();
178 
179 ta[(i+1)%3].Push(ch);
180 
181 cout << ++k << ": " << "Move disk "
182 
183 << ch << " from " << ta[(i-1)%3].name
184 
185 << " to " << ta[(i+1)%3].name << endl;
186 
187 }
188 
189 else
190 
191 {
192 
193 ch = ta[(i+1)%3].Pop();
194 
195 ta[(i-1)%3].Push(ch);
196 
197 cout << ++k << ": " << "Move disk "
198 
199 << ch << " from " << ta[(i+1)%3].name
200 
201 << " to " << ta[(i-1)%3].name << endl;
202 
203             }
204 
205         }
206 
207     }
208 
209 }  

 

posted on 2019-03-22 21:46  0xcreed  阅读(1426)  评论(0编辑  收藏  举报