文章目錄
- @[toc]
- 棧與隊列:數據結構中的雙生花
- 1. 棧:后進先出的有序世界
- 1.1 概念及結構剖析
- 1.2 實現方式深度解析
- 數組 vs 鏈表實現
- 1.3 動態棧實現詳解(附程序源碼)
- 1.定義一個動態棧
- 2.初始化
- 3.銷毀
- 4.入棧
- 5.出棧
- 6.取棧頂數據
- 7.判空
- 8.獲取數據個數
- 9.訪問棧
- 10.程序源碼
- 1.4 棧的應用場景
- 1. 函數調用棧
- 2. 表達式求值
- 3. 瀏覽器歷史記錄
- 4. 撤銷操作(Undo)
- 2. 隊列:先進先出的公平之師
- 2.1 概念及結構剖析
- 2.2 實現方式
- 為什么鏈表實現更優?
- 2.3 隊列實現詳解(附程序源碼)
- 核心數據結構定義
- 1.初始化
- 2.隊列的銷毀
- 3.隊尾插入
- 4.隊頭刪除
- 5.統計隊列中數據個數
- 6.取隊頭數據
- 7.取隊尾數據
- 8.判空
- 9.訪問隊列
- 10.程序源碼
- 2.4 隊列的應用場景
- 1. 消息隊列系統
- 2. 打印機任務調度
- 3. 廣度優先搜索(BFS)
- 4. CPU任務調度
- 3. 棧與隊列的對比分析
- 4. 高級變體與應用
- 4.1 雙端隊列(Deque)
- 4.2 優先隊列(Priority Queue)
- 5. 總結:選擇合適的數據結構
文章目錄
- @[toc]
- 棧與隊列:數據結構中的雙生花
- 1. 棧:后進先出的有序世界
- 1.1 概念及結構剖析
- 1.2 實現方式深度解析
- 數組 vs 鏈表實現
- 1.3 動態棧實現詳解(附程序源碼)
- 1.定義一個動態棧
- 2.初始化
- 3.銷毀
- 4.入棧
- 5.出棧
- 6.取棧頂數據
- 7.判空
- 8.獲取數據個數
- 9.訪問棧
- 10.程序源碼
- 1.4 棧的應用場景
- 1. 函數調用棧
- 2. 表達式求值
- 3. 瀏覽器歷史記錄
- 4. 撤銷操作(Undo)
- 2. 隊列:先進先出的公平之師
- 2.1 概念及結構剖析
- 2.2 實現方式
- 為什么鏈表實現更優?
- 2.3 隊列實現詳解(附程序源碼)
- 核心數據結構定義
- 1.初始化
- 2.隊列的銷毀
- 3.隊尾插入
- 4.隊頭刪除
- 5.統計隊列中數據個數
- 6.取隊頭數據
- 7.取隊尾數據
- 8.判空
- 9.訪問隊列
- 10.程序源碼
- 2.4 隊列的應用場景
- 1. 消息隊列系統
- 2. 打印機任務調度
- 3. 廣度優先搜索(BFS)
- 4. CPU任務調度
- 3. 棧與隊列的對比分析
- 4. 高級變體與應用
- 4.1 雙端隊列(Deque)
- 4.2 優先隊列(Priority Queue)
- 5. 總結:選擇合適的數據結構
棧與隊列:數據結構中的雙生花
在計算機科學的世界里,棧和隊列如同雙生花般存在——它們看似相似卻各有千秋,共同構成了最基本也是最強大的數據結構工具集。
1. 棧:后進先出的有序世界
1.1 概念及結構剖析
棧(Stack)是一種特殊的線性表,其核心特性是只允許在固定一端(棧頂)進行插入和刪除操作。這種結構遵循**后進先出(LIFO)**原則:最后進入的元素最先被移除。
關鍵術語解析:
- 壓棧/入棧(Push):在棧頂插入新元素
- 出棧(Pop):從棧頂刪除元素
- 棧頂(Top):唯一允許操作的一端
- 棧底(Bottom):不允許操作的一端
結構可視化:
棧頂 → D → C → B → A ← 棧底
出棧順序:D → C → B → A
1.2 實現方式深度解析
數組 vs 鏈表實現
在棧的實現中,數組和鏈表各有優劣:
特性 | 數組實現 | 鏈表實現 |
---|---|---|
內存使用 | 連續內存空間 | 非連續內存空間 |
擴容成本 | 較高(需重新分配) | 較低(動態分配) |
訪問速度 | O(1) 隨機訪問 | O(n) 順序訪問 |
緩存友好性 | 高 | 低 |
實現復雜度 | 簡單 | 中等 |
為什么數組實現更優?
對于棧這種主要在尾部操作的數據結構,數組的尾部插入/刪除操作時間復雜度為O(1),且CPU緩存預取機制對連續內存訪問更友好。雖然擴容時需要重新分配內存,但通過倍增策略可以攤還這一成本。
1.3 動態棧實現詳解(附程序源碼)
跟鏈表一樣,我們采用多文件操作
1.定義一個動態棧
typedef int STDataType;
typedef struct Stack {STDataType* _a; // 動態數組int _top; // 棧頂位置int _capacity; // 容量
} ST;
2.初始化
void STInit(ST* pst)
{assert(pst);pst->a = 0;pst->capacity = 0;pst->top = 0;
}
在這有兩種寫法,第一種是top指向棧頂,第二種是top指向棧頂的下一個位置
我個人更推薦第二種寫法 這種寫法的top類似于鏈表中的size
3.銷毀
void STDestroy(ST* pst)
{assert(pst);free(pst->a);pst->a = NULL;pst->top = pst->capacity = 0;
}
4.入棧
void STPush(ST* pst, STDataType x)
{assert(pst);//擴容if (pst->top == pst->capacity){int newcapcacity =pst->capacity==0 ? 4 : pst->capacity * 2;//起始空間為0則申請4個空間 不為0則二倍STDataType* tmp = (STDataType*)ralloc(pst->a, newcapcacity * sizeof(STDataType));//pst->a為NULL時,realloc相當與mallocif (tmp == NULL){perror("realloc fail");}pst->a = tmp;pst->capacity = newcapcacity;}pst->a[pst->top] = x;pst->top++;//top指向棧頂下一個元素}
5.出棧
void STPop(ST* pst)
{assert(pst);pst->top--;
}
6.取棧頂數據
STDataType STTop(ST* pst)
{assert(pst);assert(pst->top > 0);//top大于0才能取return pst->a[pst->top - 1];//top是棧頂的下一個數據 所以要-1
}
7.判空
bool STEmpty(ST* pst)
{assert(pst);return pst->top == 0;//==0就是空
}
8.獲取數據個數
int STSize(ST* pst)
{assert(pst);return pst->top;//也就是獲取top 因為這里的top相當于size
}
9.訪問棧
#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
int main()
{ST s;STInit(&s);STPush(&s, 1);STPush(&s, 2);STPush(&s, 3);STPush(&s, 4);while (!STEmpty(&s)){printf("%d ", STTop(&s));STPop(&s);//打印一刪除一個}STDestroy(&s);return 0;
}
10.程序源碼
Stack.h ———函數聲明
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>typedef int STDataType;typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;
//初始化和銷毀
void STInit(ST* pst);
void STDestroy(ST* pst);//入棧出棧
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);//取棧頂數據
STDataType STTop(ST* pst);//判空
bool STEmpty(ST* pst);
//獲取數據個數
int STSize(ST* pst);
Stack.c———函數的實現
#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
//初始化和銷毀
void STInit(ST* pst)
{assert(pst);pst->a = 0;pst->capacity = 0;pst->top = 0;
}
void STDestroy(ST* pst)
{assert(pst);free(pst->a);pst->a = NULL;pst->top = pst->capacity = 0;
}//入棧出棧
void STPush(ST* pst, STDataType x)
{assert(pst);//擴容if (pst->top == pst->capacity){int newcapcacity =pst->capacity==0 ? 4 : pst->capacity * 2;//起始空間為0則申請4個空間 不為0則二倍STDataType* tmp = (STDataType*)realloc(pst->a, newcapcacity * sizeof(STDataType));//pst->a為NULL時,realloc相當與mallocif (tmp == NULL){perror("realloc fail");}pst->a = tmp;pst->capacity = newcapcacity;}pst->a[pst->top] = x;pst->top++;//top指向棧頂下一個元素}
void STPop(ST* pst)
{assert(pst);pst->top--;
}//取棧頂數據
STDataType STTop(ST* pst)
{assert(pst);assert(pst->top > 0);return pst->a[pst->top - 1];
}//判空
bool STEmpty(ST* pst)
{assert(pst);return pst->top == 0;//==0就是空
}
//獲取數據個數
int STSize(ST* pst)
{assert(pst);return pst->top;
}
test.c——測試
#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
int main()
{ST s;STInit(&s);STPush(&s, 1);STPush(&s, 2);STPush(&s, 3);STPush(&s, 4);while (!STEmpty(&s)){printf("%d ", STTop(&s));STPop(&s);}STDestroy(&s);return 0;
}
1.4 棧的應用場景
1. 函數調用棧
程序執行時,每次函數調用都會在棧中創建一個棧幀(Stack Frame),存儲:
-
返回地址
-
局部變量
-
函數參數
-
寄存器狀態
void funcA() {int a = 10; // 棧幀創建funcB();// 返回后棧幀銷毀 }void funcB() {int b = 20; // 新棧幀 }
2. 表達式求值
棧用于處理各種表達式:
- 中綴轉后綴:操作數棧和運算符棧
- 括號匹配:遇到左括號入棧,右括號出棧
- 計算后綴表達式:操作數入棧,遇到運算符出棧計算
示例:
(1 + 2) * 3
的后綴表達式1 2 + 3 *
3. 瀏覽器歷史記錄
瀏覽器的后退功能使用棧實現:
class BrowserHistory:def __init__(self):self.back_stack = [] # 后退棧self.forward_stack = [] # 前進棧def visit(self, url):self.back_stack.append(url)self.forward_stack = [] # 清空前進棧def back(self):if len(self.back_stack) > 1:self.forward_stack.append(self.back_stack.pop())return self.back_stack[-1]
4. 撤銷操作(Undo)
文本編輯器中的撤銷機制:
public class TextEditor {private StringBuilder text = new StringBuilder();private Stack<String> history = new Stack<>();public void type(String words) {history.push(text.toString()); // 保存狀態text.append(words);}public void undo() {if (!history.isEmpty()) {text = new StringBuilder(history.pop());}} }
2. 隊列:先進先出的公平之師
2.1 概念及結構剖析
隊列(Queue)是一種只允許在一端(隊尾)插入,在另一端(隊頭)刪除的線性表。這種結構遵循**先進先出(FIFO)**原則:最先進入的元素最先被移除。
關鍵術語解析:
- 入隊(Enqueue):在隊尾插入新元素
- 出隊(Dequeue):從隊頭刪除元素
- 隊頭(Front):刪除操作端
- 隊尾(Rear):插入操作端
結構可視化:
隊頭 → A → B → C → D → E ← 隊尾
出隊順序:A → B → C → D → E
2.2 實現方式
實現方案:隊列也可以數組和鏈表的結構實現,使用鏈表的結構實現更優一些,因為如果使用數組的結構,出隊列在數組頭上出數據,效率會比較低。
為什么鏈表實現更優?
對于隊列這種需要在兩端操作的數據結構:
- 數組實現問題:
- 出隊操作需要移動所有元素(O(n)時間復雜度)
- 假溢出問題(實際空間可用但無法入隊)
- 需要復雜的環形緩沖區處理
- 鏈表實現優勢:
- O(1)時間復雜度的入隊/出隊操作
- 動態內存分配,無固定大小限制
- 自然避免假溢出問題
2.3 隊列實現詳解(附程序源碼)
核心數據結構定義
typedef int QDataType;typedef struct QueueNode
{struct QueueNode* next;QDataType val;
}QNode;// 隊列結構
typedef struct Queue {QNode* phead; // 隊頭指針QNode* ptail; // 隊尾指針int size;//用來計數
} Queue;//用一個結構題體來放頭節點跟尾節點,這樣傳參就可以只傳一個
1.初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = NULL;pq->ptail = NULL;pq->size = 0;
}
2.隊列的銷毀
void QueueDestroy(Queue* pq)
{assert(pq);QNode* cur = pq->phead;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->phead = pq->ptail = NULL;pq->size = 0;
}
3.隊尾插入
void QueuePush(Queue* pq, QDataType x)
{QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");return;}newnode->next = NULL;newnode->val = x;if (pq->ptail == NULL){pq->phead = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;
}
4.隊頭刪除
void QueuePop(Queue* pq)
{assert(pq);assert(pq->phead);if (pq->phead->next == NULL)//一個節點{free(pq->phead);pq->phead = pq->ptail = NULL;}else//多個節點{QNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}pq->size--;
}
5.統計隊列中數據個數
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}
6.取隊頭數據
QDataType QueueFront(Queue* pq)
{assert(pq);assert(pq->phead);return pq->phead->val;
}
7.取隊尾數據
QDataType QueueBack(Queue* pq)
{assert(pq);assert(pq->phead);return pq->ptail->val;
}
8.判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}
9.訪問隊列
int main()
{Queue q;QueueInit(&q);QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);QueuePush(&q, 4);while (!QueueEmpty(&q)){printf("%d ", QueueFront(&q));//取隊頭數據QueuePop(&q);}printf("\n");return 0;
}
10.程序源碼
Queue.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>typedef int QDataType;typedef struct QueueNode
{struct QueueNode* next;QDataType val;
}QNode;typedef struct Queue
{QNode* phead;QNode* ptail;int size;
}Queue;//初始化
void QueueInit(Queue* pq);
//隊列的銷毀
void QueueDestroy(Queue* pq);
//隊尾插入
void QueuePush(Queue* pq, QDataType x);
//隊頭刪除
void QueuePop(Queue* pq);
//統計隊列中數據的個數
int QueueSize(Queue* pq);
//取隊頭數據
QDataType QueueFront(Queue* pq);
//取隊尾數據
QDataType QueueBack(Queue* pq);//判空
bool QueueEmpty(Queue* pq);
Queue.c
#define _CRT_SECURE_NO_WARNINGS
#include"Queue.h"void QueueInit(Queue* pq)
{assert(pq);pq->phead = NULL;pq->ptail = NULL;pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");return;}newnode->next = NULL;newnode->val = x;if (pq->ptail == NULL){pq->phead = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;
}int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}void QueuePop(Queue* pq)
{assert(pq);assert(pq->phead);if (pq->phead->next == NULL)//一個節點{free(pq->phead);pq->phead = pq->ptail = NULL;}else//多個節點{QNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}pq->size--;
}
QDataType QueueFront(Queue* pq)
{assert(pq);assert(pq->phead);return pq->phead->val;
}
QDataType QueueBack(Queue* pq)
{assert(pq);assert(pq->phead);return pq->ptail->val;
}
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}
void QueueDestroy(Queue* pq)
{assert(pq);QNode* cur = pq->phead;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->phead = pq->ptail = NULL;pq->size = 0;
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include"Queue.h"
int main()
{Queue q;QueueInit(&q);QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);QueuePush(&q, 4);while (!QueueEmpty(&q)){printf("%d ", QueueFront(&q));//取隊頭數據QueuePop(&q);}printf("\n");return 0;
}
2.4 隊列的應用場景
1. 消息隊列系統
現代分布式系統的核心組件:
public class MessageQueue {private Queue<Message> queue = new LinkedList<>();public synchronized void enqueue(Message msg) {queue.add(msg);notifyAll(); // 喚醒等待的消費者}public synchronized Message dequeue() throws InterruptedException {while (queue.isEmpty()) {wait(); // 等待消息到達}return queue.remove();}
}
2. 打印機任務調度
多任務打印的公平處理:
class Printer:def __init__(self):self.print_queue = deque()self.current_task = Nonedef add_document(self, document):self.print_queue.append(document)print(f"Added document: {document}")def print_next(self):if self.print_queue:self.current_task = self.print_queue.popleft()print(f"Printing: {self.current_task}")else:print("No documents to print")
3. 廣度優先搜索(BFS)
圖遍歷算法核心:
void BFS(Graph* graph, int start) {bool visited[MAX_VERTICES] = {false};Queue queue;QueueInit(&queue);visited[start] = true;QueuePush(&queue, start);while (!QueueEmpty(&queue)) {int current = QueueFront(&queue);QueuePop(&queue);printf("Visited %d\n", current);// 遍歷所有鄰接節點for (int i = 0; i < graph->vertices; i++) {if (graph->adjMatrix[current][i] && !visited[i]) {visited[i] = true;QueuePush(&queue, i);}}}
}
4. CPU任務調度
操作系統核心調度算法:
struct Task {int pid;int priority;// 其他任務信息
};void scheduleTasks(Queue* highPriority, Queue* normalQueue) {while (!QueueEmpty(highPriority) || !QueueEmpty(normalQueue)) {Task* task;// 優先處理高優先級隊列if (!QueueEmpty(highPriority)) {task = QueueFront(highPriority);QueuePop(highPriority);} else {task = QueueFront(normalQueue);QueuePop(normalQueue);}executeTask(task);// 任務未完成則重新入隊if (!task->completed) {if (task->priority == HIGH) {QueuePush(highPriority, task);} else {QueuePush(normalQueue, task);}}}
}
3. 棧與隊列的對比分析
特性 | 棧 (Stack) | 隊列 (Queue) |
---|---|---|
操作原則 | LIFO (后進先出) | FIFO (先進先出) |
插入位置 | 棧頂 (Top) | 隊尾 (Rear) |
刪除位置 | 棧頂 (Top) | 隊頭 (Front) |
典型操作 | Push, Pop | Enqueue, Dequeue |
實現方式 | 數組(更優)/鏈表 | 鏈表(更優)/循環數組 |
空間復雜度 | O(n) | O(n) |
時間復雜度 | Push/Pop: O(1) | Enqueue/Dequeue: O(1) |
應用場景 | 函數調用、表達式求值、回溯 | 消息傳遞、BFS、緩沖、調度 |
抽象層次 | 遞歸結構 | 管道結構 |
4. 高級變體與應用
4.1 雙端隊列(Deque)
雙端隊列結合了棧和隊列的特性,允許在兩端進行插入和刪除操作:
typedef struct DequeNode {int data;struct DequeNode* prev;struct DequeNode* next;
} DequeNode;typedef struct {DequeNode* front;DequeNode* rear;
} Deque;// 前端插入
void insertFront(Deque* dq, int data) {DequeNode* newNode = createNode(data);if (dq->front == NULL) {dq->front = dq->rear = newNode;} else {newNode->next = dq->front;dq->front->prev = newNode;dq->front = newNode;}
}// 后端刪除
int deleteRear(Deque* dq) {if (dq->rear == NULL) return -1; // 錯誤值DequeNode* temp = dq->rear;int data = temp->data;if (dq->front == dq->rear) {dq->front = dq->rear = NULL;} else {dq->rear = dq->rear->prev;dq->rear->next = NULL;}free(temp);return data;
}
4.2 優先隊列(Priority Queue)
優先隊列是隊列的變體,元素按優先級出隊:
typedef struct {int* heap; // 堆數組int capacity; // 最大容量int size; // 當前大小
} PriorityQueue;void enqueue(PriorityQueue* pq, int value) {if (pq->size == pq->capacity) {// 擴容邏輯}// 將新元素添加到堆尾int i = pq->size++;pq->heap[i] = value;// 上濾操作while (i != 0 && pq->heap[(i-1)/2] > pq->heap[i]) {swap(&pq->heap[i], &pq->heap[(i-1)/2]);i = (i-1)/2;}
}int dequeue(PriorityQueue* pq) {if (pq->size <= 0) return INT_MIN;int root = pq->heap[0];pq->heap[0] = pq->heap[--pq->size];// 下濾操作int i = 0;while (true) {int smallest = i;int left = 2*i + 1;int right = 2*i + 2;if (left < pq->size && pq->heap[left] < pq->heap[smallest])smallest = left;if (right < pq->size && pq->heap[right] < pq->heap[smallest])smallest = right;if (smallest != i) {swap(&pq->heap[i], &pq->heap[smallest]);i = smallest;} else {break;}}return root;
}
5. 總結:選擇合適的數據結構
棧和隊列作為基礎數據結構,在算法設計和系統開發中無處不在:
- 選擇棧的場景:
- 需要回溯操作(如撤銷功能)
- 遞歸算法實現
- 深度優先搜索(DFS)
- 語法解析和表達式求值
- 選擇隊列的場景:
- 需要公平處理(如任務調度)
- 廣度優先搜索(BFS)
- 緩沖區和數據傳輸
- 消息傳遞系統
- 混合使用場景:
- 使用隊列實現棧(需要兩個隊列)
- 使用棧實現隊列(需要兩個棧)
- 雙端隊列滿足雙向操作需求
- 優先隊列處理帶優先級的任務