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