我幼儿园的弟看了都直呼简单的【栈和队列】
目錄
- 1. 棧
- 1.1 棧的概念及結(jié)構(gòu)
- 1.2 實(shí)現(xiàn)棧
- 2. 隊(duì)列
- 2.1 隊(duì)列的概念及結(jié)構(gòu)
- 2.2 隊(duì)列的實(shí)現(xiàn)
- 3. 概念選擇題
- 4. 棧和隊(duì)列OJ題
- 4.1 括號(hào)匹配問(wèn)題
- 4.2 用隊(duì)列實(shí)現(xiàn)棧
- 4.3 用棧實(shí)現(xiàn)隊(duì)列
- 4.4 設(shè)計(jì)循環(huán)隊(duì)列
1. 棧
1.1 棧的概念及結(jié)構(gòu)
棧:一種特殊的線性表(在邏輯上是連續(xù)存儲(chǔ)的,物理上不一定連續(xù)),其只允許在固定的一端進(jìn)行插入和刪除元素操作。
進(jìn)行數(shù)據(jù)插入和刪除操作的一端稱為棧頂,另一端稱為棧底。棧中的數(shù)據(jù)元素遵守后進(jìn)先出LIFO(Last In First Out)的原則。
例如上面在棧中的數(shù)據(jù)只能先依次拿出棧頂?shù)臄?shù)據(jù),出棧的順序依此是4,3,2,1,入棧時(shí)是1,2,3,4,所以后進(jìn)先出也可以理解為先進(jìn)后出。
壓棧:棧的插入操作叫做進(jìn)棧/壓棧/入棧,入數(shù)據(jù)在棧頂。
出棧:棧的刪除操作叫做出棧,出數(shù)據(jù)也在棧頂。
注意:這里的棧是數(shù)據(jù)結(jié)構(gòu)里的棧,主要是在內(nèi)存堆區(qū)中對(duì)數(shù)據(jù)進(jìn)行管理。而不是操作系統(tǒng)中劃分內(nèi)存區(qū)域中的棧,但是都有一個(gè)特點(diǎn),都遵循后進(jìn)先出。
1.2 實(shí)現(xiàn)棧
棧的實(shí)現(xiàn)一般可以使用數(shù)組或者鏈表實(shí)現(xiàn),相對(duì)而言數(shù)組的結(jié)構(gòu)實(shí)現(xiàn)更優(yōu)一些。因?yàn)閿?shù)組在尾上插入數(shù)據(jù)的代價(jià)比較小,并且數(shù)組的地址是連續(xù)的,訪問(wèn)起來(lái)緩存的利用率也會(huì)更高一些。
但因?yàn)閿?shù)組并不是隨時(shí)動(dòng)態(tài)開(kāi)辟的空間,如果數(shù)組元素滿了需要考慮擴(kuò)容,一次擴(kuò)容原來(lái)的2倍,勢(shì)必會(huì)有一些空間上的浪費(fèi)。
如果比較在意空間,可以使用帶頭雙向鏈表,這是比較方便的,如果使用單鏈表,要把鏈表的頭結(jié)點(diǎn)作為棧頂,尾結(jié)點(diǎn)作為棧底,因?yàn)殒湵淼念^插頭刪比較高效。
代碼實(shí)現(xiàn):
//函數(shù)聲明 #pragma once#include <stdio.h> #include <assert.h> #include <stdlib.h> #include <stdbool.h>//創(chuàng)建棧 typedef int DATATYPE; typedef struct Stack {DATATYPE* stack;int top;int capacity; }Stack;//初始化棧 void InintStack(Stack* ptrs);//入棧 void PushStack(Stack* ptrs, DATATYPE data);//出棧 void PopStack(Stack* ptrs);//訪問(wèn)棧頂數(shù)據(jù) DATATYPE StackTop(Stack* ptrs);//判斷棧是否為空 bool StackEmpty(Stack* ptrs);//數(shù)據(jù)個(gè)數(shù) int StackSize(Stack* ptrs);//銷毀棧 void DestroyStack(Stack* ptrs);注意:不要直接訪問(wèn)結(jié)構(gòu)體中的數(shù)據(jù),即使非常簡(jiǎn)單也要封裝為一個(gè)函數(shù)來(lái)訪問(wèn)。
//函數(shù)實(shí)現(xiàn) #define _CRT_SECURE_NO_WARNINGS 1 #include "StackQueue.h"//初始化棧 void InintStack(Stack* ptrs) {assert(ptrs);ptrs->stack = NULL;//注意如果初始話top為0//那么棧頂?shù)脑叵聵?biāo)時(shí)top-1ptrs->top = ptrs->capacity = 0; }//檢查容量 void checkSys(Stack* ptrs) { if (ptrs->capacity == ptrs->top){int newCapacity = ptrs->capacity == 0 ? sizeof(DATATYPE) : 2 * ptrs->capacity;DATATYPE* ret = (DATATYPE*)realloc(ptrs->stack, sizeof(DATATYPE) * newCapacity);if (!ret){perror("calloc fali");exit(-1);}ptrs->stack = ret;ptrs->capacity = newCapacity;} }//入棧 void PushStack(Stack* ptrs, DATATYPE data) {assert(ptrs);checkSys(ptrs);ptrs->stack[ptrs->top++] = data; }//出棧 void PopStack(Stack* ptrs) {assert(ptrs);assert(!StackEmpty(ptrs));ptrs->top--; }//訪問(wèn)棧頂數(shù)據(jù) DATATYPE StackTop(Stack* ptrs) {assert(ptrs);assert(!StackEmpty(ptrs));return ptrs->stack[ptrs->top - 1]; }//判斷棧是否為空 bool StackEmpty(Stack* ptrs) {assert(ptrs);return ptrs->top == 0; }//數(shù)據(jù)個(gè)數(shù) int StackSize(Stack* ptrs) {assert(ptrs);return ptrs->top; }//銷毀棧 void DestroyStack(Stack* ptrs) {assert(ptrs);free(ptrs->stack);ptrs->top = ptrs->capacity = 0;ptrs->stack = NULL; } //測(cè)試邏輯 #define _CRT_SECURE_NO_WARNINGS 1 #include "StackQueue.h"void test() {Stack sk;InintStack(&sk);PushStack(&sk, 1);PushStack(&sk, 2);printf("%d ", StackTop(&sk));PopStack(&sk);PushStack(&sk, 3);PushStack(&sk, 4);printf("%d ", StackTop(&sk));PopStack(&sk);PushStack(&sk, 5);PushStack(&sk, 6);//遍歷完棧就相當(dāng)于清空棧了while (!StackEmpty(&sk)){printf("%d ", StackTop(&sk));PopStack(&sk);}DestroyStack(&sk); } int main() {test();return 0; }2. 隊(duì)列
2.1 隊(duì)列的概念及結(jié)構(gòu)
隊(duì)列:只允許在一端進(jìn)行插入數(shù)據(jù)操作,在另一端進(jìn)行刪除數(shù)據(jù)操作的特殊線性表,隊(duì)列具有先進(jìn)先出FIFO(First In First Out)的規(guī)則。
入隊(duì)列:進(jìn)行插入操作的一端稱為隊(duì)尾
出隊(duì)列:進(jìn)行刪除操作的一端稱為隊(duì)頭。
2.2 隊(duì)列的實(shí)現(xiàn)
隊(duì)列也可以數(shù)組和鏈表的結(jié)構(gòu)實(shí)現(xiàn),使用鏈表的結(jié)構(gòu)實(shí)現(xiàn)更優(yōu)一些,因?yàn)槿绻褂脭?shù)組的結(jié)構(gòu),出隊(duì)列在數(shù)組頭上出數(shù)據(jù),就需要挪動(dòng)后面的數(shù)據(jù),效率會(huì)比較低。
代碼實(shí)現(xiàn):
//函數(shù)聲明 #pragma once #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <stdbool.h> //創(chuàng)建隊(duì)列 typedef int QDataType; typedef struct QueueNode {QDataType data;struct QueueNode* next; }QNode;//因?yàn)殛?duì)列只有頭刪尾插 //因此需要定義兩個(gè)指針 //一個(gè)指向隊(duì)頭,另一個(gè)指向隊(duì)尾 typedef struct Queue {QNode* head;QNode* tail;int size; }Queue;// 初始化隊(duì)列 void QueueInit(Queue* q);// 隊(duì)尾入隊(duì)列 void QueuePush(Queue* q, QDataType data);// 隊(duì)頭出隊(duì)列 void QueuePop(Queue* q);// 獲取隊(duì)列頭部元素 QDataType QueueFront(Queue* q);// 獲取隊(duì)列隊(duì)尾元素 QDataType QueueBack(Queue* q);// 獲取隊(duì)列中有效元素個(gè)數(shù) int QueueSize(Queue* q);// 檢測(cè)隊(duì)列是否為空,如果為空返回非零結(jié)果,如果非空返回0 int QueueEmpty(Queue* q);// 銷毀隊(duì)列 void QueueDestroy(Queue* q); //函數(shù)實(shí)現(xiàn) #define _CRT_SECURE_NO_WARNINGS 1 #include "Queue.h" // 初始化隊(duì)列 void QueueInit(Queue* q) {assert(q);q->head = q->tail = NULL;q->size = 0; }// 隊(duì)尾入隊(duì)列 void QueuePush(Queue* q, QDataType data) {assert(q);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (!newnode){perror("malloc fail");exit(-1);}newnode->data = data;newnode->next = NULL;//當(dāng)頭指針為空需要特殊處理if (q->head == NULL){q->head = q->tail = newnode;}else{q->tail->next = newnode;q->tail = q->tail->next;}q->size++; }// 隊(duì)頭出隊(duì)列 void QueuePop(Queue* q) {assert(q);assert(!QueueEmpty(q));if (q->head->next == NULL){free(q->head);q->head = q->tail = NULL;q->size = 0;}else{QNode* del = q->head;q->head = q->head->next;free(del);del = NULL;q->size--;} }// 獲取隊(duì)列頭部元素 QDataType QueueFront(Queue* q) {assert(q);assert(!QueueEmpty(q));return q->head->data; }// 獲取隊(duì)列隊(duì)尾元素 QDataType QueueBack(Queue* q) {assert(q);assert(!QueueEmpty(q));return q->tail->data; }// 獲取隊(duì)列中有效元素個(gè)數(shù) int QueueSize(Queue* q) {assert(q);return q->size; }// 檢測(cè)隊(duì)列是否為空,如果為空返回非零結(jié)果,如果非空返回0 int QueueEmpty(Queue* q) {assert(q);return q->head == NULL && q->tail == NULL; }// 銷毀隊(duì)列 void QueueDestroy(Queue* q) {assert(q);QNode* cur = q->head;while (cur){QNode* del = cur;cur = cur->next;free(del);del = NULL;}q->head = q->tail = NULL; } //主邏輯測(cè)試 void testQueue() {Queue qq;QueueInit(&qq); QueuePush(&qq, 1);QueuePush(&qq, 2);printf("%d ", QueueFront(&qq));QueuePop(&qq);QueuePush(&qq, 3);QueuePush(&qq, 4);printf("%d ", QueueFront(&qq));QueuePop(&qq);QueuePush(&qq, 5);while (!QueueEmpty(&qq)){printf("%d ", QueueFront(&qq));QueuePop(&qq);}QueueDestroy(&qq); } int main() {//testStack();testQueue();return 0; }注意:一個(gè)入隊(duì)列順序?qū)?yīng)一個(gè)出隊(duì)列順序,一個(gè)入棧順序?qū)?yīng)多個(gè)出棧順序
3. 概念選擇題
一個(gè)棧的初始 狀態(tài)為空。現(xiàn)將元素1、2、3、4、5、A、B、C、D、E依次入棧,然后再依次出棧,則元素出棧的順序是( )。
A 12345ABCDE
B EDCBA54321
C ABCDE12345
D 54321EDCBA
若進(jìn)棧序列為 1,2,3,4 ,進(jìn)棧過(guò)程中可以出棧,則下列不可能的一個(gè)出棧序列是()
A 1,4,3,2
B 2,3,4,1
C 3,1,4,2
D 3,4,2,1
現(xiàn)有一循環(huán)隊(duì)列,其隊(duì)頭指針為front,隊(duì)尾指針為rear;循環(huán)隊(duì)列長(zhǎng)度為N。其隊(duì)內(nèi)有效長(zhǎng)度為?(假設(shè)多給一個(gè)空間,實(shí)際長(zhǎng)度為N)
A (rear - front + N) % N + 1
B (rear - front + N) % N
C (rear - front) % (N + 1)
D (rear - front + N) % (N - 1)
答案:1.B 2.C 3.B
第一題很簡(jiǎn)單,先進(jìn)后出原則。
第二題需要注意的是在入棧的過(guò)程中是可以直接出棧的,比如說(shuō)入棧的順序?yàn)?,2,3,4,那么出棧的順序也是1,2,3,4這是因?yàn)榭梢匀?出1,入2出2…,因此只需要往選項(xiàng)中代入就可以找出不可能的一個(gè)順序。
一個(gè)入棧順序是可能有多個(gè)出棧順序。
第三題實(shí)際長(zhǎng)度為N,有效范圍為N-1,直接帶入即可求出當(dāng)前的有效數(shù)據(jù)。
4. 棧和隊(duì)列OJ題
4.1 括號(hào)匹配問(wèn)題
來(lái)源:Leetcode。OJ鏈接
給定一個(gè)只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判斷字符串是否有效。
有效字符串需滿足:
左括號(hào)必須用相同類型的右括號(hào)閉合。
左括號(hào)必須以正確的順序閉合。
解題思路:本題解法所需要的數(shù)據(jù)結(jié)構(gòu)正是棧。
4.2 用隊(duì)列實(shí)現(xiàn)棧
來(lái)源:Leetcode。OJ鏈接
題目描述:請(qǐng)你僅使用兩個(gè)隊(duì)列實(shí)現(xiàn)一個(gè)后入先出(LIFO)的棧,并支持普通棧的全部四種操作(push、top、pop 和 empty)。
實(shí)現(xiàn) MyStack 類:
void push(int x) 將元素 x 壓入棧頂。
int pop() 移除并返回棧頂元素。
int top() 返回棧頂元素。
boolean empty() 如果棧是空的,返回 true ;否則,返回 false 。
思路:由于隊(duì)列的性質(zhì)是先進(jìn)先出,和棧先進(jìn)后出相反,因此需要用到兩個(gè)隊(duì)列q1和q2,其中一個(gè)為push的隊(duì)列,另一個(gè)為pop的輔助隊(duì)列。
具體地,當(dāng)pop時(shí),找到兩個(gè)中不為空的隊(duì)列,把不為空的前N-1個(gè)數(shù)據(jù)全部依次push到為空的輔助隊(duì)列,此時(shí)剩下的一個(gè)元素(隊(duì)尾的元素)即為棧頂元素,pop后此隊(duì)列為空。當(dāng)下一次pop時(shí),也是同樣的操作。
4.3 用棧實(shí)現(xiàn)隊(duì)列
來(lái)源:Leetcode。OJ鏈接
請(qǐng)你僅使用兩個(gè)棧實(shí)現(xiàn)先入先出隊(duì)列。隊(duì)列應(yīng)當(dāng)支持一般隊(duì)列支持的所有操作(push、pop、peek、empty):
實(shí)現(xiàn) MyQueue 類:
void push(int x) 將元素 x 推到隊(duì)列的末尾
int pop() 從隊(duì)列的開(kāi)頭移除并返回元素
int peek() 返回隊(duì)列開(kāi)頭的元素
boolean empty() 如果隊(duì)列為空,返回 true ;否則,返回 false
解題思路:棧的規(guī)則是先進(jìn)后出,如何利用兩個(gè)棧實(shí)現(xiàn)先進(jìn)先出模擬隊(duì)列?
不難發(fā)現(xiàn),如果一個(gè)棧只入數(shù)據(jù),另一個(gè)棧從入棧中依次取出棧頂?shù)臄?shù)據(jù)后再出棧,這種出棧的順序正好符合先進(jìn)先出。
具體地,定義一個(gè)入棧s1,出棧s2,當(dāng)入數(shù)據(jù)全部入到s1中,當(dāng)出數(shù)據(jù)時(shí)判斷s2是否為空,如果為空,開(kāi)始從s1的棧頂取出數(shù)據(jù),每次取出后,pop掉s1中棧頂?shù)臄?shù)據(jù)。
全部取出放入s2后,再進(jìn)行出棧操作,數(shù)據(jù)的出棧順序就變成了先進(jìn)先出。
s2的棧頂就可以理解為隊(duì)列的隊(duì)頭,此時(shí)s2棧頂?shù)臄?shù)據(jù)依次出棧(出隊(duì)),就模擬出了隊(duì)列的效果。
4.4 設(shè)計(jì)循環(huán)隊(duì)列
來(lái)源:Leetcode。OJ鏈接
設(shè)計(jì)你的循環(huán)隊(duì)列實(shí)現(xiàn)。 循環(huán)隊(duì)列是一種線性數(shù)據(jù)結(jié)構(gòu),其操作表現(xiàn)基于 FIFO(先進(jìn)先出)原則并且隊(duì)尾被連接在隊(duì)首之后以形成一個(gè)循環(huán)。它也被稱為“環(huán)形緩沖器”。
循環(huán)隊(duì)列的一個(gè)好處是我們可以利用這個(gè)隊(duì)列之前用過(guò)的空間。在一個(gè)普通隊(duì)列里,一旦一個(gè)隊(duì)列滿了,我們就不能插入下一個(gè)元素,即使在隊(duì)列前面仍有空間。但是使用循環(huán)隊(duì)列,我們能使用這些空間去存儲(chǔ)新的值。
你的實(shí)現(xiàn)應(yīng)該支持如下操作:
- MyCircularQueue(k): 構(gòu)造器,設(shè)置隊(duì)列長(zhǎng)度為 k 。
- Front: 從隊(duì)首獲取元素。如果隊(duì)列為空,返回 -1 。
- Rear: 獲取隊(duì)尾元素。如果隊(duì)列為空,返回 -1 。
- enQueue(value): 向循環(huán)隊(duì)列插入一個(gè)元素。如果成功插入則返回真。
- deQueue(): 從循環(huán)隊(duì)列中刪除一個(gè)元素。如果成功刪除則返回真。
- isEmpty(): 檢查循環(huán)隊(duì)列是否為空。
- isFull(): 檢查循環(huán)隊(duì)列是否已滿。
環(huán)形隊(duì)列可以使用數(shù)組實(shí)現(xiàn),也可以使用循環(huán)鏈表實(shí)現(xiàn)。
環(huán)形隊(duì)列第一個(gè)比較棘手的問(wèn)題是如何判空以及如何判滿,有兩種方法:
1、添加一個(gè)size變量用來(lái)記錄數(shù)據(jù)個(gè)數(shù)
2、額外增加一個(gè)空間,滿的時(shí)候永遠(yuǎn)留一個(gè)位置。比如說(shuō)有效數(shù)據(jù)個(gè)數(shù)為4,那么開(kāi)空間時(shí)開(kāi)辟5個(gè)數(shù)據(jù)的空間,在判斷時(shí)會(huì)比較方便。
如上圖,到這里其實(shí)可以發(fā)現(xiàn)使用鏈表來(lái)實(shí)現(xiàn)的話會(huì)有一個(gè)不好的地方,不方便取出尾結(jié)點(diǎn)的數(shù)據(jù),因?yàn)閞ear那地方并不是有效數(shù)據(jù)的位置,除非再定義一個(gè)指針,指向尾結(jié)點(diǎn)的前一個(gè)結(jié)點(diǎn),但是實(shí)現(xiàn)起來(lái)就比較麻煩了。但是使用數(shù)組就比較方便訪問(wèn)了,因?yàn)榭梢灾С窒聵?biāo)快速訪問(wèn),無(wú)需遍歷,rear-1就可以取出最后一個(gè)位置的數(shù)據(jù),因此這題選擇數(shù)組來(lái)實(shí)現(xiàn)。
如果選擇數(shù)組,還有一個(gè)小問(wèn)題是判滿情況,判滿的表達(dá)式為rear+1 == front,如果下標(biāo)rear+1越界了如何處理?
可以使用取模運(yùn)算來(lái)巧妙地解決這個(gè)問(wèn)題,因?yàn)樵摂?shù)組的實(shí)際大小為5(下標(biāo)訪問(wèn)范圍為0~4),而實(shí)際有效的范圍為4(下標(biāo)訪問(wèn)范圍為0~3),因此,當(dāng)rear的下標(biāo)為4的時(shí)候,說(shuō)明到了數(shù)組的最后一個(gè)位置,此時(shí)令(rear+1) %= 5,讓其回到數(shù)組最開(kāi)頭的位置,這也就是循環(huán)數(shù)組一個(gè)最基本的做法(到達(dá)最后一個(gè)位置時(shí)再回到最開(kāi)始的位置),這時(shí)判斷rear == front。
搞清楚這個(gè)之后,實(shí)現(xiàn)循環(huán)隊(duì)列就比較簡(jiǎn)單了.
typedef struct {int* arr;int front;int rear;int arrSize; } MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));//多增加一個(gè)位置obj->arr = (int*)malloc(sizeof(int)*(k+1));obj->front = obj->rear = 0;obj->arrSize = k+1;return obj; } //頭尾相等則為空 bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->front == obj->rear; } //尾+1等于頭則為滿 bool myCircularQueueIsFull(MyCircularQueue* obj) {return ((obj->rear+1) % obj->arrSize) == obj->front; } //插入數(shù)據(jù) bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if(myCircularQueueIsFull(obj)){return 0;}obj->arr[obj->rear] = value;obj->rear++;//++后如果==arrSize,讓其%arrSize回到0//小于%后值不變obj->rear %= obj->arrSize; return 1; } //刪除數(shù)據(jù) bool myCircularQueueDeQueue(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj)){return 0;}//同樣的道理,如果++等于arrSizeobj->front++;obj->front %= obj->arrSize; return 1; } //返回隊(duì)頭數(shù)據(jù) int myCircularQueueFront(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj))return -1;return obj->arr[obj->front]; } //返回隊(duì)尾數(shù)據(jù) int myCircularQueueRear(MyCircularQueue* obj) {if(myCircularQueueIsEmpty(obj))return -1;return obj->arr[(obj->rear-1) % obj->N]; }void myCircularQueueFree(MyCircularQueue* obj) {free(obj->arr);free(obj); }有點(diǎn)難哦
總結(jié)
以上是生活随笔為你收集整理的我幼儿园的弟看了都直呼简单的【栈和队列】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 在 Oracle Enterprise
- 下一篇: 【限时删】刘*55页ppt大瓜,比项*醒