棧與遞歸

  遞歸的概念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 }
相關文章
相關標籤/搜索