【數據結構】線性表--隊列
- 一.什么是隊列
- 二.隊列的實現
- 1.隊列結構定義:
- 2.隊列初始化函數:
- 3.隊列銷毀函數:
- 4.入隊列函數(尾插):
- 5.出隊列函數(頭刪):
- 6.取隊頭元素:
- 7.取隊尾元素:
- 8.求隊長:
- 9.判空:
- 三.總結
一.什么是隊列
情景引入:
你們在用電腦時有沒有經歷過,機器有時會處于疑似死機的狀態,鼠標點什么似乎都沒用,雙擊任何快捷方式都不動彈。就當你失去耐心,打算reset時,突然它像酒醒了一樣,把你剛才單擊的所有操作全部都按順序執行了一遍。這是因為操作系統在當時可能CPU一時忙不過來,等前面的事忙完后,后面多個指令需要通過一個通道輸出,按先后次序排隊執行造成的結果。
再比如像移動、聯通、電信等客服電話,客服人員與客戶相比總是少數,在所有的客服人員都占線的情況下,客戶會被要求等待,直至有某個客服人員空下來,才能讓最先等待的客戶接通電話。這里也是將所有當前撥打客服電話的客戶進行了排隊處理。
操作系統和客服系統中,都是應用了一種數據結構來實現剛才提到的先進先出的排隊功能,這就是隊列。
概念理解:
隊列也是一種操作受限的線性表,與棧不同的是,隊列要求在一端插入數據,在另一端刪除數據(就類似于日常生活中的排隊一樣)。
我們將插入數據的一端稱為隊尾,刪除數據的一端稱為隊頭。隊列滿足FIFO(first in first out)原則。如圖所示:
二.隊列的實現
隊列也有兩種表示方法:①順序存儲②鏈式存儲。
順序存儲即采用數組做為底層結構,由于在一端插入數據,另一端刪除數據,此時需要大量移動元素。
鏈式存儲可以使用單鏈表,也可以使用雙向鏈表。
使用單鏈表時,可以將鏈表頭作為隊頭,鏈表尾作為隊尾,此時刪除數據的操作即是頭刪,時間復雜度為O(1),但是插入數據時,需要進行頻繁的尾插操作,時間復雜度為O(n),因此可以再設立一個尾指針指向最后一個結點,避免每次尾插需要遍歷整個鏈表,優化時間復雜度為O(1)。
使用雙鏈表時,即頭插尾刪,或頭刪尾插。
本文使用單鏈表實現隊列。
1.隊列結構定義:
因為使用單鏈表來實現隊列,所以先定義出結點結構。結點需要存儲數據本身信息,以及保存下一個結點的位置信息。
typedef int QDataType;typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QNode;
因為隊列需要進行頻繁的尾插操作,所以再定義一個結構,用頭指針和尾指針來保存隊列的頭結點和尾結點的信息,以優化尾插操作的時間復雜度。
typedef struct Queue
{QNode* head;QNode* tail;
}Queue;
2.隊列初始化函數:
隊列初始化即將頭指針和尾指針均置為空即可。
//初始化函數
void QueueInit(Queue* pq)
{assert(pq);pq->head = pq->tail = NULL;
}
3.隊列銷毀函數:
隊列的銷毀即通過頭指針來找到各個結點,將結點空間依次釋放,最后再將頭尾指針再次置空。
//銷毀函數
void QueueDestory(Queue* pq)
{assert(pq);QNode* cur = pq->head;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->head = pq->tail = NULL;
}
4.入隊列函數(尾插):
數據元素入隊列:先以元素的值申請一個新結點,然后通過尾指針找到最后一個結點,將新結點鏈接到最后一個結點的next指針,最后再將尾指針指向新結點。
//入隊列函數
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));//處理申請空間失敗的情況if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = NULL;//隊列為空時,將頭尾指針指向同一個結點if (pq->head == NULL)pq->head = pq->tail = newnode;else{//先將新結點鏈接到隊列的隊尾pq->tail->next = newnode;//再將尾指針指向新結點pq->tail = newnode;}
}
5.出隊列函數(頭刪):
出隊列時需要注意先判斷隊列是否為空,當隊列為空時不能出數據,可以使用assert斷言,也可以直接返回。
不為空時通過頭指針找到第一個結點,再順著找到下一個結點(若下一個結點為空,則代表此時鏈表只有一個結點,直接刪除),釋放掉第一個結點,并將頭指針指向下一個結點。
//出隊列函數
void QueuePop(Queue* pq)
{assert(pq);//隊列為空時不能出數據(或者使用斷言)if (pq->head == NULL){return;}else{//先記錄頭指針的下一個結點//需要先判斷下一個結點是不是空if (pq->head->next == NULL){free(pq->head);pq->head = pq->tail = NULL;}else{QNode* next = pq->head->next;//釋放頭指針指向的結點free(pq->head);//將頭指針指向下一個結點,成為新的隊頭pq->head = next;}}
}
6.取隊頭元素:
取隊頭元素也要注意判斷隊列是否為空,使用斷言判斷,不為空直接返回頭指針指向第一個結點的值。
//取隊頭數據
QDataType QueueFront(Queue* pq)
{assert(pq);assert(pq->head);//隊為空不能取數據return pq->head->data;
}
7.取隊尾元素:
取隊尾元素也要注意判斷是否為空,使用斷言判斷。不為空時直接返回尾指針指向結點的值。
//取隊尾數據
QDataType QueueBack(Queue* pq)
{assert(pq);assert(pq->head);//隊為空不能取數據return pq->tail->data;
}
8.求隊長:
求隊長直接用一個int變量,通過頭指針依次遍歷到尾指針,該變量++即可。
//求隊長(數據個數)
int QueueSize(Queue* pq)
{assert(pq);int size = 0;QNode* cur = pq->head;while (cur != NULL){size++;cur = cur->next;}return size;
}
9.判空:
直接看頭指針是否為空即可判斷隊列是否為空。
//判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->head == NULL;//隊列為空返回true,否則返回false
}
三.總結
除了上述實現的普通隊列,還有循環隊列、優先隊列、雙端隊列、阻塞隊列等隊列,感興趣可以上網搜索一下相關實現。
以下是本文全部源碼:
Queue.h:
#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>typedef int QDataType;typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QNode;typedef struct Queue
{QNode* head;QNode* tail;
}Queue;//初始化函數
void QueueInit(Queue* pq);//銷毀函數
void QueueDestory(Queue* pq);//入隊列函數
void QueuePush(Queue* pq, QDataType x);//出隊列函數
void QueuePop(Queue* pq);//取隊頭數據
QDataType QueueFront(Queue* pq);//取隊尾數據
QDataType QueueBack(Queue* pq);//求隊長(數據個數)
int QueueSize(Queue* pq);//判空
bool QueueEmpty(Queue* pq);
Queue.c:
#include"Queue.h"//初始化函數
void QueueInit(Queue* pq)
{assert(pq);pq->head = pq->tail = NULL;
}//銷毀函數
void QueueDestory(Queue* pq)
{assert(pq);QNode* cur = pq->head;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->head = pq->tail = NULL;
}//入隊列函數
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));//處理申請空間失敗的情況if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = NULL;//隊列為空時,將頭尾指針指向同一個結點if (pq->head == NULL)pq->head = pq->tail = newnode;else{//先將新結點鏈接到隊列的隊尾pq->tail->next = newnode;//再將尾指針指向新結點pq->tail = newnode;}
}//出隊列函數
void QueuePop(Queue* pq)
{assert(pq);//隊列為空時不能出數據(或者使用斷言)if (pq->head == NULL){return;}else{//先記錄頭指針的下一個結點//需要先判斷下一個結點是不是空if (pq->head->next == NULL){free(pq->head);pq->head = pq->tail = NULL;}else{QNode* next = pq->head->next;//釋放頭指針指向的結點free(pq->head);//將頭指針指向下一個結點,成為新的隊頭pq->head = next;}}
}//取隊頭數據
QDataType QueueFront(Queue* pq)
{assert(pq);assert(pq->head);//隊為空不能取數據return pq->head->data;
}//取隊尾數據
QDataType QueueBack(Queue* pq)
{assert(pq);assert(pq->head);//隊為空不能取數據return pq->tail->data;
}//求隊長(數據個數)
int QueueSize(Queue* pq)
{assert(pq);int size = 0;QNode* cur = pq->head;while (cur != NULL){size++;cur = cur->next;}return size;
}//判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->head == NULL;//隊列為空返回true,否則返回false
}
test.c:
//測試各個函數功能
void test()
{Queue q;QueueInit(&q);QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);QueuePush(&q, 4);QueuePush(&q, 5);while (!QueueEmpty(&q)){printf("%d ", QueueFront(&q));QueuePop(&q);}QueueDestory(&q);
}int main()
{test();return 0;
}
感謝閱讀!^_^