目錄
第1步:設計藍圖 (The Struct)
第2步:隊列的誕生 (創建與初始化)
第3步:狀態檢查 (判滿與判空)
第4步:核心操作 (入隊與出隊)
入隊 (Enqueue)
出隊 (Dequeue)
第5步:善后工作 (銷毀隊列)
現在,我們聚焦于如何用代碼一步步地實現這個最終方案。我們從零開始,只寫必要的代碼,并在每一步都解釋為什么這么寫。
數據結構:隊列(Queue)與循環隊列(Circular Queue)-CSDN博客
第1步:設計藍圖 (The Struct)
從第一性原理出發,要描述一個隊列,我們需要知道哪些信息?
-
數據存放在哪里? -> 需要一個指針指向我們的數據區。 (
int* data
) -
隊列的總容量是多少? -> 需要一個變量記錄容量,這樣我們的取模運算才有依據。 (
int capacity
) -
隊頭在哪里? -> 需要一個下標作為隊頭指針。 (
int front
) -
隊尾在哪里? -> 需要一個下標作為隊尾指針。 (
int rear
)
把這些信息組合起來,就形成了我們的“藍圖”——結構體定義。
【代碼實現 1:結構體定義】
#include <stdio.h>
#include <stdlib.h> // 我們需要用 malloc 和 free 來動態管理內存// 循環隊列的藍圖
typedef struct {int* data; // 指向一塊用于存儲數據的內存int capacity; // 這塊內存的總容量 (實際能存的元素數是 capacity-1)int front; // 隊頭元素的下標int rear; // 隊尾元素的下一個位置的下標
} CircularQueue;
這就像建房子前畫的設計圖,它定義了我們的隊列由哪幾部分構成。
第2步:隊列的誕生 (創建與初始化)
有了藍圖,我們就可以“施工”了,即創建一個具體的隊列實例。創建一個容量為 k
的隊列,需要做什么?
-
為
CircularQueue
這個結構體本身分配一塊內存。 -
為
data
指針指向的數組分配一塊內存。根據我們的最終方案,要存儲k
個元素,需要k+1
的數組空間。 -
設置初始狀態。一個空隊列的初始狀態是什么?根據我們的約定,是
front
和rear
相等。最簡單的就是都設為 0。
【代碼實現 2:創建隊列函數】
// 功能:創建一個能容納 k 個元素的空隊列
CircularQueue* createQueue(int k) {// 1. 為隊列的整體結構分配內存CircularQueue* q = (CircularQueue*)malloc(sizeof(CircularQueue));// 防御性編程:檢查內存是否分配成功if (!q) {perror("Failed to allocate memory for queue structure");return NULL;}// 2. 設置容量。注意,我們需要 k+1 個空間q->capacity = k + 1;// 3. 為實際存儲數據的數組分配內存q->data = (int*)malloc(q->capacity * sizeof(int));// 再次進行防御性編程if (!q->data) {perror("Failed to allocate memory for queue data");free(q); // 如果數據區分配失敗,必須釋放之前為結構體分配的內存,防止內存泄漏return NULL;}// 4. 初始化頭尾指針,表示隊列為空q->front = 0;q->rear = 0;// 5. 返回創建好的隊列實例return q;
}
第3步:狀態檢查 (判滿與判空)
在進行核心操作(入隊/出隊)之前,必須先能準確判斷隊列的狀態。這就像開車前要先看油表和儀表盤。
-
判空 (isEmpty): 我們的約定是
front == rear
。 -
判滿 (isFull): 我們的約定是隊尾的下一個位置是隊頭,即
(rear + 1) % capacity == front
。
這些是實現后續功能的基石。
【代碼實現 3:狀態判斷函數】
// 功能:判斷隊列是否為空
int isEmpty(CircularQueue* q) {// 直接翻譯我們的約定: front 等于 rearreturn q->front == q->rear;
}// 功能:判斷隊列是否為滿
int isFull(CircularQueue* q) {// 直接翻譯我們的約定: (rear + 1) 對 capacity 取模后等于 frontreturn (q->rear + 1) % q->capacity == q->front;
}
這兩個函數非常簡潔,但它們是整個循環隊列邏輯的核心。
第4步:核心操作 (入隊與出隊)
現在,萬事俱備,我們可以實現隊列的兩個核心靈魂操作了。
入隊 (Enqueue)
要將一個值 value
加入隊尾,需要三步:
-
檢查:隊列是不是已經滿了?如果滿了,就不能再入了。這是操作的“前置條件”。
-
放置:如果沒滿,就把
value
放到rear
指向的位置。 -
更新:
rear
指針需要向前移動一位,為下一次入隊做準備。別忘了,要用取模運算來實現循環。
【代碼實現 4:入隊函數】
// 功能:將一個元素 value 加入隊尾
int enqueue(CircularQueue* q, int value) {// 1. 前置條件檢查if (isFull(q)) {printf("入隊失敗:隊列已滿。\n");return 0; // 返回 0 表示失敗}// 2. 放置數據q->data[q->rear] = value;// 3. 更新隊尾指針,使用取模運算實現循環q->rear = (q->rear + 1) % q->capacity;return 1; // 返回 1 表示成功
}
出隊 (Dequeue)
要從隊頭取出一個元素,邏輯類似:
-
檢查:隊列是不是空的?如果是空的,就無元素可取。這是操作的“前置條件”。
-
取出:如果沒空,就從
front
指向的位置獲取元素值。 -
更新:
front
指針需要向前移動一位,指向新的隊頭。同樣,要用取模運算。
【代碼實現 5:出隊函數】
// 功能:從隊頭取出一個元素,并通過指針 pValue 返回該元素的值
int dequeue(CircularQueue* q, int* pValue) {// 1. 前置條件檢查if (isEmpty(q)) {printf("出隊失敗:隊列為空。\n");return 0; // 失敗}// 2. 取出數據*pValue = q->data[q->front];// 3. 更新隊頭指針,使用取模運算實現循環q->front = (q->front + 1) % q->capacity;return 1; // 成功
}
第5步:善后工作 (銷毀隊列)
我們用 malloc
申請了內存,在程序結束前,作為一個負責任的程序員,必須將這些內存歸還給操作系統,否則會造成內存泄漏。
銷毀的順序應該是:先釋放內部的 data
數組,再釋放隊列結構體本身。
【代碼實現 6:銷毀函數】
// 功能:釋放隊列占用的所有內存
void destroyQueue(CircularQueue* q) {if (q != NULL) {// 1. 如果 data 指針有效,則先釋放它指向的數組內存if (q->data != NULL) {free(q->data);}// 2. 再釋放隊列結構體本身的內存free(q);}
}
至此,我們已經從一張“藍圖”開始,一步步地構建了創建、檢查、操作、銷毀一個完整循環隊列所需的所有代碼。每一步的實現都緊密圍繞著我們最初通過第一性原理推導出的核心約定。