目錄
概念
隊列的實現
利用結構體存放隊列結構
為什么單鏈表不使用這種方法?
初始化隊列
小提示:
隊尾入隊列
隊頭出隊列
獲取隊頭元素
獲取隊尾元素
獲取隊列中有效元素個數
檢測隊列是否為空
銷毀隊列
最終代碼
循環隊列?
隊列的OJ題
用隊列實現棧
用棧實現隊列
概念
只允許在一端進行插入數據操作,在另一端進行刪除數據操作的特殊線性表,隊列具有先進先出FIFO(First In First Out) 的特性,進行插入操作的一端稱為隊尾,進行刪除操作的一端稱為隊頭
隊列的實現
tip:隊列也可以數組和鏈表的結構實現,使用鏈表的結構實現更優一些,因為如果使用數組的結構,出隊列在數組頭上出數據,效率會比較低。
利用結構體存放隊列結構
????????我們之前在實現單鏈表的時候使用到了二級指針來達到修改頭尾結點的效果,這樣會增加代碼復雜性和理解難度......
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>//鏈式結構:表示隊列
typedef int QDataType;
typedef struct QueueNode
{QDataType val;struct QueueNode* next;
}QNode;//隊列的結構(使用結構體避免了二級指針的使用)
typedef struct Queue
{QNode* phead;QNode* ptail;int size; //存放隊列大小
}Queue;
????????現在我們依然選擇用單鏈表實現隊列,但是我們將指向鏈表的頭尾結點的指針信息都存放在一個結構體中,這樣就起到了簡化參數傳遞的作用。即在初始化隊列時只需分配一個包含頭尾節點、隊列大小等信息的結構對象,并將其作為參數傳遞給相關函數。這樣就可以直接通過訪問該結構對象中的相應成員變量來修改或獲取所需信息
為什么單鏈表不使用這種方法?
????????這是因為在單鏈表中,使用結構體來表示整個單鏈表可能會帶來一些不必要的復雜性,并且沒有明顯的好處,相比于這種方法使用二級指針會有以下有點:
1. 簡化插入和刪除操作:由于插入或刪除操作需要調整前后兩個節點之間的連接關系(而隊列不需要考慮在指定位置插入的問題),將頭尾結點分別存放在結構體中可能需要更多額外處理步驟才能保持正確連接關系。
2. 節省內存空間:如果每個節點都包含了頭尾信息,則會導致額外占用內存空間,并且增加了維護數據一致性所需付出成本。
3. 降低復雜度:通過僅保存對首元素(即頭部)進行引用,在大多數情況下足以滿足對單鏈表進行各種操作所需求。這樣可以簡化代碼邏輯并提高代碼可讀性與可維護性。
初始化隊列
//初始化隊列
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;pq->size = 0;
}
實現步驟:?
1、利用斷言檢測隊列指針的有效性
2、有效則將隊列初始化為空隊列,即將指向隊頭元素和隊尾元素的指針及隊列元素個數都置為空
小提示:
這個看不看都行
隊尾入隊列
//隊尾入隊列
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");return;}newnode->val = x;newnode->next = NULL;if (pq->ptail == NULL){pq->ptail = pq->phead = newnode;}else{pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;
}
實現步驟:?
1、利用斷言檢測隊列指針的有效性
2、malloc申請新的結點空間,并進行開辟失敗的判斷
3、若開辟成功則向鏈表結點中插入有效數據,以及下一個結點的置空
4、檢測隊列是否為空,若為空則將新申請的結點作為隊列的第一個元素,令指向隊頭和隊尾的指針指向該元素
5、若不為空,則將指向隊尾元素的指針指向新元素,同時將指向隊尾的指針向后移動指向該元素
6、完成一次隊尾入隊操作后,將隊列元素個數加一
隊頭出隊列
// 對頭出隊列
void QueuePop(Queue* pq)
{assert(pq);assert(pq->phead);QNode* del = pq->phead;pq->phead = pq->phead->next;free(del);del = NULL;if (pq->phead == NULL)pq->ptail = NULL;pq->size--;
}
實現步驟:?
1、利用斷言檢測隊列指針的有效性
2、利用斷言檢測隊列頭元素不為空
3、利用臨時指針變量刪除隊頭結點,并將指向隊頭結點指針向后移動,最后釋放該指針并置空
4、隊頭元素出隊列后,若頭指針變為空,則將尾指針也變為空
獲取隊頭元素
//獲取隊頭元素
QDataType QueueFront(Queue* pq)
{assert(pq);// assert(pq->phead);return pq->phead->val;
}
實現步驟:?
1、利用斷言檢測隊列指針的有效性
2、利用斷言檢測隊列頭元素不為空
3、返回此時隊頭元素的值
獲取隊尾元素
//獲取隊頭元素
QDataType QueueFront(Queue* pq)
{assert(pq);// assert(pq->phead);return pq->phead->val;
}
實現步驟:?
1、利用斷言檢測隊列指針的有效性
2、利用斷言檢測隊列頭元素不為空
3、返回此時隊尾元素的值
獲取隊列中有效元素個數
//獲取隊列中有效元素個數
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}
實現步驟:?
1、利用斷言檢測隊列指針的有效性
2、返回結構體中的size值
檢測隊列是否為空
//檢測隊列是否為空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}
實現步驟:?
1、利用斷言檢測隊列指針的有效性
2、返回對pq->phead == NULL的判斷結果,返回結果為真則隊列為空,為假則隊列非空
銷毀隊列
//銷毀隊列
void QueueDestroy(Queue* pq)
{assert(pq);QNode* cur = pq->phead;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->phead = pq->ptail = NULL;pq->size = 0;
}
實現步驟:?
1、利用斷言檢測隊列指針的有效性
2、遍歷每一個隊頭元素并銷毀,最后將頭尾指針及隊列元素數量均置為空
最終代碼
Queue.h文件
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>//鏈式結構:表示隊列
typedef int QDataType;
typedef struct QueueNode
{QDataType val;struct QueueNode* next;
}QNode;//隊列的結構(使用結構體避免了二級指針的使用)
typedef struct Queue
{QNode* phead;QNode* ptail;int size;
}Queue;//初始化隊列
void QueueInit(Queue* pq);//隊尾入隊列
void QueueDestroy(Queue* pq);//隊頭出隊列
void QueuePush(Queue* pq, QDataType x);//獲取隊頭元素
void QueuePop(Queue* pq);//獲取隊尾元素
QDataType QueueFront(Queue* pq);//獲取隊列中有效元素個數
QDataType QueueBack(Queue* pq);//檢測隊列是否為空
bool QueueEmpty(Queue* pq);//銷毀隊列
int QueueSize(Queue* pq);
Queue.c文件
#include"Queue.h"//初始化隊列
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;pq->size = 0;
}//銷毀隊列
void QueueDestroy(Queue* pq)
{assert(pq);QNode* cur = pq->phead;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->phead = pq->ptail = NULL;pq->size = 0;
}//隊尾入隊列
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");return;}newnode->val = x;newnode->next = NULL;if (pq->ptail == NULL){pq->ptail = pq->phead = newnode;}else{pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;
}// 對頭出隊列
void QueuePop(Queue* pq)
{assert(pq);// assert(pq->phead);QNode* del = pq->phead;pq->phead = pq->phead->next;free(del);del = NULL;if (pq->phead == NULL)pq->ptail = NULL;pq->size--;
}//獲取隊頭元素
QDataType QueueFront(Queue* pq)
{assert(pq);// assert(pq->phead);return pq->phead->val;
}//獲取隊尾元素
QDataType QueueBack(Queue* pq)
{assert(pq);// assert(pq->ptail);return pq->ptail->val;
}//檢測隊列是否為空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}//獲取隊列中有效元素個數
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}
test.c文件
#include "Queue.h"int main()
{Queue q;QueueInit(&q);QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);printf("%d ", QueueFront(&q));QueuePop(&q);printf("%d ", QueueFront(&q));QueuePop(&q);QueuePush(&q, 4);QueuePush(&q, 5);while (!QueueEmpty(&q)){printf("%d ", QueueFront(&q));QueuePop(&q);}QueueDestroy(&q);return 0;
}
循環隊列?
概念:循環隊列是一種特殊的隊列數據結構,它通過使用固定大小的數組來實現。與普通隊列不同的是,在循環隊列中,當尾指針(rear)達到數組末尾時,會從數組開頭重新開始存儲元素
優點:充分利用出隊操作后釋放出來的空間,避免頻繁地移動元素
適用場景:循環隊列常用于需要高效處理連續輸入輸出流數據的場景,如緩沖區、任務調度等
關于循環隊列的實現放在下一篇文章......
隊列的OJ題
用隊列實現棧
225. 用隊列實現棧 - 力扣(LeetCode)
具體解題思路如下:
1、題目要求使用兩個隊列,但隊列的基本功能需要自己實現
2、利用棧的結構體存儲兩個隊列的頭尾指針及隊列元素個數的信息
3、為隊列申請結點空間并利用QueueInit函數初始化隊列
4、 入棧時,利用if判斷誰為非空隊列后,將要入棧的元素尾插進非空隊列中(QueueBack函數),出棧的大體過程如下圖所示:
5、出棧時,為了將隊列的最后一個元素先排出,就需要讓原來存儲有效數據的隊列的前N-1個元素放入空的隊列中,然后再將元隊列中的最后一個元素排出,兩個隊列的作用是來回切換的需要利用if語句判斷隊列的空與非空后在進行操作,出棧的大體過程如下圖所示:
6、獲取棧頂元素時,只需要判斷兩個隊列誰不為空,將不為空的隊列的隊尾元素返回即可
7、判斷棧是否為空時,當兩個隊列均為空時棧才能為空,所以需要用&&
8、銷毀棧時、不僅要釋放掉malloc申請的內存空間,還要將兩個隊列一同銷毀
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>//鏈式結構:表示隊列
typedef int QDataType;
typedef struct QueueNode
{QDataType val;struct QueueNode* next;
}QNode;//隊列的結構(使用結構體避免了二級指針的使用)
typedef struct Queue
{QNode* phead;QNode* ptail;int size;
}Queue;//初始化隊列
void QueueInit(Queue* pq);//隊尾入隊列
void QueueDestroy(Queue* pq);//隊頭出隊列
void QueuePush(Queue* pq, QDataType x);//獲取隊頭元素
void QueuePop(Queue* pq);//獲取隊尾元素
QDataType QueueFront(Queue* pq);//獲取隊列中有效元素個數
QDataType QueueBack(Queue* pq);//檢測隊列是否為空
bool QueueEmpty(Queue* pq);//銷毀隊列
int QueueSize(Queue* pq);//初始化隊列
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;pq->size = 0;
}//銷毀隊列
void QueueDestroy(Queue* pq)
{assert(pq);QNode* cur = pq->phead;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->phead = pq->ptail = NULL;pq->size = 0;
}//隊尾入隊列
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");return;}newnode->val = x;newnode->next = NULL;if (pq->ptail == NULL){pq->ptail = pq->phead = newnode;}else{pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;
}// 對頭出隊列
void QueuePop(Queue* pq)
{assert(pq);// assert(pq->phead);QNode* del = pq->phead;pq->phead = pq->phead->next;free(del);del = NULL;if (pq->phead == NULL)pq->ptail = NULL;pq->size--;
}//獲取隊頭元素
QDataType QueueFront(Queue* pq)
{assert(pq);assert(pq->phead);return pq->phead->val;
}//獲取隊尾元素
QDataType QueueBack(Queue* pq)
{assert(pq);assert(pq->ptail);return pq->ptail->val;
}//檢測隊列是否為空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}//獲取隊列中有效元素個數
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}typedef struct {Queue q1;Queue q2;
} MyStack;//初始化棧
MyStack* myStackCreate() {//申請結點空間MyStack* pst = (MyStack*)malloc(sizeof(MyStack));//取地址后就可以操作棧結構體中p1和p2中的內容QueueInit(&pst->q1); //->的優先級大于&QueueInit(&pst->q2); //->的優先級大于&return pst;
}//入棧
void myStackPush(MyStack* obj, int x) {//如果q1隊列不為空則q1隊列用于存放導出的數據if(!QueueEmpty(&obj->q1)){QueuePush(&obj->q1,x);}//如果q1隊列為空則q2隊列用于存放導出的數據else{QueuePush(&obj->q2,x);}
}//出棧
int myStackPop(MyStack* obj) {//起始假設q1為空,q2不為空,empty指針指向空隊列,noempty指向非空隊列Queue* empty = &obj->q1;Queue* noempty = &obj->q2;//如果我們假設失敗則證明q1不為空,q2為空,交換標志if(!QueueEmpty(&obj->q1)){empty = &obj->q2;noempty = &obj->q1;}//然后將隊列中的前N-1個元素導入空隊列,所以循環結束的條件就是隊列中元素等于1(這個剩下的就是要出棧的元素)while(QueueSize(noempty) > 1){QueuePush(empty,QueueFront(noempty));QueuePop(noempty);}//將隊列中前N-1個元素導出后剩余的就是要出棧的元素,將該元素存儲進整型變量top中int top = QueueFront(noempty);//存儲完成后將該元素也進行出隊列操作QueuePop(noempty);//最后返回要出棧的元素return top;
}//獲取棧頂元素
int myStackTop(MyStack* obj) {//誰不為空就獲取誰的隊尾元素if(!QueueEmpty(&obj->q1)){return QueueBack(&obj->q1);}//如果q1隊列為空則q2隊列用于存放導出的數據else{return QueueBack(&obj->q2);}
}//判斷棧是否為空
bool myStackEmpty(MyStack* obj) {//只有當兩個隊列均為空的時候,該棧才不會有有效數據return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}//銷毀棧
void myStackFree(MyStack* obj) {//初始化時除了malloc了一個結點,還初始化了兩個隊列,所以除了要將申請的結點釋放還要將申請的兩個隊列銷毀QueueDestroy(&obj->q1);QueueDestroy(&obj->q2);free(obj);
}
關于&pst->q1的解釋:
為了操作隊列,我們在初始化棧時需要將隊列對象作為參數傳遞給函數,即&pst->q1,它表示獲取指針?
pst
?所指向的結構體中成員變量 q1
?的地址(可以理解為隊列q1的地址),而pst是一個指向MyStack結構體的指針,所以說就是獲取MyStack結構體中成員變量的地址。
用棧實現隊列
232. 用棧實現隊列 - 力扣(LeetCode)
具體解題思路如下:
1、題目要求使用兩個棧,但棧的基本功能需要自己實現
2、利用棧的結構體存儲兩個隊列的頭尾指針及隊列元素個數的信息
3、為棧申請結點空間并利用STInit函數初始化棧
4、入隊時,直接利用STPush函數進行入棧即可
5、先返回隊頭元素然后再出隊,先判斷用于出數據的popst棧是否為空,如果為空則將pushst棧中的數據倒順序后放入popst棧中,利用STPush函數將pushst棧的棧頂元素放入空的popst棧中
6、出隊時,用臨時變量front接收返回的隊頭元素,然后利用STPop函數將該元素出隊,最后返回該元素的值
7、判斷隊列是否為空時,當兩個棧均為空時隊列才能為空,所以需要用&&
8、銷毀棧時、不僅要釋放掉malloc申請的內存空間,還要將兩個棧一同銷毀
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>//支持動態增長的棧
typedef int STDataType;
typedef struct Stack
{STDataType* a;int top; //表示棧頂位置int capacity; //棧的容量
}ST;// 初始化棧
void STInit(ST* ps);// 銷毀棧
void STDestroy(ST* ps);// 入棧
void STPush(ST* ps, STDataType data);// 出棧
void STPop(ST* ps);// 獲取棧頂元素
STDataType STTop(ST* ps);// 獲取棧中有效元素個數
int STSize(ST* ps);// 檢測棧是否為空,如果為空返回非零結果,如果不為空返回0
int STEmpty(ST* ps);// 初始化棧
void STInit(ST* pst)
{//首先要指向一個棧assert(pst);pst->a = NULL;pst->capacity = 0;pst->top = 0; //令pop表示棧頂元素的下一個元素的下標
}// 銷毀棧
void STDestroy(ST* pst)
{//首先要指向一個棧assert(pst);//正常操作不再過多復述free(pst->a);pst->a = NULL;pst->top = pst->capacity = 0;
}//入棧
void STPush(ST* pst, STDataType x)
{//首先要指向一個棧assert(pst);//判斷棧是否已滿,如果棧滿則申請新的內存空間if (pst->top == pst->capacity){int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newCapacity);if (tmp == NULL){perror("realloc fail");return;}pst->a = tmp;pst->capacity = newCapacity;}//如果棧未滿則進行入棧操作(若初始化時pop=-1,則下面兩行代碼交換執行順序)pst->a[pst->top] = x; //此時pop表示的是棧頂元素的下一個元素的下標 pst->top++; //top表示的下標數++
}//出棧
void STPop(ST* pst)
{//首先要指向一個棧assert(pst);//top<0表示棧為空,top=0表示沒有元素入棧,存在這兩種情況就不能執行出棧操作(可以提供的圖理解)assert(pst->top > 0);//直接對top執行減減操作以獲取實際數組元素下標pst->top--;
}// 獲取棧頂元素
STDataType STTop(ST* pst)
{//首先要指向一個棧assert(pst);//top<0表示棧為空,top=0表示沒有元素入棧,存在這兩種情況就不能執行出棧操作(可以提供的圖理解)assert(pst->top > 0);//當初始化top=0時,top的值與實際數組元素下標的值之間的關系是:實際下標 = top-1//所以這里要進行減一操作得到實際的數組元素下標后再輸出return pst->a[pst->top - 1];
}//獲取棧中有效元素個數
int STSize(ST* pst)
{//首先要指向一個棧assert(pst);//初始化top=0,則top等于多少棧中就有多少個元素return pst->top;
}//檢測棧是否為空,如果為空返回非零結果,如果不為空返回0
int STEmpty(ST* pst)
{//首先要指向一個棧assert(pst);//如果pst->top不為空則返回結果為真,為空則返回假return pst->top == NULL;
}typedef struct {ST pushst;ST popst;
} MyQueue;//初始化棧
MyQueue* myQueueCreate() {MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));STInit(&obj->pushst);STInit(&obj->popst);return obj;
}//入隊
void myQueuePush(MyQueue* obj, int x) {STPush(&obj->pushst,x);
}//出隊
int myQueuePop(MyQueue* obj) {int front = myQueuePeek(obj);STPop(&obj->popst);return front;
}//返回隊頭元素
int myQueuePeek(MyQueue* obj) {//棧不為空就倒數據if(STEmpty(&obj->popst)){//將pusust里面的數據倒轉順序后傳遞給popstwhile(!STEmpty(&obj->pushst)){STPush(&obj->popst, STTop(&obj->pushst));STPop(&obj->pushst);}}return STTop(&obj->popst);
}//判斷是否為空
bool myQueueEmpty(MyQueue* obj) {return STEmpty(&obj->popst) && STEmpty(&obj->pushst);
}//銷毀隊
void myQueueFree(MyQueue* obj) {STDestroy(&obj->popst);STDestroy(&obj->pushst);
}/*** Your MyQueue struct will be instantiated and called as such:* MyQueue* obj = myQueueCreate();* myQueuePush(obj, x);* int param_2 = myQueuePop(obj);* int param_3 = myQueuePeek(obj);* bool param_4 = myQueueEmpty(obj);* myQueueFree(obj);
*/
關于&pst->pushst和&pst->popst的解釋:
~over~?