目錄
棧(后進先出)
棧的實現
頭文件
初始化
入棧
注意:
bool 判空
出棧----棧頂
注意
出棧頂元素,元素不會刪除
注意:
獲取棧中有效個數
銷毀棧
源文件操作
用棧實現遞歸*
隊列(先進先出)
隊列實現
頭文件(基本操作):
結構
初始化
判空
入隊----隊尾
出隊----隊頭
優勢:
取隊頭隊尾數據
優勢:
銷毀
棧(后進先出)
是?種特殊的線性表,其只允許在固定的?端進?插?和刪除元素操作。進?數據插?和刪除操作
的?端稱為棧頂,另?端稱為棧底。
壓棧:棧的插?操作叫做進棧/壓棧/?棧,入數據在棧頂。
出棧:棧的刪除操作叫做出棧。出數據也在棧頂。

底層結構是由數組實現的,相對來說數組在尾結點插入快
邏輯結構和物理結構都是線性的
棧的實現
頭文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//定義棧的結構
typedef int STDataType;
typedef struct Stack
{STDataType* arr;int top;//有效數據個數int capacity;//容量
}ST;//初始化
void StackInit(ST* ps);//棧是否為空
bool STEmpty(ST* ps);//入棧----棧頂
void StackPush(ST* ps, STDataType x);//出棧----棧頂
void StackPop(ST* ps);//出棧頂數據
STDataType StackTop(ST* ps);
\ No newline at end of file
STDataType StackTop(ST* ps);//獲取棧中有效元素個數
int StackSize(ST* ps);//棧是否為空
bool STEmpty(ST* ps);
初始化
//初始化
void StackInit(ST* ps)
{assert(ps);ps->arr = NULL;ps->top = ps->capacity = 0;
}
使用前先調用StackInit
初始化棧,然后才能進行入棧等其他操作,否則可能會導致未定義行為。
入棧
//入棧----棧頂
void StackPush(ST* ps, STDataType x)
{if (ps->top == ps->capacity){//增容int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc");exit(1);}//空間申請成功ps->arr = tmp;ps->capacity = newCapacity;}ps->arr[ps->top++] = x;
}
- 首先檢查棧是否已滿(
ps->top == ps->capacity
) - 如果棧滿,則進行擴容操作:
- 初始容量為 0 時,擴容到 4 個元素
- 否則,按照 2 倍容量進行擴容
- 使用
realloc
重新分配內存空間 - 處理內存分配失敗的情況(打印錯誤并退出程序)
- 將新元素
x
放入棧頂位置(ps->arr[ps->top]
) - 棧頂指針
top
自增,指向新的棧頂位置
注意:
- ps指針不為空,一般斷言一下。
- 棧結構
ST
已經正確初始化 STDataType
已經定義(通常是某種基本數據類型)
bool 判空
//棧是否為空
bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}
為空則返回true,非空則是false
出棧----棧頂
//出棧----棧頂
void StackPop(ST* ps)
{assert(!STEmpty(ps));--ps->top;
}
- 效率高,時間復雜度為 O (1)
- 實現簡潔,通過移動棧頂指針而非真正釋放內存來 "刪除" 元素
注意
- 該函數依賴
STEmpty
函數來判斷棧是否為空,需要確保STEmpty
已正確實現 - 出棧操作只是邏輯上移除元素(移動棧頂指針),并未真正釋放內存,這是棧實現的常見做法
- 調用前應確保棧不為空,否則
assert
會觸發程序中斷
出棧頂元素,元素不會刪除
//出棧頂數據,元素不會刪除
STDataType StackTop(ST* ps)
{assert(!STEmpty(ps));return ps->arr[ps->top - 1];}
注意:
- 棧頂指針
top
指向的是下一個可以插入元素的位置,所以當前棧頂元素的索引是top - 1
- 僅僅返回元素值,不修改
top
指針,因此元素不會被 "刪除" - 依賴
STEmpty
函數判斷棧是否為空,需要確保該函數已正確實現
獲取棧中有效個數
//獲取棧中有效元素個數
int StackSize(ST* ps)
{return ps->top;
}
- 直接返回棧頂指針
top
的值作為棧中有效元素的個數 - 這是因為棧的實現中
top
指針恰好表示了下一個可以插入元素的位置,同時也等于當前棧中元素的數量 - 時間復雜度為 O (1),效率極高
銷毀棧
void StackDestroy(ST* ps)
{assert(ps);free(ps->arr); // 釋放底層數組內存ps->arr = NULL; // 避免野指針ps->top = ps->capacity = 0; // 重置狀態
}
源文件操作
#include"Stack.h"
void test1()
{ST st;StackInit(&st);StackPush(&st,1);StackPush(&st, 1);StackPush(&st, 2);StackPush(&st, 3);StackPush(&st,4);StackPush(&st, 4);StackPush(&st, 5);/*StackPop(&st);StackPop(&st);StackPop(&st);StackPop(&st);StackPop(&st);StackPop(&st);StackPop(&st);*//*while (!STEmpty(&st)){int top = StackTop(&st);printf("%d ", top);StackPop(&st);}*/int size = StackSize(&st);printf("%d\n", size);
}
int main()
{test1();return 0;
}
用棧實現遞歸*
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>// 定義棧結構
typedef int STDataType;
typedef struct Stack {STDataType* arr;int top; // 棧頂指針,指向棧頂元素的下一個位置int capacity; // 容量
} ST;// 棧的基本操作
void StackInit(ST* ps) {assert(ps);ps->arr = NULL;ps->top = ps->capacity = 0;
}void StackPush(ST* ps, STDataType x) {assert(ps);if (ps->top == ps->capacity) {int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));if (tmp == NULL) {perror("realloc");exit(1);}ps->arr = tmp;ps->capacity = newCapacity;}ps->arr[ps->top++] = x;
}void StackPop(ST* ps) {assert(ps);assert(ps->top > 0);ps->top--;
}STDataType StackTop(ST* ps) {assert(ps);assert(ps->top > 0);return ps->arr[ps->top - 1];
}int StackSize(ST* ps) {assert(ps);return ps->top;
}void StackDestroy(ST* ps) {assert(ps);free(ps->arr);ps->arr = NULL;ps->top = ps->capacity = 0;
}// 用棧模擬遞歸計算n的階乘
int Factorial(int n) {if (n < 0) return -1; // 處理異常情況ST stack;StackInit(&stack);// 1. 模擬遞歸調用過程:將所有需要計算的數值入棧while (n > 1) {StackPush(&stack, n);n--;}// 2. 模擬遞歸返回過程:從棧頂開始計算int result = 1;while (StackSize(&stack) > 0) {result *= StackTop(&stack);StackPop(&stack);}StackDestroy(&stack);return result;
}int main() {int num = 5;int result = Factorial(num);printf("%d的階乘是: %d\n", num, result); // 輸出:5的階乘是: 120return 0;
}
隊列(先進先出)
只允許在?端進?插?數據操作,在另?端進?刪除數據操作的特殊線性表
?隊列:進?插?操作的?端稱為隊尾
出隊列:進?刪除操作的?端稱為隊頭

隊列實現
頭文件(基本操作):
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
//隊列結點的結構
typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QueueNode;
//隊列的結構
typedef struct Queue
{QueueNode* phead;QueueNode* ptail;//int size; //隊列中有效數據個數
}Queue;//初始化
void QueueInit(Queue* pq);
//銷毀隊列
void QueueDestroy(Queue* pq);//入隊——隊尾
void QueuePush(Queue* pq, QDataType x);//出隊——隊頭
void QueuePop(Queue* pq);
//隊列判空
bool QueueEmpty(Queue* pq);
//隊列有效元素個數
int QueueSize(Queue* pq);//取隊頭數據
QDataType QueueFront(Queue* pq);
//取隊尾數據
QDataType QueueBack(Queue* pq);
結構
//隊列結點的結構
typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QueueNode;
//隊列的結構
typedef struct Queue
{QueueNode* phead;QueueNode* ptail;//int size; //隊列中有效數據個數
}Queue;
- 插入和刪除操作效率高(隊尾插入、隊首刪除均可在 O (1) 時間完成)
- 不需要預先分配固定大小的內存,動態性好
初始化
//初始化
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;//pq->size = 0;
}
- 使用
assert(pq)
確保傳入的隊列指針pq
不為空,避免空指針操作 - 將隊頭指針
phead
和隊尾指針ptail
都初始化為NULL
,表示初始狀態下隊列為空 - 注釋掉的
pq->size = 0
用于初始化隊列元素個數(如果保留size
成員的話)
判空
/隊列判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}
入隊----隊尾
//入隊——隊尾
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc fail");exit(1);}newnode->data = x;newnode->next = NULL;if (pq ->phead!= NULL)//隊列非空{pq->ptail->next = newnode;pq->ptail = newnode;}else//隊列為空{pq->phead = pq->ptail = newnode;}//size++;
}
- 當隊列為空時(
phead
為NULL
),新節點既是隊頭也是隊尾 - 當隊列非空時,將新節點鏈接到當前隊尾節點(
ptail
)的next
,然后更新ptail
指向新節點
出隊----隊頭
//出隊——隊頭
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));if (pq->phead == pq->ptail)//只有一個節點,頭尾都置為空{free(pq->phead);pq->phead=pq->ptail = NULL;}else{QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}
}
- 分兩種情況處理:
- 當隊列中只有一個節點時(
pq->phead == pq->ptail
):- 釋放該節點內存
- 將
phead
和ptail
都置為NULL
,保持空隊列狀態
- 當隊列中有多個節點時:
- 先保存頭節點的下一個節點指針
- 釋放頭節點內存
- 更新
phead
指向保存的下一個節點
- 當隊列中只有一個節點時(
優勢:
- 正確維護了隊列的頭指針和尾指針狀態
- 避免了內存泄漏(釋放了被移除節點的內存)
- 處理了隊列從有元素變為空的邊界情況
若包含size,size--保持數量一致
取隊頭隊尾數據
//取隊頭數據
QDataType QueueFront(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}//取隊尾數據
QDataType QueueBack(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;}
優勢:
- 時間復雜度都是 O (1),效率很高
- 僅獲取元素值,不會修改隊列的結構和狀態
- 依賴
QueueEmpty
函數判斷隊列是否為空,保持了代碼的一致性 - 符合隊列 "先進先出" 的特性,分別提供了訪問兩端元素的接口
銷毀
//銷毀隊列
void QueueDestroy(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;while (pcur){QueueNode* next = pcur->next;free(pcur);pcur = next;}pq->phead = pq->ptail =NULL;//pq->size =0;
}
銷毀隊列是一個通用操作,即使隊列為空(初始狀態或已清空),也應該允許調用QueueDestroy
,這樣可以避免在調用銷毀函數前還需要手動判斷隊列是否為空。
允許對空隊列進行銷毀
- 當隊列為空時,
pcur
初始為NULL
,循環不會執行,直接重置phead
和ptail
- 當隊列非空時,循環釋放所有節點,最后重置指針