递归的概念
栈的一个典型应用是程序设计中的递归过程的设计与实现,用栈来保存调用过程中的参数和返回地址。
递归过程(或函数)就是子程序或函数中直接或间接的调用自己。
递归定义实例:
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 ",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 ", 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 }