栈和队列OJ练习——栈实现队列,队列实现栈
文章目錄
- ?棧實現隊列
- 🥝雙棧數據倒入法
- 🌠隊列“壓棧”
- 🌠棧的“出隊”
- 🌠棧"取隊頭"
- 🌠遍歷雙棧“隊列”
- 🌠棧的隊列判空和釋放
- ?隊列實現棧
- 🥝雙隊列結點遷移法
- 🌠隊列“壓棧”
- 🌠隊列“彈棧”
- 🌠隊列取“棧頂”
- 🌠雙隊列的棧中元素個數
- 🌠雙隊列的棧遍歷
- ?后話
?棧實現隊列
棧與隊列的數據存儲方式完全不同,棧的數據遵循先進后出模式FILO,而隊列為先進先出模式FIFO,要想使用棧的結構實現隊列的數據增刪模式,需要使用棧的性質并對其稍加巧用,就可以達到同隊列的數據存儲訪問相同的效果。
注意,本章中用棧實現隊列所用到的棧函數,以及隊列實現棧使用到的隊列接口函數都在上一章模擬實現提及到,詳情請參照上一章,鏈接在此數據結構——棧和隊列_VelvetShiki_Not_VS的博客-CSDN博客。
🥝雙棧數據倒入法
定義兩個棧,一個用于臨時存放壓棧的數據,命名其為Push棧,再定義一個專用于出數據的棧Pop,當數據壓入時,全部壓入Push棧,只要遇到出棧命令,就將Push棧的所有數據取頂并全部倒入Pop棧中,此時的Pop棧數據為Push棧數據的逆序,即如果Push棧的數據壓入為1,2,3,4,分別取頂為4,3,2,1并壓入Pop棧,此時Pop棧由棧頂自棧底的數據依次為1,2,3,4,如果全部出棧則剛好與數據的入棧順序相同(即入棧1,2,3,4,出棧也為1,2,3,4的順序)。
🎀雙棧指針的結構定義
棧的基本結構定義遵循順序表的定義方式,包含存儲數據的數值域,標識棧頂下標的整型top,標識棧容量的整型capacity,相關棧函數接口可參考上述棧和隊列鏈接,本章的棧轉換隊列算法引用的棧接口函數不再詳細贅述。
//棧定義 typedef int STEtype; typedef struct Stack {STEtype* arr; //棧通過順序表實現,定義可擴容數組,容量和頂部int top;int capacity; }ST;//兩個棧底指針的結構體 typedef struct MyQueue {ST* Push; //Push棧用于數據壓入的臨時存放ST* Pop; //當需要出隊時,將Push棧的數據取頭倒入Pop棧,并逐個取頭出隊 }MQ;🎀棧指針結構體初始化函數
MQ* StructInit() //初始化棧指針 {MQ* Stacks = (MQ*)malloc(sizeof(MQ));Stacks->Push = StackInit();Stacks->Pop = StackInit();return Stacks; }🌠隊列“壓棧”
在隊列一章我們曾使用鏈表的方式實現隊列數據的入隊,而使用順序表的方式同樣可是實現入隊操作,此處因為需使用棧的空間結構實現隊列功能,實質上也使用了順序表的方式入隊。對于隊列的“壓棧”操作,只需根據需求將需要入隊的數據全部壓到Push棧中即可,原理圖如下:
🎀隊列壓棧函數
void Push(MQ* obj, STEtype x) {assert(obj);StackPush(obj->Push, x); //調用了棧自身的壓棧函數 }🌠棧的“出隊”
使用棧入隊與壓棧的方式大體相同,因為不需要對數據進行過多的操作,直接將數據存入即可,如果此時直接從Push棧里將數據彈出,就是棧的出棧方式。但如果想要模擬隊列的出隊方式,需要對棧的彈出做些文章,原理圖如下:
🎀棧出隊函數
void Pop(MQ* obj) {assert(obj); //檢查兩個棧底指針有效性if (StackEmpty(obj->Pop)) //如果Pop棧不為空,則執行Push棧中數據的倒入{while (!StackEmpty(obj->Push)) //如果Push棧不為空,則繼續將數據取頂倒入Pop,再將Push頂依次彈棧{StackPush(obj->Pop, StackTop(obj->Push));StackPop(obj->Push);}}StackPop(obj->Pop); //將位于Pop棧頂元素彈出 }🎃一定要記住,要符合棧的隊列出隊性質,數據的入隊只能從Push棧一端進入,而數據的出隊只能從Pop棧一端彈出。
🌠棧"取隊頭"
對于雙棧結構的隊列取隊頭元素,就是取非空Pop棧的棧頂元素,如果僅有Pop棧為空,Push棧非空,則從Push棧元素全部取頂壓入Pop棧再取頂(Pop不彈棧);如果兩棧均空,則由棧取頂函數StackTop中判斷棧為空,返回無意義值-1。
🎀棧取隊頭函數
STEtype Peek(MQ* obj) //取隊頭元素 {assert(obj);if (StackEmpty(obj->Pop)){while (!StackEmpty(obj->Push)){StackPush(obj->Pop, StackTop(obj->Push));StackPop(obj->Push);}}return StackTop(obj->Pop); }🌈測試用例
MQ* MyQ = StructInit(); printf("%d", Peek(MyQ)); Push(MyQ, 1); printf("隊頭元素為:%d\n", Peek(MyQ)); Push(MyQ, 2); printf("隊頭元素為:%d\n", Peek(MyQ)); Push(MyQ, 3); printf("隊頭元素為:%d\n", Peek(MyQ)); Push(MyQ, 4); printf("隊頭元素為:%d\n", Peek(MyQ)); Pop(MyQ); printf("隊頭元素為:%d\n", Peek(MyQ));觀察結果
🌠遍歷雙棧“隊列”
對于棧和隊列的結構已知,如果要對這兩種特殊的線性結構進行數值遍歷,則需要清空棧或隊列的元素,將元素全部彈棧或出隊,這樣遍歷完成后棧或隊列均為空。對于使用棧結構模擬的隊列亦是如此。
🎀遍歷函數
void PrintQueue(MQ* obj) {assert(obj);printf("Head-> ");while (!StackEmpty(obj->Pop)) //如果Pop棧不為空,將Pop棧元素循環取頂并彈棧{printf("%d ", Peek(obj));Pop(obj);}if (!StackEmpty(obj->Push)) //此時Pop棧一定為空,再判斷Push棧是否為空{Peek(obj); //如果非空,則將Push棧中元素循環取頂并壓入Pop棧while (!StackEmpty(obj->Pop)){printf("%d ", Peek(obj)); //再將從Push棧中壓入Pop棧的數據依次取頂并彈棧Pop(obj);}}printf("<-Tail\n"); }🌈測試用例
//雙棧初始化為空 MQ* MyQ = StructInit(); //入隊&出隊 Push(MyQ, 1); Push(MyQ, 2); Push(MyQ, 3); Pop(MyQ); Push(MyQ, 4); Push(MyQ, 5); Push(MyQ, 6); Push(MyQ, 7); Pop(MyQ); //兩次隊列遍歷 PrintQueue(MyQ); PrintQueue(MyQ);🌈觀察結果
Head-> 3 4 5 6 7 <-Tail Head-> <-Tail🌠棧的隊列判空和釋放
當兩個棧均為空時,雙棧構成的隊列才為空。
🎀判空函數
bool Empty(MQ* obj) //判斷隊列是否為空 {assert(obj);return StackEmpty(obj->Push) && StackEmpty(obj->Pop); }釋放雙棧構成的隊列時,需要將先前開辟過的所有內存空間均釋放,這些空間包括:
🎀雙棧的隊列釋放函數
MQ* Free(MQ* obj) //銷毀隊列 {assert(obj); //檢查雙棧指針有效性obj->Push = StackDestroy(obj->Push); //分別釋放兩個棧,順序可以顛倒obj->Pop = StackDestroy(obj->Pop);free(obj); //再釋放雙棧結構體信息指針,置空后返回obj = NULL;return obj; }🌈調試觀察結果
?隊列實現棧
在前一章中,我們使用了鏈表的方式實現了隊列的基本結構,而使用隊列的結構來實現棧,其思維邏輯與棧實現隊列的大體相同,都是需要進行數據的倒入和交換。因為需要使用到隊列的結構,所以隊列的基本函數接口也可以參考前章中如上文的棧和隊列鏈接,而本課題在此基礎上實現對棧的數據存儲和訪問先進后出(FILO)的模擬。
🥝雙隊列結點遷移法
使用雙棧的隊列模擬實現,使用了兩個棧Push和Pop棧分別實現入隊與數據倒入Pop棧再取頂彈出的方式實現出隊操作。而要使用隊列模擬棧的數據存儲和訪問方式,也需要使用到兩個隊列,分別進行結點數據的入隊和出隊,整體思路大致如下圖所示:
🎃可以看出,雙隊列的“壓棧”操作與隊列的入隊幾乎沒有區別,都是將數據直接入隊“壓棧”即可,而在“彈棧”過程中,隊列將數據的出隊為了滿足棧的性質,即最先存儲的數據最后出隊,而最后存儲的數據反而最先出隊,所以需要將隊列最后入隊的那個數據出隊即可。但根據隊列的性質,出隊操作僅能對隊頭元素彈出,所以將一個非空隊列中最后一個結點元素前面的所有結點都依次入隊到另一個隊列,再將最后一個元素出隊,即可滿足要求。
🎀雙隊列結構體模擬棧
typedef int QEtype; typedef struct Queue //基于鏈表結構的隊列結構體定義 {QEtype data;struct Queue* next; }QE;//指向兩個隊列指針的指針 typedef struct MyStack {QE* Q1; //指向第一個隊列QE* Q2; //指向第二個隊列 }MST;🎀雙隊列指針初始化函數
MST* StackCreate() {MST* Queues = (MST*)malloc(sizeof(MST)); //開辟包含隊列指針信息的結構體空間初始化Queues->Q1 = Queues->Q2 = NULL; //將兩個指向Q1和Q2隊列的指針初始化置空return Queues; }🎀雙隊列指針銷毀函數
MST* Free(MST* obj) {assert(obj);QueueDestroy(&obj->Q1); //將隊列Q1和Q2銷毀,即鏈表中所有結點的釋放,并將隊列指針置空QueueDestroy(&obj->Q2);free(obj); //將指向隊列指針信息的結構體形參指針釋放并置空,并返回給實參obj = NULL;return obj; }🎀雙隊列判空函數:
bool Empty(MST* obj) {assert(obj);return QueueEmpty(&(obj->Q1)) && QueueEmpty(&(obj->Q2)); }🌠隊列“壓棧”
從開頭的原理圖可看出,對于隊列的壓棧操作與入隊基本一致,因為是基于鏈表對數據的存儲,所以只需向非空隊列的一端入隊數據即可。
void Push(MST* obj, QEtype x) {assert(obj);if (QueueEmpty(&obj->Q1)) //判斷Q1是否為空,如果為空則保持指針指向不變,如果非空,則此時Q2一定為空,指針交換指向{QueuePush(&obj->Q2, x); //將新增數據入隊到非空隊列中(可能是Q1隊列,也有可能是Q2隊列)}else{QueuePush(&obj->Q1, x); } }🎃需要注意的是,使用雙隊列結構入隊,入隊的一端永遠是非空隊列,而另一個隊列一定為空。不能對空隊列進行入隊操作,而空隊列與非空隊列不能確定具體是Q1或是Q2,因為在不斷的入隊與出隊過程中,兩個隊列都分別可能對空隊列或非空隊列,但入隊操作僅能在非空隊列的一方進行。如果初始兩個隊列均為空,依據上述函數作if判斷,當Q1為空時,默認Q2為非空隊列(即使Q2隊列本身也為空),向其中入隊數據即可。
🌠隊列“彈棧”
隊列出隊要遵循棧的彈棧規則,就必須讓隊列進行結點數據的遷移,讓非空隊列的隊尾結點數據進行單獨的出隊“彈棧”。
🎀雙隊列彈棧函數
void Pop(MST* obj) {assert(obj);if (Empty(obj)) //如果雙隊列均為空,則無需出隊{return;}QE** Empty = &(obj->Q1), ** NonEmpty = &(obj->Q2); //將兩個隊列指針取地址,分別賦值給定義的二級指針空和非空if (!QueueEmpty(Empty)) //如果空指針指向隊列不為空,則交換兩個指針指向{Empty = &(obj->Q2);NonEmpty = &(obj->Q1);}while (QueueSize(NonEmpty) > 1) //將非空隊列的數據除隊末結點元素外,全部壓入空隊列一方{QueuePush(Empty, QueueTop(NonEmpty));QueuePop(NonEmpty);}QueuePop(NonEmpty); //最后將僅留下一個結點的原非空隊末元素出隊,同時此隊列變為空隊列 }原理圖如下
🌠隊列取“棧頂”
雙隊列結構棧的取棧頂元素函數也需要尋找空與非空隊列,在隊列結構中,非空隊列的隊尾元素是最后入隊的,所以該元素即為棧的棧頂元素,遍歷非空隊列并對隊尾元素取值返回即可。
🎀雙隊列棧取頂函數
QEtype Top(MST* obj) {assert(obj);if (Empty(obj)) //如果雙隊列均為空,則無棧頂元素可取{return NULL;}QE** Empty = &obj->Q1, ** NonEmpty = &obj->Q2; if (!QueueEmpty(Empty)) //重新分配空與非空指向隊列指針,原理與Pop函數交換一致 {Empty = &obj->Q2;NonEmpty = &obj->Q1;}QE* tail = *NonEmpty; //定義臨時遍歷找尾結點指針tail,對非空隊列進行遍歷取尾while (tail->next){tail = tail->next;}return tail->data; //找到末節點,返回結點數值域值,且不彈棧(不出隊) }🌠雙隊列的棧中元素個數
🎀雙隊列的棧元素個數計算函數
int Size(MST* obj) {assert(obj);if (Empty(obj)){return 0;}if (QueueEmpty(&obj->Q1)){return QueueSize(&obj->Q2);}return QueueSize(&obj->Q1); }🌠雙隊列的棧遍歷
棧或隊列的遍歷都會清空其結構的所有元素,只不過每次出隊或彈棧都會先將隊列取頂后再出隊。
🎀遍歷函數
void Print(MST* obj) {assert(obj);printf("Top-> ");while (!Empty(obj)){printf("%d ", Top(obj));Pop(obj);}printf("<-Bot\n"); }🌈測試用例1
//棧的隊列指針初始化 MST* MyST = StackCreate(); //隊列壓棧 Push(MyST, 1); Push(MyST, 2); Push(MyST, 3); Push(MyST, 4); //遍歷打印和全部彈棧 Print(MyST); printf("棧頂元素為:%d\n", Top(MyST)); printf("棧中有%d個元素\n", Size(MyST)); printf("棧是否為空:%d\n", Empty(MyST));🌈結果觀察
Top-> 4 3 2 1 <-Bot 棧頂元素為:-1 棧中有0個元素 棧是否為空:1🌈測試用例2
MST* MyST = StackCreate(); Push(MyST, 1); Push(MyST, 2); Push(MyST, 3); Push(MyST, 4); printf("棧頂元素為:%d\n", Top(MyST)); Pop(MyST); Pop(MyST); printf("棧頂元素為:%d\n", Top(MyST)); Push(MyST, 5); Push(MyST, 6); Push(MyST, 7); printf("棧頂元素為:%d\n", Top(MyST)); Pop(MyST); Push(MyST, 8); Push(MyST, 9); printf("棧中有%d個元素\n", Size(MyST)); printf("棧是否為空:%d\n", Empty(MyST)); Print(MyST); printf("棧頂元素為:%d\n", Top(MyST)); printf("棧是否為空:%d\n", Empty(MyST));🌈結果觀察
棧頂元素為:4 棧頂元素為:2 棧頂元素為:7 棧中有6個元素 棧是否為空:0 Top-> 9 8 6 5 2 1 <-Bot 棧頂元素為:-1 棧是否為空:1🌈測試用例3
MST* MyST = StackCreate(); Push(MyST, 1); Push(MyST, 2); Push(MyST, 3); Push(MyST, 4); MyST = Free(MyST); Print(MyST);🌈結果觀察
當函數執行到Free釋放時,可看到實參和旗下指針已經被全部釋放:
所以當空指針進入Print打印遍歷函數時,就會被指針斷言判空所截斷,程序終止。
為空:%d\n", Empty(MyST));
🌈測試用例3
MST* MyST = StackCreate(); Push(MyST, 1); Push(MyST, 2); Push(MyST, 3); Push(MyST, 4); MyST = Free(MyST); Print(MyST);🌈結果觀察
當函數執行到Free釋放時,可看到實參和旗下指針已經被全部釋放:
所以當空指針進入Print打印遍歷函數時,就會被指針斷言判空所截斷,程序終止。
?后話
總結
以上是生活随笔為你收集整理的栈和队列OJ练习——栈实现队列,队列实现栈的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我的原创软件作品——弹窗拦截器V1.0.
- 下一篇: C++实现车轮轨迹