隊列是只允許在一端進行插入操作、而在另-端進行刪除操作的線性表。
棧
棧與隊列:棧是限定僅在表尾進行插入和刪除操作的線性表。
我們把允許插入和刪除的一端稱為棧頂(top),另一端稱為棧底(bottom),不含任何數據元素的棧稱為空棧。棧又稱為后進先出(Last In First Out)的線性表,簡稱LIFO結構。
問題:最先進棧的元素,是不是就只能是最后出棧呢?
不一定。棧對線性表的插入和刪除的位置進行了限制,并沒有對元素進出的時間進行限制,也就是說,在不是所有元素都進棧的情況下,事先進去的元素也可以出棧,只要保證是棧頂元素出棧就可以。
棧的抽象數據類型
對干棧來講,理論上線性表的操作特性它都具備,可由干它的特殊性,所以針對它在操作上會有些變化。特別是插入和刪除操作,我們改名為push和pop。
由于棧本身就是一個線性表,因此線性表的順序存儲和鏈式存儲,對于棧來說,也是同樣適用。
棧的順序存儲結構及實現
棧是線性表的特例,因此棧的順序存儲也是線性表順序存儲的簡化,我們簡稱為順序棧。
線性表是用數組來實現的,用數組的下標0作為棧底比較好,因為首元素都會存在棧底,變化量小。
我們定義一個top變量來指示棧頂元素在數組中的位置,它可以來回移動,意味著棧頂的top可以變大變小,若存儲棧的長度為StackSize,則棧頂位置top必須小于StackSize。當棧存在—個元素時,top等于0,因此通常把空棧的判定條件定為top等于-1。
#include "stdio.h"
#include "stdlib.h" #include "math.h"
#include "time.h"#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存儲空間初始分配量 */typedef int Status;
typedef int SElemType; /* SElemType類型根據實際情況而定,這里假設為int *//* 順序棧結構 */
typedef struct
{SElemType data[MAXSIZE];int top; /* 用于棧頂指針 */
}SqStack;Status visit(SElemType c)
{printf("%d ",c);return OK;
}/* 構造一個空棧S */
Status InitStack(SqStack *S)
{ /* S.data=(SElemType *)malloc(MAXSIZE*sizeof(SElemType)); */S->top=-1;return OK;
}/* 把S置為空棧 */
Status ClearStack(SqStack *S)
{ S->top=-1;return OK;
}/* 若棧S為空棧,則返回TRUE,否則返回FALSE */
Status StackEmpty(SqStack S)
{ if (S.top==-1)return TRUE;elsereturn FALSE;
}/* 返回S的元素個數,即棧的長度 */
int StackLength(SqStack S)
{ return S.top+1;
}/* 若棧不空,則用e返回S的棧頂元素,并返回OK;否則返回ERROR */
Status GetTop(SqStack S,SElemType *e)
{if (S.top==-1)return ERROR;else*e=S.data[S.top];return OK;
}/* 插入元素e為新的棧頂元素 */
Status Push(SqStack *S,SElemType e)
{if(S->top == MAXSIZE -1) /* 棧滿 */{return ERROR;}S->top++; /* 棧頂指針增加一 */S->data[S->top]=e; /* 將新插入元素賦值給棧頂空間 */return OK;
}/* 若棧不空,則刪除S的棧頂元素,用e返回其值,并返回OK;否則返回ERROR */
Status Pop(SqStack *S,SElemType *e)
{ if(S->top==-1)return ERROR;*e=S->data[S->top]; /* 將要刪除的棧頂元素賦值給e */S->top--; /* 棧頂指針減一 */return OK;
}/* 從棧底到棧頂依次對棧中每個元素顯示 */
Status StackTraverse(SqStack S)
{int i;i=0;while(i<=S.top){visit(S.data[i++]);}printf("\n");return OK;
}int main()
{int j;SqStack s;int e;if(InitStack(&s)==OK)for(j=1;j<=10;j++)Push(&s,j);printf("棧中元素依次為:");StackTraverse(s);Pop(&s,&e);printf("彈出的棧頂元素 e=%d\n",e);printf("棧空否:%d(1:空 0:否)\n",StackEmpty(s));GetTop(s,&e);printf("棧頂元素 e=%d 棧的長度為%d\n",e,StackLength(s));ClearStack(&s);printf("清空棧后,棧空否:%d(1:空 0:否)\n",StackEmpty(s));return 0;
}
兩棧共享空間
前提:兩個棧中的數據類型相同
使用場景:通常是當兩個棧的空間需求有相反關系時,即一個棧增長時另一個棧在縮短。
#include "stdio.h"
#include "stdlib.h" #include "math.h"
#include "time.h"#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存儲空間初始分配量 */typedef int Status; typedef int SElemType; /* SElemType類型根據實際情況而定,這里假設為int *//* 兩棧共享空間結構 */
typedef struct
{SElemType data[MAXSIZE];int top1; /* 棧1棧頂指針 */int top2; /* 棧2棧頂指針 */
}SqDoubleStack;Status visit(SElemType c)
{printf("%d ",c);return OK;
}/* 構造一個空棧S */
Status InitStack(SqDoubleStack *S)
{ S->top1=-1;S->top2=MAXSIZE;return OK;
}/* 把S置為空棧 */
Status ClearStack(SqDoubleStack *S)
{ S->top1=-1;S->top2=MAXSIZE;return OK;
}/* 若棧S為空棧,則返回TRUE,否則返回FALSE */
Status StackEmpty(SqDoubleStack S)
{ if (S.top1==-1 && S.top2==MAXSIZE)return TRUE;elsereturn FALSE;
}/* 返回S的元素個數,即棧的長度 */
int StackLength(SqDoubleStack S)
{ return (S.top1+1)+(MAXSIZE-S.top2);
}/* 插入元素e為新的棧頂元素 */
Status Push(SqDoubleStack *S,SElemType e,int stackNumber)
{if (S->top1+1==S->top2) /* 棧已滿,不能再push新元素了 */return ERROR; if (stackNumber==1) /* 棧1有元素進棧 */S->data[++S->top1]=e; /* 若是棧1則先top1+1后給數組元素賦值。 */else if (stackNumber==2) /* 棧2有元素進棧 */S->data[--S->top2]=e; /* 若是棧2則先top2-1后給數組元素賦值。 */return OK;
}/* 若棧不空,則刪除S的棧頂元素,用e返回其值,并返回OK;否則返回ERROR */
Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
{ if (stackNumber==1) {if (S->top1==-1) return ERROR; /* 說明棧1已經是空棧,溢出 */*e=S->data[S->top1--]; /* 將棧1的棧頂元素出棧 */}else if (stackNumber==2){ if (S->top2==MAXSIZE) return ERROR; /* 說明棧2已經是空棧,溢出 */*e=S->data[S->top2++]; /* 將棧2的棧頂元素出棧 */}return OK;
}Status StackTraverse(SqDoubleStack S)
{int i;i=0;while(i<=S.top1){visit(S.data[i++]);}i=S.top2;while(i<MAXSIZE){visit(S.data[i++]);}printf("\n");return OK;
}int main()
{int j;SqDoubleStack s;int e;if(InitStack(&s)==OK){for(j=1;j<=5;j++)Push(&s,j,1);for(j=MAXSIZE;j>=MAXSIZE-2;j--)Push(&s,j,2);}printf("棧中元素依次為:");StackTraverse(s);printf("當前棧中元素有:%d \n",StackLength(s));Pop(&s,&e,2);printf("彈出的棧頂元素 e=%d\n",e);printf("棧空否:%d(1:空 0:否)\n",StackEmpty(s));for(j=6;j<=MAXSIZE-2;j++)Push(&s,j,1);printf("棧中元素依次為:");StackTraverse(s);printf("棧滿否:%d(1:否 0:滿)\n",Push(&s,100,1));ClearStack(&s);printf("清空棧后,棧空否:%d(1:空 0:否)\n",StackEmpty(s));return 0;
}
棧的鏈式存儲結構及實現
棧的鏈式存儲結構,簡稱為鏈棧。
棧只是棧頂來做插入和刪除操作,棧頂應該在鏈表的頭部還是尾部呢?由于單鏈表有頭指針,而棧頂指針也是必需的,所以好的辦法是把棧頂放在單鏈表的頭部。
因為已經有棧頂在頭部了,所以單鏈表中的頭結點也就失去了意義,通常對于鏈棧來說,是不需要頭結點的。
對于空棧來說,鏈表原定義是頭指針指向空,那么鏈棧的空其實就是top=NULL的時候。
#include "stdio.h"
#include "stdlib.h" #include "math.h"
#include "time.h"#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存儲空間初始分配量 */typedef int Status;
typedef int SElemType; /* SElemType類型根據實際情況而定,這里假設為int *//* 鏈棧結構 */
typedef struct StackNode
{SElemType data;struct StackNode *next;
}StackNode,*LinkStackPtr;typedef struct
{LinkStackPtr top;int count;
}LinkStack;Status visit(SElemType c)
{printf("%d ",c);return OK;
}/* 構造一個空棧S */
Status InitStack(LinkStack *S)
{ S->top = (LinkStackPtr)malloc(sizeof(StackNode));if(!S->top)return ERROR;S->top=NULL;S->count=0;return OK;
}/* 把S置為空棧 */
Status ClearStack(LinkStack *S)
{ LinkStackPtr p,q;p=S->top;while(p){ q=p;p=p->next;free(q);} S->count=0;return OK;
}/* 若棧S為空棧,則返回TRUE,否則返回FALSE */
Status StackEmpty(LinkStack S)
{ if (S.count==0)return TRUE;elsereturn FALSE;
}/* 返回S的元素個數,即棧的長度 */
int StackLength(LinkStack S)
{ return S.count;
}/* 若棧不空,則用e返回S的棧頂元素,并返回OK;否則返回ERROR */
Status GetTop(LinkStack S,SElemType *e)
{if (S.top==NULL)return ERROR;else*e=S.top->data;return OK;
}/* 插入元素e為新的棧頂元素 */
Status Push(LinkStack *S,SElemType e)
{LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode)); s->data=e; s->next=S->top; /* 把當前的棧頂元素賦值給新結點的直接后繼,見圖中① */S->top=s; /* 將新的結點s賦值給棧頂指針,見圖中② */S->count++;return OK;
}/* 若棧不空,則刪除S的棧頂元素,用e返回其值,并返回OK;否則返回ERROR */
Status Pop(LinkStack *S,SElemType *e)
{ LinkStackPtr p;if(StackEmpty(*S))return ERROR;*e=S->top->data;p=S->top; /* 將棧頂結點賦值給p,見圖中③ */S->top=S->top->next; /* 使得棧頂指針下移一位,指向后一結點,見圖中④ */free(p); /* 釋放結點p */ S->count--;return OK;
}Status StackTraverse(LinkStack S)
{LinkStackPtr p;p=S.top;while(p){visit(p->data);p=p->next;}printf("\n");return OK;
}int main()
{int j;LinkStack s;int e;if(InitStack(&s)==OK)for(j=1;j<=10;j++)Push(&s,j);printf("棧中元素依次為:");StackTraverse(s);Pop(&s,&e);printf("彈出的棧頂元素 e=%d\n",e);printf("棧空否:%d(1:空 0:否)\n",StackEmpty(s));GetTop(s,&e);printf("棧頂元素 e=%d 棧的長度為%d\n",e,StackLength(s));ClearStack(&s);printf("清空棧后,棧空否:%d(1:空 0:否)\n",StackEmpty(s));return 0;
}
順序棧與鏈棧對比
對比一下順序棧與鏈棧,它們在時間復雜度上是一樣的,均為O(1)。對于空間性能,順序棧需要事先確定一個固定的長度,可能會存在內存空間浪費的問題,但它的優勢是存取時定位很方便,而鏈棧則要求每個元素都有指針域,這同時也增加了—些內存開銷,但對于棧的長度無限制。
所以它們的區別和線性表—樣,如果棧的使用過程中元素變化不可預料,有時很小,有時非常大,那么最好是用鏈棧。反之,如果它的變化在可控范圍內,建議使用順序棧會更好一些。
棧的作用
棧的引入簡化了程序設計的問題,劃分了不同關注層次,使得思考范圍縮小,更加聚焦于我們要解決的問題核心。
而像線性表順序存儲結構用到的數組,因為要分散精力去考慮數組的下標增減等細節問題反而掩蓋了問題的本質。
棧的應用——遞歸
斐波那契數列
#include "stdio.h"/* 斐波那契的遞歸函數 */
int Fbi(int i)
{if( i < 2 )return i == 0 ? 0 : 1; return Fbi(i - 1) + Fbi(i - 2); /* 這里Fbi就是函數自己,等于在調用自己 */
} int main()
{int i;int a[40]; printf("迭代顯示斐波那契數列:\n");a[0]=0;a[1]=1;printf("%d ",a[0]); printf("%d ",a[1]); for(i = 2;i < 40;i++) { a[i] = a[i-1] + a[i-2]; printf("%d ",a[i]); } printf("\n");printf("遞歸顯示斐波那契數列:\n");for(i = 0;i < 40;i++) printf("%d ", Fbi(i)); return 0;
}
迭代和遞歸的區別是:迭代使用的是循環結構,遞歸使用的是選擇結構。遞歸能使程序的結構更清晰、更簡潔、更容易讓人理解,從而減少讀懂代碼的時間。但是大量的遞歸調用會建立函數的副本,會耗費大量的時間和內存。迭代則不需要反復調用函數和占用額外的內存。因此我們應該視不同情況選擇不同的代碼實現方式。
遞歸過程退回的順序是它前行順序的逆序。在退回過程中,可能要執行某些動作,包括恢復在前行過程中存儲起來的某些數據。
這種存儲某些數據,并在后面又以存儲的逆序恢復這些數據,以提供之后使用的需求,顯然很符臺棧這樣的數據結構,因此,編譯器使用棧實現遞歸就沒什么好驚訝的了。
簡單地說,就是在前行階段,對于每一層遞歸,函數的局部變量、參數值以及返回地址都被壓入棧中。在退回階段,位于棧頂的局部變量、參數值和返回地址被彈出,用于返回調用層次中執行代碼的其余部分,也就是恢復了調用的狀態。
當然,對于現在的高級語言,這樣的遞歸問題是不需要用戶來管理這個棧的,一切都由系統代勞了。
棧的應用—四則運算表達式求值
"9 3 1 -3 *+10 2/+”,這樣的表達式稱為后綴表達式,叫后綴的原因在于所有的符號都是在要運算數字的后面出現。