棧有一個很重要的應用:在程序設計語言中講了遞歸。那么什么是遞歸呢?當你往鏡子前面一站,鏡子里面就有一個你的像。但你試過兩面鏡子一起照嗎?如果A、B兩面鏡子互相面對面放着,你往中間一站,嘿,兩面鏡子都有你的千百個“化身”,為什么會有這么奇妙的現象呢?原來,A鏡子里有B鏡子的像,B鏡子里也有A鏡子的像,這樣反反復復,就會產生一連串的“像中像”。這是一種遞歸現象。我們先來看一個經典的遞歸例子:斐波那契數列(Fibonacci)。為了說明這個數列,這位斐老還舉了一個很形象的例子。
一、 棧的應用——遞歸
在高級語言中,調用自己和其他函數並沒有本質的不同。我們把一個直接調用自己或通過一系列的調用語句間接地調用自己的函數,稱做遞歸函數。
函數怎么可以自己調用自己?聽起來有些難以理解,不過你可以不要把一個遞歸函數中調用自己的函數看做是在調用自己,而就當它是在調用另一個函數。只不過,這個函數和自己長得一樣而已。當然,寫遞歸程序最怕的就是陷入永不結束的無窮遞歸中,所以,每個遞歸都需要一個退出遞歸的條件。遞歸使用的是選擇結構。遞歸能使程序的結構更清晰、更簡潔、更容易讓人理解,從而減少讀懂代碼的時間。但是大量的遞歸調用會建立函數的副本,會消耗大量的時間和內存。那么我們講了那么多遞歸的內容,和棧有什么關系呢?程序設計基礎階段我們已經了解了遞歸是如何執行它的前行和退回的。遞歸過程退回的順序是它前行順序的逆序。顯然這符合棧的存儲方式。簡單的說,就是在前行階段,對於每一層遞歸,函數的局部變量、參數值以及返回地址都被壓如棧中。在退回階段,位於棧頂的局部變量、參數值和返回地址被彈出,用於返回調用層次中執行代碼的其余部分,也就是恢復了調用的狀態。當然,對於現在的高級語言,這樣的遞歸問題是不需要用戶來管理這個棧的,一切都由系統代勞就可以了。
- 1. ACM算法:n的階乘
n的階乘意思是:從一乘到n,是一個連續的乘法問題。其中n是自然數。
實現如下:
int fun(int n) { if(n==1) { return 1; } else { return fun(n-1)*n; } }
- 2. ACM算法:斐波那契數列實現
說如果兔子在出生兩個月后,就有繁殖能力,一對兔子每個月能生出一對小兔子來。假設所有兔都不死,那么一年以后可以繁殖多少對兔子呢?
我們拿新出生的一對小兔子分析一下:第一個月小兔子沒有繁殖能力,所以還是一對,兩個月后,生下一對小兔子數共有兩對;三個月后,老兔子又生下一對,因為小兔子還沒有繁殖能力,所以一共是三對····依次類推。
那么很簡單就推出1,1,2,3,5,8,13····構成了一個序列。這個數列有個十分明顯的特點,那是:前面相鄰兩項之和,構成了后一項。
int fun(int n)//計算斐波那契數列第n項的值 { if(n==1||n==2) { return 1; } else { return fun(n-1)+fun(n-2); } }
- 3. ACM算法:n的k次冪
int fun(int n,int k) { if(k==1) { return n; } else { return fun(n,k-1)*n; } }
- 4. ACM算法:字符串逆轉
void strReverse(char s[],int len) { if(len==1) { printf(“%c”,s[0]); } else { printf(“%c”,s[len-1]); strReverse(s,len-1); } }
二、 棧的應用
- 1. 進制轉換
在計算機中存儲的數據都是二進制,所以往往需要把十進制數據轉換成二進制,轉換的過程實際就是除2取余數,這其中我們可以看到最先求得余數實際是個位數,書寫一個數據的時候都是先書寫高位的數據,而后依次到個位。這正好和棧后進先出的特性吻合,因此可以使用棧來存儲。
例如:十進制的25轉換成2進制
25 25/2 25%2 n 0==n/2 y=n%2
25 12 1
12 6 0
6 3 0
3 1 1
1 0 1
根據最后得到的是高位,先除余得到的是個位,最后得到的二進制值是:11001
- 2. 括號匹配
判斷一個表達式的”(“和”)”是否匹配,思路是這樣的:遇到”(“則入棧,遇到”)”則從棧頂彈出”(“與之配成一對,當整個表達式掃描完畢時:
(1) 若棧內為空,則說明(與)是匹配的。
(2) 若表達式掃描完畢,棧內仍有(則說明左括號是多的。
(3) 若當)被掃描到,棧里卻沒有(能彈出了,說明)多,表達式中)也是多的。
- 3. 表達式求值
算法思想:
(1) 首先置操作數棧OPND為空棧,表達式的起始符#為運算符棧OPTR的棧底元素;
(2) 依次讀入表達式中的每個字符
若運算符是‘#’或棧頂是‘#’,結束計算,返回OPND棧頂值。
if(是操作數) → 則PUSH( OPND,操作數);
if(是運算符) → 則與OPTR棧頂元素進行比較,按優先級進行操作;
優先級操作細則如下:
① if棧頂元素<輸入算符,則算符壓入OPTR棧,並接收下一字符
② if棧頂元素=運算符但≠‘#’,則脫括號(彈出左括號)並收下一字;
③ if棧頂元素>運算符,則退棧、按棧頂計算,將結果壓入OPND棧。
④ 且該未入棧的運算符要保留,繼續與下一個棧頂元素比較!
表達式求值過程的描述:3*(7 – 2 )
棧的技能
/* Note:Your choice is C IDE */ #include "stdio.h" #include "stdlib.h" int e; #define MAX 5 struct person{ int xh; char name[20]; char jg[20]; char tel[20]; }; struct stack1{ struct person arr[MAX]; int top; }s1; struct stack2{ struct person arr[MAX]; int front; int rear; }s2; void menu1(){ printf("+++++++++++++++++++++\n"); printf("1.進棧\n"); printf("2.出棧\n"); printf("3.打印\n"); printf("4.退出\n"); printf("+++++++++++++++++++++\n"); } void menu2(){ printf("+++++++++++++++++++++\n"); printf("1.入隊\n"); printf("2.出隊\n"); printf("3.打印\n"); printf("4.退出\n"); printf("+++++++++++++++++++++\n"); } void push1(){ if(s1.top==MAX){ printf("溢出!\n"); }else{ s1.top++; printf("請輸入進棧的學生信息:\n"); printf("請輸入學生的學號:"); scanf("%d",&s1.arr[s1.top].xh); printf("請輸入學生的姓名:"); scanf("%s",s1.arr[s1.top].name); printf("請輸入學生的籍貫:"); scanf("%s",s1.arr[s1.top].jg); printf("請輸入學生的學號:"); scanf("%s",s1.arr[s1.top].tel); printf("進棧成功\n"); } } void pop1(){ if(s1.top<0){ printf("棧已經空了!\n"); }else{ e=s1.top; printf("學號:%d,姓名:%s,籍貫:%s,手機號:%s\n",s1.arr[e].xh,s1.arr[e].name,s1.arr[e].jg,s1.arr[e].tel); s1.top--; } } void push2(){ if((s2.rear+1)%MAX==s2.front){ printf("溢出!\n"); }else{ printf("請輸入進棧的學生信息:\n"); printf("請輸入學生的學號:"); scanf("%d",&s2.arr[s2.rear].xh); printf("請輸入學生的姓名:"); scanf("%s",s2.arr[s2.rear].name); printf("請輸入學生的籍貫:"); scanf("%s",s2.arr[s2.rear].jg); printf("請輸入學生的學號:"); scanf("%s",s2.arr[s2.rear].tel); s2.rear=(s2.rear+1)%MAX; printf("進隊成功\n"); } } void pop2(){ if(s2.rear==s2.front){ printf("隊列已經空了!\n"); }else{ e=s2.front; printf("學號:%d,姓名:%s,籍貫:%s,手機號:%s\n",s2.arr[e].xh,s2.arr[e].name,s2.arr[e].jg,s2.arr[e].tel); s2.front=(s2.front+1)%MAX; } } void fun1(){ int i,b,flag=0; for(;;){ menu1(); scanf("%d",&b); switch(b){ case 1: push1(); break; case 2: pop1(); break; case 3: if(s1.top<0){ printf("沒東西了!\n"); break; } for(i=0;i<=s1.top;i++){ printf("學號:%d,姓名:%s,籍貫:%s,手機號:%s\n",s1.arr[i].xh,s1.arr[i].name,s1.arr[i].jg,s1.arr[i].tel); } break; case 4: flag=1; break; } if(flag==1) { break; } } } void fun2(){ int i,b,flag=0; for(;;){ menu2(); scanf("%d",&b); switch(b){ case 1: push2(); break; case 2: pop2(); break; case 3: if(s2.rear==s2.front){ printf("沒東西了!\n"); break; } for(i=s2.front;i!=s2.rear;i=(i+1)%MAX){ printf("學號:%d,姓名:%s,籍貫:%s,手機號:%s\n",s2.arr[i].xh,s2.arr[i].name,s2.arr[i].jg,s2.arr[i].tel); } break; case 4: flag=1; break; } if(flag==1) { break; } } } void main() { int bh; s1.top=-1; s2.front=0; s2.rear=0; for(;;){ printf("1.棧\t\t2.隊\t\t3.退出\n"); printf("輸入編號:"); scanf("%d",&bh); switch(bh){ case 1: fun1(); break; case 2: fun2(); break; case 3: exit(0); default: printf("輸入的編號有誤!\n"); } } }