數據結構與算法 - 隊列

本文首發於 我的博客node

以前分享了一篇關於棧這種數據結構的邏輯和實現,這篇文章咱們看看隊列這種數據結構是一種什麼樣的結構以及如何從順序存儲和鏈式存儲去實現這麼一個結構。git

隊列也是一種線性數據結構,跟棧的結構差很少,惟一不一樣的的就是 棧是先進後出隊列是先進先出 也就是一般所說的 FIFO : first in first out !github

順序存儲隊列

以上是一個順序存儲隊列的示意圖,標紅的字母表示目前不在隊列中,可是這些數據依舊在開闢的存儲空間(數組)中(俗稱髒數據) ,這點很重要,由於咱們只是挪動頭尾表明的索引,並不會刪除具體的數據,從圖中能夠看出:數組

  • ①圖:空隊列的時候隊頭和隊尾都指向默認的一個節點markdown

  • ②圖:入隊的時候只須要挪動隊尾的位置便可數據結構

  • ③圖:出隊同入隊只須要挪動隊頭的位置便可app

  • ③④圖:當隊尾達到存儲空間尾部即表明隊列已滿oop

    綜上咱們會發現當 Q.rear 到達尾部存儲空間的時候表明隊列已滿,可是 Q.front 可能已經隨着出隊列已經空出了一些空間,這樣就致使了整個存儲空間的浪費。第二在咱們 圖中若是 f 數據也出隊列的話 Q.rear == Q.front 跟咱們判斷隊列是否爲空是同樣的,因此就致使了整個判斷的多意性,因此咱們須要另外的一種形式來表示咱們的線性隊列spa

循環隊列

爲了不存儲空間的浪費以及這種結構的重複利用性,出現 循環隊列 的存儲結構,看起來是這樣:設計

可是這個結構仍然有個問題就是依舊沒法判斷隊滿仍是隊空,計算機科學領域的任務問題均可以經過增長一箇中間層來解決 ,這也同樣,犧牲掉一個存儲單元來對他們進行分隔就很容易判斷了,其實說是犧牲一個存儲單元不如說是額外增長一個存儲單元,講道理不是一個意思嘛!

循環隊列的線性結構

typedef struct Node {
    Data data[MAXSIZE];
    int rear;
    int front;
} Queue;
複製代碼

基於此種結構,咱們對它的一些方法進行構造:

// 初始化一個空隊列
Status InitQueue(Queue *Q) {
    Q->rear = 0;
    Q->rear = 0;
    return SUCCESS;
}

// 清空一個隊列
Status ClearQueue(Queue *Q) {
    Q->rear = 0;
    Q->front = 0;
    return SUCCESS;
}

// 是否爲空隊列
Status IsEmpty(Queue Q) {
    return Q.rear == Q.front ? TRUE:FALSE;
}

// 隊列長度
int Length(Queue Q) {
    return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}

// 獲取頭節點
Status GetHead(Queue Q,Data *data) {
    if (IsEmpty(Q)) return ERROR;
    *data = Q.data[Q.front];
    return SUCCESS;
}

// 入丟列
Status QueueEnter(Queue *Q,Data data) {
    if ((Q->rear+1)%MAXSIZE == Q->front) return ERROR;
    Q->data[Q->rear] = data;
    // rear 指針向後移一位,若是到達最後則轉到數組頭部
    Q->rear = (Q->rear+1)%MAXSIZE;
    return SUCCESS;
}

// 出隊列
Status QueuePop(Queue *Q, Data *data) {
    if (IsEmpty(*Q)) return ERROR;
    *data = Q->data[Q->front];
    // front 同上面出隊列的rear同樣
    Q->front = (Q->front+1)%MAXSIZE;
    return SUCCESS;
}

// 遍歷隊列
Status QueueTraverse(Queue Q) {
    if (IsEmpty(Q)) return ERROR;
    int i = Q.front;
    while ((i+Q.front) != Q.rear) {
        printf("%d   ",Q.data[i]);
        i = (i+1)%MAXSIZE;
    }
    printf("\n");
    return SUCCESS;
}
複製代碼

注意循環隊列的關鍵點在於

  • 咱們犧牲了一個節點,因此在於隊列是否滿的狀態判斷中要針對 +1 進行操做
  • 循環隊列的循環入隊以及出隊的過程當中涉及到 rearfront 位置的變遷,這也涉及到一個環狀計算的問題,這裏通常使用 %(模) 運算進行處理

鏈式存儲隊列

相比線性循環隊列而言,對列的鏈式存儲方式就簡單太多,其實說到底就是一個 鏈表,具體怎麼設計就看本身了,這裏我以 帶頭節點的單向鏈表做爲例子:

如圖咱們首先要定義節點的結構和隊列的結構,隊列的結構又依託於節點,因此它們的結構應該是這樣:

typedef int Status;
typedef int Data;

typedef struct Node {
    Data data;
    struct Node *next;
} Node;

typedef struct {
    Node *front;
    Node *rear;
} Queue;
複製代碼

其實說到底仍是隊單鏈表的處理:

// 初始化隊列
Status InitQueue(Queue *Q) {
    Q->front = Q->rear = (Node *)malloc(sizeof(Node));
    if (!Q->front) return ERROR;
    Q->front->next = NULL;
    return SUCCESS;
}

// 銷燬隊列
Status DestroyQueue(Queue *Q) {
    // 遍歷鏈表進行銷燬
    while (Q->front) {
        Node *temp = = Q->front;
        Q->front = Q->front->next;
        free(temp);
    }
    return SUCCESS;
}

// 將隊列置空
Status ClearQueue(Queue *Q) {
    Node *target = Q->front->next;
    while (target) {
        Node *temp = target;
        target = target->next;
        free(temp);
    }
    Q->front->next = Q->rear->next = NULL;
    return SUCCESS;
}

// 判斷隊列是否爲空
Status IsEmpty(Queue Q) {
    return (Q.rear == Q.front)?TRUE:FALSE;
}

// 獲取隊列的長度
int GetLength(Queue Q) {
    Node *target = Q.front->next;
    int i=0;
    while (target) {
        i++;
        target = target->next;
    }
    return i;
}

// 入隊
Status QueueEnter(Queue *Q,Data data) {
    Node *node = (Node *)malloc(sizeof(node));
    if (!node) return ERROR;
    node->data = data;
    node->next = NULL;
    Q->rear->next = node;
    // 修改隊尾指針
    Q->rear = node;
    return SUCCESS;
}

// 出隊
Status QueuePop(Queue *Q,Data *data) {
    if (Q->front == Q->rear) return ERROR;
    Node *temp = Q->front->next;
    *data = temp->data;
    Q->front->next = temp->next;
    // 若是隻有一個節點,就要移動尾指針
    if (temp == Q->rear) {
        Q->rear = Q->front;
    }
    free(temp);
    return SUCCESS;
}

// 獲取隊頭元素
Status GetHead(Queue Q,Data *data) {
    if (Q.front == Q.rear) return ERROR;
    *data = Q.front->next->data;
    return SUCCESS;
}

// 遍歷隊列
Status QueueTraverse(Queue Q) {
    Node *target = Q.front->next;
    printf("打印隊列是:")
    while (target) {
        printf("%d ",target->data);
        target = target->next;
    }
    printf("\n");
    return SUCCESS;
}
複製代碼

關於單鏈表前面咱們已經有文章作過概述,這裏咱們就不對其進行一一驗證,具體的代碼有須要請前往下載代碼

這篇文章主要是講述了隊列的結構原理以及隊列的兩種實現方式,但願可以講清楚,有問題還請隨時留言,謝謝!

相關文章
相關標籤/搜索