遞歸的概念html
棧的一個典型應用是程序設計中的遞歸過程的設計與實現,用棧來保存調用過程當中的參數和返回地址。ios
遞歸過程(或函數)就是子程序或函數中直接或間接的調用本身。算法
遞歸定義實例:編程
1.某人祖先的遞歸定義:數組
某人的雙親是他的祖先[基本狀況]函數
某人祖先的雙親一樣是某人的祖先[遞歸步驟]性能
2.階乘函數spa
3.斐波那契數列設計
通常遞歸分爲直接遞歸和間接遞歸code
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 }