
圖 1 棧存儲結構示意圖
1,棧只能從表的一端存取數據,另一端是封閉的,如圖 1 所示;2,在棧中,無論是存數據還是取數據,都必須遵循"先進后出"的原則,即最先進棧的元素最后出棧。拿圖 1 的棧來說,從圖中數據的存儲狀態可判斷出,元素 1 是最先進的棧。因此,當需要從棧 中取出元素 1 時,根據"先進后出"的原則,需提前將元素 3 和元素 2 從棧中取出,然后才能成功取出元素 1。

圖 2 棧頂和棧底
進棧和出棧
基於 棧結構的特點,在實際應用中,通常只會對棧執行以下兩種操作:- 向棧中添加元素,此過程被稱為"進棧"(入棧或壓棧);
- 從棧中提取出指定元素,此過程被稱為"出棧"(或彈棧);
棧的具體實現
棧是一種 "特殊" 的線性存儲結構,因此棧的具體實現有以下兩種方式:
兩種實現方式的區別,僅限於數據元素在實際物理空間上存放的相對位置,順序棧底層采用的是 數組,鏈棧底層采用的是鏈表。有關順序棧和鏈棧的具體實現會在后續章節中作詳細講解。
棧的應用
基於棧結構對數據存取采用 "先進后出" 原則的特點,它可以用於實現很多功能。例如,我們經常使用瀏覽器在各種網站上查找信息。假設先瀏覽的頁面 A,然后關閉了頁面 A 跳轉到頁面 B,隨后又關閉頁面 B 跳轉到了頁面 C。而此時,我們如果想重新回到頁面 A,有兩個選擇:
- 重新搜索找到頁面 A;
- 使用瀏覽器的"回退"功能。瀏覽器會先回退到頁面 B,而后再回退到頁面 A。
不僅如此,棧存儲結構還可以幫我們檢測代碼中的 括號匹配問題。多數編程語言都會用到括號(小括號、中括號和大括號),括號的錯誤使用(通常是丟右括號)會導致程序編譯錯誤,而很多開發工具中都有檢測代碼是否有編輯錯誤的功能,其中就包含檢測代碼中的括號匹配問題,此功能的底層實現使用的就是棧結構。
同時,棧結構還可以實現數值的 進制轉換功能。例如,編寫程序實現從十進制數自動轉換成二進制數,就可以使用棧存儲結構來實現。
以上也僅是棧應用領域的冰山一角,這里不再過多舉例。在后續章節的學習中,我們會大量使用到棧結構。
接下來,我們學習如何實現順序棧和鏈棧,以及對棧中元素進行入棧和出棧的操作。
{1,2,3,4}
,存儲狀態如
圖 1 所示:

圖 1 順序表存儲 {1,2,3,4}
{1,2,3,4}
,其存儲狀態如圖 2 所示:

圖 2 棧結構存儲 {1,2,3,4}
從數組下標為 0 的模擬棧存儲數據是常用的方法,從其他數組下標處存儲數據也完全可以,這里只是為了方便初學者理解。
順序棧元素"入棧"
{1,2,3,4}
的過程。最初,棧是"空棧",即數組是空的,top 值為初始值 -1,如圖 3 所示:

圖 3 空棧示意圖

圖 4 模擬棧存儲元素 1

圖 5 模擬棧存儲{1,2,3,4}
因此,C 語言實現代碼為:
//元素elem進棧,a為數組,top值為當前棧的棧頂位置 int push(int* a,int top,int elem){ a[++top]=elem; return top; }
代碼中的 a[++top]=elem,等價於先執行 ++top,再執行 a[top]=elem。
順序棧元素"出棧"
其實,top 變量的設置對模擬數據的 "入棧" 操作沒有實際的幫助,它是為實現數據的 "出棧" 操作做准備的。比如,將圖 5 中的元素 2 出棧,則需要先將元素 4 和元素 3 依次出棧。需要注意的是,當有數據出棧時,要將 top 做 -1 操作。因此,元素 4 和元素 3 出棧的過程分別如圖 6a) 和 6b) 所示:

圖 6 數據元素出棧
注意,圖 6 數組中元素的消失僅是為了方便初學者學習,其實,這里只需要對 top 值做 -1 操作即可,因為 top 值本身就表示棧的棧頂位置,因此 top-1 就等同於棧頂元素出棧。並且后期向棧中添加元素時,新元素會存儲在類似元素 4 這樣的舊元素位置上,將舊元素覆蓋。
//數據元素出棧 int pop(int * a,int top){ if (top==-1) { printf("空棧"); return -1; } printf("彈棧元素:%d\n",a[top]); top--; return top; }
代碼中的 if 語句是為了防止用戶做 "棧中已無數據卻還要數據出棧" 的錯誤操作。代碼中,關於對棧中元素出棧操作的實現,只需要 top 值 -1 即可。
總結
通過學習順序表模擬棧中數據入棧和出棧的操作,初學者完成了對順序棧的學習,這里給出順序棧及對數據基本操作的 C 語言完整代碼:#include <stdio.h> //元素elem進棧 int push(int* a,int top,int elem){ a[++top]=elem; return top; } //數據元素出棧 int pop(int * a,int top){ if (top==-1) { printf("空棧"); return -1; } printf("彈棧元素:%d\n",a[top]); top--; return top; } int main() { int a[100]; int top=-1; top=push(a, top, 1); top=push(a, top, 2); top=push(a, top, 3); top=push(a, top, 4); top=pop(a, top); top=pop(a, top); top=pop(a, top); top=pop(a, top); top=pop(a, top); return 0; }
程序輸出結果為:
鏈棧的實現思路同順序棧類似,順序棧是將數順序表(數組)的一端作為棧底,另一端為棧頂;鏈棧也如此,通常我們將鏈表的頭部作為棧頂,尾部作為棧底,如圖 1 所示:

圖 1 鏈棧示意圖
- 在實現數據"入棧"操作時,需要將數據從鏈表的頭部插入;
- 在實現數據"出棧"操作時,需要刪除鏈表頭部的首元節點;
鏈棧元素入棧
例如,將元素 1、2、3、4 依次入棧,等價於將各元素采用頭插法依次添加到鏈表中,每個數據元素的添加過程如圖 2 所示:
圖 2 鏈棧元素依次入棧過程示意圖
//鏈表中節點結構 typedef struct lineStack{ int data; struct lineStack* next; }; //壓棧 stack 當前鏈棧 a 入棧元素 lineStack* push(lineStack* stack,int a) { //創建存儲新元素的節點 lineStack* temp = (lineStack*)malloc(sizeof(lineStack)); temp->data = a; //新節點與頭節點建立關聯 temp->next = stack; //更新頭指針指向 stack = temp; return stack; }
鏈棧元素出棧
例如,圖 2e) 所示的鏈棧中,若要將元素 3 出棧,根據"先進后出"的原則,要先將元素 4 出棧,也就是從鏈表中摘除,然后元素 3 才能出棧,整個操作過程如圖 3 所示:

圖 3 鏈棧元素出棧示意圖
//棧頂元素出棧的實現函數 lineStack* pop(lineStack* stack) { if (stack) { //聲明一個新指針指向棧頂節點 lineStack *p = stack; //更新頭節點 stack = stack->next; printf("出棧元素:%d \n",p->data); if (stack) { printf("新棧頂元素:%d\n",stack->data); } else { printf("棧已空\n"); } free(p); } else { printf("棧內沒有元素\n"); return stack; } return stack;
代碼中通過使用 if 判斷語句,避免了用戶執行"棧已空卻還要數據出棧"錯誤操作。
總結
本節,通過采用頭插法操作數據的單鏈表實現了鏈棧結構,這里給出鏈棧及基本操作的C語言完整代碼:
#include<stdlib.h> #include<stdio.h> //鏈表中節點結構 typedef struct lineStack{ int data; struct lineStack* next; }; //壓棧 stack 當前鏈棧 a 入棧元素 lineStack* push(lineStack* stack,int a) { //創建存儲新元素的節點 lineStack* temp = (lineStack*)malloc(sizeof(lineStack)); temp->data = a; //新節點與頭節點建立關聯 temp->next = stack; //更新頭指針指向 stack = temp; return stack; } //棧頂元素出棧的實現函數 lineStack* pop(lineStack* stack) { if (stack) { //聲明一個新指針指向棧頂節點 lineStack *p = stack; //更新頭節點 stack = stack->next; printf("出棧元素:%d ",p->data); if (stack) { printf("新棧頂元素:%d\n",stack->data); } else { printf("棧已空\n"); } free(p); } else { printf("棧內沒有元素\n"); return stack; } return stack; } int main() { lineStack * stack = NULL; stack = push(stack, 1); stack = push(stack, 2); stack = push(stack, 3); stack = push(stack, 4); stack = pop(stack); stack = pop(stack); stack = pop(stack); stack = pop(stack); stack = pop(stack); return 0; }
運行結果:
二,順序表及隊列的實現

圖 1 隊列存儲結構
通常,稱進數據的一端為 "隊尾",出數據的一端為 "隊頭",數據元素進隊列的過程稱為 "入隊",出隊列的過程稱為 "出隊"。
棧和隊列不要混淆,棧結構是一端封口,特點是"先進后出";而隊列的兩端全是開口,特點是"先進先出"。
隊列的實現
實際生活中,隊列的應用隨處可見,比如排隊買 XXX、醫院的掛號系統等,采用的都是隊列的結構。
拿排隊買票來說,所有的人排成一隊,先到者排的就靠前,后到者只能從隊尾排隊等待,隊中的每個人都必須等到自己前面的所有人全部買票成功並從隊頭出隊后,才輪到自己買票。這就不是典型的隊列結構嗎?
明白了什么是隊列,接下來開始系統地學習順序隊列和鏈隊列。

圖 1 順序隊列實現示意圖
在圖 1 的基礎上,當有數據元素進隊列時,對應的實現操作是將其存儲在指針 rear 指向的數組位置,然后 rear+1;當需要隊頭元素出隊時,僅需做 top+1 操作。
{1,2,3,4}
用順序隊列存儲的實現操作如圖 2 所示:

圖 2 數據進順序隊列的過程實現示意圖

圖 3 數據出順序隊列的過程示意圖
#include <stdio.h> int enQueue(int *a,int rear,int data){ a[rear]=data; rear++; return rear; } void deQueue(int *a,int front,int rear){ //如果 front==rear,表示隊列為空 while (front!=rear) { printf("出隊元素:%d\n",a[front]); front++; } } int main() { int a[100]; int front,rear; //設置隊頭指針和隊尾指針,當隊列中沒有元素時,隊頭和隊尾指向同一塊地址 front=rear=0; //入隊 rear=enQueue(a, rear, 1); rear=enQueue(a, rear, 2); rear=enQueue(a, rear, 3); rear=enQueue(a, rear, 4); //出隊 deQueue(a, front, rear); return 0; }
程序輸出結果:
出隊元素:1
出隊元素:2
出隊元素:3
出隊元素:4
此方法存在的問題
先來分析以下圖 2b) 和圖 3b)。圖 2b) 是所有數據進隊成功的示意圖,而圖 3b) 是所有數據全部出隊后的示意圖。通過對比兩張圖,你會發現,指針 top 和 rear 重合位置指向了 a[4] 而不再是 a[0]。也就是說,整個順序隊列在數據不斷地進隊出隊過程中,在順序表中的位置不斷后移。順序隊列整體后移造成的影響是:
- 順序隊列之前的數組存儲空間將無法再被使用,造成了空間浪費;
- 如果順序表申請的空間不足夠大,則直接造成程序中數組 a 溢出,產生溢出錯誤;
順序隊列另一種實現方法
既然明白了上面這種方法的弊端,那么我們可以試着在它的基礎上對其改良。為了解決以上兩個問題,可以使用巧妙的方法將順序表打造成一個環狀表,如圖 4 所

圖 4 環狀順序隊列
#include <stdio.h> #define max 5//表示順序表申請的空間大小 int enQueue(int *a,int front,int rear,int data){ //添加判斷語句,如果rear超過max,則直接將其從a[0]重新開始存儲,如果rear+1和front重合,則表示數組已滿 if ((rear+1)%max==front) { printf("空間已滿"); return rear; } a[rear%max]=data; rear++; return rear; } int deQueue(int *a,int front,int rear){ //如果front==rear,表示隊列為空 if(front==rear%max) { printf("隊列為空"); return front; } printf("%d ",a[front]); //front不再直接 +1,而是+1后同max進行比較,如果=max,則直接跳轉到 a[0] front=(front+1)%max; return front; } int main() { int a[max]; int front,rear; //設置隊頭指針和隊尾指針,當隊列中沒有元素時,隊頭和隊尾指向同一塊地址 front=rear=0; //入隊 rear=enQueue(a,front,rear, 1); rear=enQueue(a,front,rear, 2); rear=enQueue(a,front,rear, 3); rear=enQueue(a,front,rear, 4); //出隊 front=deQueue(a, front, rear); //再入隊 rear=enQueue(a,front,rear, 5); //再出隊 front=deQueue(a, front, rear); //再入隊 rear=enQueue(a,front,rear, 6); //再出隊 front=deQueue(a, front, rear); front=deQueue(a, front, rear); front=deQueue(a, front, rear); front=deQueue(a, front, rear); return 0; }
程序運行結果:
1 2 3 4 5 6
- 當隊列為空時,隊列的頭指針等於隊列的尾指針;
- 當數組滿員時,隊列的頭指針等於隊列的尾指針;

圖 1 鏈式隊列的初始狀態
在創建鏈式隊列時,強烈建議初學者創建一個帶有頭節點的鏈表,這樣實現鏈式隊列會更簡單。
//鏈表中的節點結構 typedef struct QNode{ int data; struct QNode * next; }QNode; //創建鏈式隊列的函數 QNode * initQueue(){ //創建一個頭節點 QNode * queue=(QNode*)malloc(sizeof(QNode)); //對頭節點進行初始化 queue->next=NULL; return queue; }
鏈式隊列數據入隊
鏈隊隊列中,當有新的數據元素入隊,只需進行以下 3 步操作:
- 將該數據元素用節點包裹,例如新節點名稱為 elem;
- 與 rear 指針指向的節點建立邏輯關系,即執行 rear->next=elem;
- 最后移動 rear 指針指向該新節點,即 rear=elem;
由此,新節點就入隊成功了。
例如,在圖 1 的基礎上,我們依次將 {1,2,3}
依次入隊,各個數據元素入隊的過程如圖 2 所示:

圖 2 {1,2,3} 入鏈式隊列
QNode* enQueue(QNode * rear,int data){ //1、用節點包裹入隊元素 QNode * enElem=(QNode*)malloc(sizeof(QNode)); enElem->data=data; enElem->next=NULL; //2、新節點與rear節點建立邏輯關系 rear->next=enElem; //3、rear指向新節點 rear=enElem; //返回新的rear,為后續新元素入隊做准備 return rear; }
鏈式隊列數據出隊
當鏈式隊列中,有數據元素需要出隊時,按照 "先進先出" 的原則,只需將存儲該數據的節點以及它之前入隊的元素節點按照原則依次出隊即可。這里,我們先學習如何將隊頭元素出隊。
鏈式隊列中隊頭元素出隊,需要做以下 3 步操作:
- 通過 top 指針直接找到隊頭節點,創建一個新指針 p 指向此即將出隊的節點;
- 將 p 節點(即要出隊的隊頭節點)從鏈表中摘除;
- 釋放節點 p,回收其所占的內存空間;
例如,在圖 2b) 的基礎上,我們將元素 1 和 2 出隊,則操作過程如圖 3 所示

圖 3 鏈式隊列中數據元素出隊
鏈式隊列中隊頭元素出隊的 C 語言實現代碼為:
void DeQueue(QNode * top,QNode * rear){ if (top->next==NULL) { printf("隊列為空"); return ; } // 1、 QNode * p=top->next; printf("%d",p->data); top->next=p->next; if (rear==p) { rear=top; } free(p); }
注意,將隊頭元素做出隊操作時,需提前判斷隊列中是否還有元素,如果沒有,要提示用戶無法做出隊操作,保證程序的健壯性。
總結
通過學習鏈式隊列最基本的數據入隊和出隊操作,我們可以就實際問題,對以上代碼做適當的修改。
前面在學習順序隊列時,由於順序表的局限性,我們在順序隊列中實現數據入隊和出隊的基礎上,又對實現代碼做了改進,令其能夠充分利用數組中的空間。鏈式隊列就不需要考慮空間利用的問題,因為鏈式隊列本身就是實時申請空間。因此,這可以算作是鏈式隊列相比順序隊列的一個優勢。
這里給出鏈式隊列入隊和出隊的完整 C 語言代碼為:
#include <stdio.h> #include <stdlib.h> typedef struct QNode{ int data; struct QNode * next; }QNode; QNode * initQueue(){ QNode * queue=(QNode*)malloc(sizeof(QNode)); queue->next=NULL; return queue; } QNode* enQueue(QNode * rear,int data){ QNode * enElem=(QNode*)malloc(sizeof(QNode)); enElem->data=data; enElem->next=NULL; //使用尾插法向鏈隊列中添加數據元素 rear->next=enElem; rear=enElem; return rear; } QNode* DeQueue(QNode * top,QNode * rear){ if (top->next==NULL) { printf("\n隊列為空"); return rear; } QNode * p=top->next; printf("%d ",p->data); top->next=p->next; if (rear==p) { rear=top; } free(p); return rear; } int main() { QNode * queue,*top,*rear; queue=top=rear=initQueue();//創建頭結點 //向鏈隊列中添加結點,使用尾插法添加的同時,隊尾指針需要指向鏈表的最后一個元素 rear=enQueue(rear, 1); rear=enQueue(rear, 2); rear=enQueue(rear, 3); rear=enQueue(rear, 4); //入隊完成,所有數據元素開始出隊列 rear=DeQueue(top, rear); rear=DeQueue(top, rear); rear=DeQueue(top, rear); rear=DeQueue(top, rear); rear=DeQueue(top, rear); return 0; }
程序運行結果為:
1 2 3 4
隊列為空