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