03 棧和隊列
棧
- 定義
棧(Stack)是限定僅在表尾進行插入和刪除操作的線性表,棧又稱爲後進先出(Last In First Out)的線性表,簡稱LIFO結構
ADT 棧(stack)
Data
同線性表。元素具有相同的類型,相鄰元素具有前驅和後堆關係。
Operation
InitStack ( *S ):初始化操作.建立一個空棧S。
DestroyStack ( *S ):若棧存在,則銷燬它。
ClearStack (*S):將棧清空。
StackEmpty ( S ):若棧爲空,返回true,否則返回 false。
GetTop (S,*e):若棧存在且非空,用e返回S的棧頂元素。
Push (*S,e):若棧S存在,插入新元素e到棧S中併成爲棧頂元素。
Pop (*S,*e):刪除棧S中棧頂元素,並用e返回其值。
StackLength (S):返回回棧S的元素個數。
endADT
-
順序存儲結構
typedef int SElemType; /* SElemType類型根據實際情況而定,這裏假設爲int */ /* 順序棧結構 */ typedef struct { SElemType data[MAXSIZE]; int top; /* 用於棧頂指針 */ }SqStack;
-
出棧操作:時間複雜度O(1)
/* 若棧不空,則刪除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; }
-
進棧操作:時間複雜度O(1)
/* 插入元素e爲新的棧頂元素 */ Status Push(SqStack *S,SElemType e) { if(S->top == MAXSIZE -1) /* 棧滿 */ { return ERROR; } S->top++; /* 棧頂指針增加一 */ S->data[S->top]=e; /* 將新插入元素賦值給棧頂空間 */ return OK; }
-
兩棧共享空間
-
結構
/* 兩棧共享空間結構 */ typedef struct { SElemType data[MAXSIZE]; int top1; /* 棧1棧頂指針 */ int top2; /* 棧2棧頂指針 */ }SqDoubleStack;
-
push
/* 插入元素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; }
-
pop
/* 若棧不空,則刪除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; }
-
-
-
鏈式存儲結構
/* 鏈棧結構 */ typedef struct StackNode { SElemType data; struct StackNode *next; }StackNode,*LinkStackPtr; typedef struct { LinkStackPtr top; int count; }LinkStack;
-
進棧操作:時間複雜度O(1)
/* 插入元素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; }
-
出棧操作:時間複雜度O(1)
/* 若棧不空,則刪除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; }
-
總結:對比一下順序棧和鏈棧,它們在時間複雜度上是一樣的,均爲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
-
後綴(逆波蘭)表示法
9 3 1 - 3 * + 10 2 / +
-
中綴表達式轉後綴表達式
-
後綴表達式計算結果
中綴表達式轉成後綴表達式後,藉助棧來計算結果
-
-
隊列
- 定義
隊列(queue)是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表。隊列是一種先進先出(First In First Out)的線性表,簡稱FIFO,允許插入的一端稱爲隊尾,允許刪除的一端稱爲隊頭。
ADT 隊列 (Queue)
Data
同線性表。元素具有相同的類型,相鄰元素具有前驅和後繼關係 。
Operation
InitQueue(*Q) :初始化操作,建立一個空隊列Q。
DestroyQueue(*Q) :若隊列Q存在,則銷燬它。
ClearQueue(*Q) :將隊列Q清空。
QueueEmpty(Q) :若隊列Q爲空,返回true,否則返回false。
GetHead(Q,*e) :若隊列Q存在且非空,用e返回隊列Q的隊頭元素。
EnQueue(*Q,e) :若隊列Q存在,插入新元素e到隊列Q中併成爲對尾元素 。
DeQueue(*Q,*e) :刪除隊列Q中隊頭元素,並用e返回其值。
QueueLength(Q) :返回隊列Q的元素個數
endADT
-
順序存儲結構
-
循環隊列
我們把隊列的這種頭尾相接的順序存儲結構稱爲循環隊列
-
循環隊列的順序存儲結構如下:
typedef int QElemType; /* QElemType類型根據實際情況而定,這裏假設爲int */ /* 循環隊列的順序存儲結構 */ typedef struct { QElemType data[MAXSIZE]; int front; /* 頭指針 */ int rear; /* 尾指針,若隊列不空,指向隊列尾元素的下一個位置 */ }SqQueue;
-
循環隊列初始化:
/* 初始化一個空隊列Q */ Status InitQueue(SqQueue *Q) { Q->front=0; Q->rear=0; return OK; }
-
循環隊列求隊列長度代碼如下:
/* 返回Q的元素個數,也就是隊列的當前長度 */ int QueueLength(SqQueue Q) { return (Q.rear-Q.front+MAXSIZE)%MAXSIZE; }
-
循環隊列的入隊列操作代碼:
/* 若隊列未滿,則插入元素e爲Q新的隊尾元素 */ Status EnQueue(SqQueue *Q,QElemType e) { if ((Q->rear+1)%MAXSIZE == Q->front) /* 隊列滿的判斷 */ return ERROR; Q->data[Q->rear]=e; /* 將元素e賦值給隊尾 */ Q->rear=(Q->rear+1)%MAXSIZE;/* rear指針向後移一位置, */ /* 若到最後則轉到數組頭部 */ return OK; }
-
循環隊列的出隊列操作代碼
/* 若隊列不空,則刪除Q中隊頭元素,用e返回其值 */ Status DeQueue(SqQueue *Q,QElemType *e) { if (Q->front == Q->rear) /* 隊列空的判斷 */ return ERROR; *e=Q->data[Q->front]; /* 將隊頭元素賦值給e */ Q->front=(Q->front+1)%MAXSIZE; /* front指針向後移一位置, */ /* 若到最後則轉到數組頭部 */ return OK; }
-
-
鏈式存儲結構
隊列的鏈式存儲結構,其實就是線性表的單鏈表,只不過它只能尾進頭出而已,我們把它簡稱爲鏈隊列。
typedef int QElemType; /* QElemType類型根據實際情況而定,這裏假設爲int */ typedef struct QNode /* 結點結構 */ { QElemType data; struct QNode *next; }QNode,*QueuePtr; typedef struct /* 隊列的鏈表結構 */ { QueuePtr front,rear; /* 隊頭、隊尾指針 */ }LinkQueue;
-
入隊操作
/* 插入元素e爲Q的新的隊尾元素 */ Status EnQueue(LinkQueue *Q,QElemType e) { QueuePtr s=(QueuePtr)malloc(sizeof(QNode)); if(!s) /* 存儲分配失敗 */ exit(OVERFLOW); s->data=e; s->next=NULL; Q->rear->next=s; /* 把擁有元素e的新結點s賦值給原隊尾結點的後繼,見圖中① */ Q->rear=s; /* 把當前的s設置爲隊尾結點,rear指向s,見圖中② */ return OK; }
-
出隊操作
/* 若隊列不空,刪除Q的隊頭元素,用e返回其值,並返回OK,否則返回ERROR */ Status DeQueue(LinkQueue *Q,QElemType *e) { QueuePtr p; if(Q->front==Q->rear) return ERROR; p=Q->front->next; /* 將欲刪除的隊頭結點暫存給p,見圖中① */ *e=p->data; /* 將欲刪除的隊頭結點的值賦值給e */ Q->front->next=p->next;/* 將原隊頭結點的後繼p->next賦值給頭結點後繼,見圖中② */ if(Q->rear==p) /* 若隊頭就是隊尾,則刪除後將rear指向頭結點,見圖中③ */ Q->rear=Q->front; free(p); return OK; }
-
-
總結:
對於循環隊列與鏈隊列的比較,可以從兩方面來考慮,從時間上,其實它們的基本操作都是常數時間,即都爲O(1),不過循環隊列是事先申請好空間,使用期間不釋放,而對於鏈隊列,每次申請和釋放結點也會存在一些時間開銷,如果入隊出隊頻繁,則兩者還是有細微差異,對於空間上來說,循環隊列必須有一個固定的長度,所以就有了存儲元素個數和空間浪費的問題。而鏈隊列不存在這個問題,儘管它需要一個指針域,會產生一些空間上的開銷,但也可以接受。所以在空間上,鏈隊列更加靈活。總的來說,在可以確定隊列長度最大值的情況下,建議用循環隊列,如果你無法預估隊列的長度時,則用鏈隊列。