數據結構-隊列(queue)

隊列(queue)是一種採用先進先出(FIFO)策略的抽象數據結構,它的想法來自於生活中排隊的策略。顧客在付款結賬的時候,按照到來的先後順序排隊結賬,先來的顧客先結賬,後來的顧客後結賬。

這裏寫圖片描述

隊列實現

同棧的實現一樣,隊列的實現也有數組實現和鏈表實現兩種方式。

數組實現

先來看看數組實現的方法。棧使用top變量記錄棧頂的位置,而隊列則使用front和rear分別隊列第一個元素和最後一個元素的位置。

這裏寫圖片描述

#define SIZE 20
typedef struct queue
{
    int arr[SIZE];
    int front;
    int rear;

} Queue;

入隊、出隊操作很簡單。入隊時,通過rear的位置判斷隊列是否已滿。如果沒有滿,則將rear後移一位,將新元素放置在rear所在位置。出隊時,也可以通過rear的位置判斷隊列是否爲空。如果不空,則只需將front後移一位即可。 獲取隊頭元素,判斷隊列不空,則只需返回front指向位置的元素即可。

哇塞!好簡單啊!這樣就完成了隊列的實現啊!太天真了。。。想一下,通過出隊操作將數據彈出隊列後,front之前的空間還能夠再次得到嗎?不能。所以使用普通數組實現隊列,就再也不能使用front之前的空間了,這會導致大量空間丟失。

循環數組

爲了解決這個問題,將普通數組換成循環數組。在循環數組中,末尾元素的下一個元素不是數組外,而是數組的頭元素。這樣就能夠再次使用front之前的存儲空間了。

循環數組

哇塞!有解決了這個問題。可以開始碼代碼了!且慢,在實現修改入隊和出隊操作之前,再深思一步,這時是否還能簡單通過判斷rear的位置來判斷隊列空或滿呢?當然不可以。那是否可以考慮front和rear之間的位置關係呢?考慮如下兩種邊界情況:

這裏寫圖片描述

在這兩種情況下,rear都在front前一個位置,無法判斷此時隊列滿還是空。爲了解決這個問題,通常採用的最簡單的方法就是,使用一個counter記錄隊列中元素個數。修改隊列定義爲下:

#define SIZE 20

typedef struct queue
{
    int arr[SIZE];
    int front;
    int rear;
    int counter;

} Queue;

Init(Queue *q)
{
    q->front = 0;
    q->rear = -1;
    q->counter = 0;
} 


bool IsFull(Queue *q)
{
    return (q->counter >= SIZE);
}

void EnQueue(Queue *q, int val)
{
    if(IsFull(q))
        return;

    q->rear = (q->rear + 1) % SIZE;
    q->arr[q->rear] = val;
    q->counter++;
}


bool IsEmpty(Queue *q)
{
    return (q->counter <= 0);
}


void DeQueue(Queue *q)
{
    if (IsEmpty(q))
        return;

    q->front = (q->front + 1) % SIZE;
    q->counter--;

}

//get the front element from queue
int Front(Queue *q)
{
    if (IsEmpty(q))
        return;

    return q->arr[q->front];
}

另外一種方法是,在數組中空出一個位置,用以標記隊列是否已滿。
這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

bool IsFull(Queue *q)
{
    return ((q->rear + 2) % SIZE == q->front);
}

bool IsEmpty(Queue *q)
{
    return ((q->rear + 1) % SIZE == q->front);
}

有時候,rear表示隊尾元素的下一個位置,即表示即將插入元素的位置。
這裏寫圖片描述

此時,判斷隊列的操作應該修改如下:

#define SIZE 20

typedef struct queue
{
    int arr[SIZE];
    int front;
    int rear;

} Queue;


Init(Queue *q)
{
    q->front = 0;
    q->rear = 0;
} 


bool IsFull(Queue *q)
{
    return ((q->rear + 1) % SIZE == q->front);
}

void EnQueue(Queue *q, int val)
{
    if(IsFull(q))
        return;

    q->arr[q->rear] = val;
    q->rear = (q->rear + 1) % SIZE;
}


bool IsEmpty(Queue *q)
{
    return (q->rear == q->front);
}


void DeQueue(Queue *q)
{
    if (IsEmpty(q))
        return;

    q->front = (q->front + 1) % SIZE;

}


int Front(Queue *q)
{
    if (IsEmpty(q))
        return;

    return q->arr[q->front];
}

鏈表實現

隊列的數組實現比較麻煩,需要考慮各種邊界情況,所以通常使用鏈表形式來實現隊列。

使用單向鏈表來實現鏈式隊列,鏈式隊列中存儲front和rear即可。

typedef struct node
{
    int val;
    struct node *next;
}Node;

typedef struct queue
{
    Node *front;
    Node *rear;
};

爲了方便實現,鏈式隊列中的front表示鏈表的頭節點,而front的next才表示隊頭。鏈式隊列的入隊和出隊操作如下圖所示。從圖中可以看出鏈式隊列的實現採用尾進頭出的策略。

鏈式隊列

在鏈式隊列中,不需要考慮隊列是否已滿,只要內存足夠就可以一直分配空間。而當front和rear都指向頭節點的時候,則表示此時隊列爲空。

bool IsEmpty(Queue *q)
{
    return (q->front == q->rear);
}

入隊時移動rear指向最後一個元素即可。

Node* CreateNode(int val)
{
    Node *newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL)
        return NULL;
    else {
        newNode->val = val;
        newNode->next = NULL;
        return newNode;
    }
}


void EnQueue(Queue *q, int val)
{
    Node *newNode = CreateNode(val);
    if (newNode == NULL)
        return;
    else {
        q->rear->next = newNode;
        q->rear = q->rear->next;
    }
}

出隊時,不需要移動front指針,只需將其next指針指向下一個next元素即可。q->front->next = q->front->next->next; 但是需要考慮一種特殊情況,即當隊列中只剩下一個元素時,還需要使rear指針重新指向頭節點。

int Front(Queue *q)
{
    if (IsEmpty(q))
        return -1;
    else
        return (q->front->next->val);
}

void DeQueue(Queue *q)
{
    if (IsEmpty(q))
        return;
    else 
    {
        Node *deNode = q->front->next;
        q->front->next = deNode->next;

        if (q->rear == deNode) //only one element
            q->rear = q->front;

        free(deNode);
    }
}
發佈了37 篇原創文章 · 獲贊 52 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章