棧與隊列及其基礎應用

一.棧

1.棧的定義

棧是一種特殊的線性表,其只允許在固定的一端進行插入和刪除元素操作。進行數據插入和刪除操作的一端稱為棧頂,另一端稱為棧底。棧中的數據元素遵守后進先出LIFO(Last In First Out)的原則。其結構可以參考羽毛球筒

在現實中我們通常用棧解決對稱性問題,如經典的括號匹配問題。

?2.棧的實現

由上結構圖可看出,我們用一個指針bottom指向棧底元素,一個指針top指向棧頂元素的下一個位置。實現棧可以用數組。

typedef int STDataType;
typedef struct Stack {STDataType *a;//一個動態創建的數組int top;//棧頂數據的下個位置,也是當前棧內元素個數int capacity;//當前棧的最大數據容量
} ST;

為了實現棧的增刪改查等功能,我們可以定義若干接口

//初始化
void StackInit(ST *pst);
//銷毀
void StackDestroy(ST *pst);
//入棧
void StackPush(ST *pst, STDataType x);
//出棧
void StackPop(ST *pst);
//獲取棧頂元素
STDataType StackTop(ST* pst);
//獲取棧是否為空
bool StackEmpty(ST* pst);
//獲取棧中元素的個數
int StackSize(ST *pst);

現在對這些接口實現。

#include "Stack.h"
//注意top的指向會影響到他的初始值
//初始化
void StackInit(ST* pst) {assert(pst != NULL);pst->a= NULL;//top指向棧頂元素的下一個空間,即表示當前棧內沒有元素pst->top = 0;pst->capacity = 0;
}
//銷毀
void StackDestroy(ST* pst) {assert(pst != NULL);free(pst->a);pst->top=pst->capacity=0;
}
//入棧
//先判斷當前空間是否足夠,不夠就動態開辟
//將入棧元素的值x賦予top,更新top位置
void StackPush(ST* pst, STDataType x) {assert(pst != NULL);if (pst->top == pst->capacity) {int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));if (tmp == NULL) {perror("realloc fail");return;}}pst->a[pst->top] = x;pst->top++;
}//出棧
//從棧頂出元素,直接令top--
void StackPop(ST* pst){assert(pst != NULL);pst->top--;
}
//獲取棧頂元素
//top為棧頂元素下個位置,返回top-1處值即可
STDataType StackTop(ST* pst) {assert(pst);assert(pst->top != 0);return pst->a[pst->top - 1];
}
//獲取棧是否為空
bool StackEmpty(ST* pst) {assert(pst != NULL);return pst->top == 0;
}
//獲取棧中元素的個數
int StackSize(ST* pst) {assert(pst != NULL);return pst->top;
}

二.隊列

1.隊列的定義

隊列也是一種特殊的線性表,其只允許在一端進行插入數據操作,在另一端進行刪除數據操作的特殊線性表,隊列具有先進先出FIFO(First In First Out) 入隊列:進行插入操作的一端稱為隊尾 出隊列:進行刪除操作的一端稱為隊頭

在實際應用中我們常用隊列解決公平性問題。如排隊取號,以及并發編程中鎖的競爭分配問題。?

隊列由于其隊尾插入,隊頭刪除的特性,若使用數組實現會比較困難:無論是插入還是刪除,都需要考慮空間大小和數據移動的問題,因此在這里我僅展示用鏈表實現。因此隊列的功能我們僅對鏈表進行尾插頭刪即可

聲明隊列節點結構

typedef int QDataType;typedef struct QueueNode
{struct QueueNode* next;QDataType val;
}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);int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);

?實現

#include"Queue.h"void QueueInit(Queue* pq)
{assert(pq);pq->phead = NULL;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;
}// 隊尾插入
//申請節點,插入節點后更新size
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");return;}newnode->next = NULL;newnode->val = x;if (pq->ptail == NULL)//若此時隊列沒有節點{pq->phead = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;
}// 隊頭刪除
//存頭節點下個位置的節點
//釋放頭節點位置節點
//更新頭節點和size
void QueuePop(Queue* pq)
{assert(pq);assert(pq->size != 0);/*QNode* next = pq->phead->next;free(pq->phead);pq->phead = next;if (pq->phead == NULL)pq->ptail = NULL;*/// 一個節點if (pq->phead->next == NULL){free(pq->phead);pq->phead = pq->ptail = NULL;}else // 多個節點{QNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}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;
}//求當前隊列的數據個數
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}//當前隊列數據個數0則為空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}

?

三.棧和隊列的簡單應用

1.用雙棧實現隊列

該問題的核心是:如何用后進先出的結構,實現一個先進先出的結構。這個核心會同時影響到插入和刪除的設計邏輯。

不妨先大致分析一下出隊的邏輯

?

我們向Stack1中插入數據[1,2,3]。若要進行出隊,應該出最先入隊的1

然而在Stack1中,1顯然位于棧底,想要將其最先出隊,不難觀察出:

我們只要將Stack1中數據以此出棧到Stack2中即可。

?此時將Stack2的棧頂出棧,就完成了出隊的操作。

typedef struct{int*a;int top;int capacity;
}Stack;void stackInit(Stack*pst){assert(pst!=NULL);pst->top=pst->capacity=0;pst->a=NULL;
}void stackPush(Stack*pst,int x){assert(pst!=NULL);if(pst->top==pst->capacity){int newcapacity=pst->capacity==0?4:pst->capacity*2;int *tmp=(int*)realloc(pst->a,newcapacity*sizeof(int));pst->a=tmp;pst->capacity=newcapacity;}pst->a[pst->top]=x;pst->top++;
}void stackPop(Stack*pst){assert(pst!=NULL);pst->top--;}int stackTop(Stack*pst){return pst->a[pst->top-1];
}bool stackEmpty(Stack*pst){return pst->top==0;
}void stackFree(Stack*pst){free(pst->a);pst->a=NULL;
}
//至此都是棧的結構和接口,因為C語言庫沒有內置棧。。//可以看到這里的隊列由雙棧實現
typedef struct {Stack stack1;Stack stack2;
} MyQueue;//建隊,即將隊中的兩個棧初始化即可
MyQueue* myQueueCreate() {MyQueue*obj=(MyQueue*)malloc(sizeof(MyQueue));stackInit(&(obj->stack1));stackInit(&(obj->stack2));return obj;
}int myQueuePop(MyQueue* obj){// 當stack2為空時,轉移所有元素if(stackEmpty(&obj->stack2)){while(!stackEmpty(&obj->stack1)){int tmp = stackTop(&obj->stack1);stackPush(&obj->stack2, tmp);stackPop(&obj->stack1);}}int x = stackTop(&obj->stack2);stackPop(&obj->stack2);return x;
}
void myQueuePush(MyQueue* obj, int x){stackPush(&obj->stack1,x);
}
int myQueuePeek(MyQueue* obj) {if (stackEmpty(&obj->stack2)) {// 循環轉移所有元素while (!stackEmpty(&obj->stack1)) {int tmp = stackTop(&obj->stack1);stackPush(&obj->stack2, tmp);stackPop(&obj->stack1);}}return stackTop(&obj->stack2); // 正確返回隊首元素
}bool myQueueEmpty(MyQueue* obj) {return stackEmpty(&obj->stack1)&&stackEmpty(&obj->stack2);
}void myQueueFree(MyQueue* obj) {stackFree(&obj->stack1);stackFree(&obj->stack2);
}

這段代碼就是上面提到要實現出隊時,將棧滿的元素一個個入棧到棧空的棧中

a.取stack1棧頂元素值

b.將stack1棧頂元素入空棧stack2

c.將當前stack1棧頂元素出棧,更新新的棧頂元素

由此直至stack1變為空棧,stack2棧滿,stack1的棧底為stack2的棧頂。

 while(!stackEmpty(&obj->stack1)){int tmp = stackTop(&obj->stack1);stackPush(&obj->stack2, tmp);stackPop(&obj->stack1);}

?

然而上述代碼存在一定的問題。?

int myQueuePop(MyQueue* obj){// 當stack2為空時,轉移所有元素if(stackEmpty(&obj->stack2)){while(!stackEmpty(&obj->stack1)){int tmp = stackTop(&obj->stack1);stackPush(&obj->stack2, tmp);stackPop(&obj->stack1);}}int x = stackTop(&obj->stack2);stackPop(&obj->stack2);return x;
}
void myQueuePush(MyQueue* obj, int x){stackPush(&obj->stack1,x);
}
int myQueuePeek(MyQueue* obj) {if (stackEmpty(&obj->stack2)) {// 循環轉移所有元素while (!stackEmpty(&obj->stack1)) {int tmp = stackTop(&obj->stack1);stackPush(&obj->stack2, tmp);stackPop(&obj->stack1);}}return stackTop(&obj->stack2); // 正確返回隊首元素
}

在進行轉移元素的操作時,我們默認向空棧中轉移。在初次創建棧時空棧即為Stack2(因為上述接口中首先向Stack1插入元素),但若測試用例中對同一個myQueue進行出隊時,此時的空棧又變成了Stack1。這里建議使用if-else判斷空棧或者用假設法判定空棧,否則可能無法通過某些測試用例。

2.用兩個隊列實現棧

?

這題的思想其實與第一個完全一致。在這里直接放出代碼,并解決以上提到的問題?

#define LEN 20
typedef struct queue {int *data;int head;int rear;int size;
} Queue;typedef struct {Queue *queue1, *queue2;
} MyStack;Queue *initQueue(int k) {Queue *obj = (Queue *)malloc(sizeof(Queue));obj->data = (int *)malloc(k * sizeof(int));obj->head = -1;obj->rear = -1;obj->size = k;return obj;
}void enQueue(Queue *obj, int e) {if (obj->head == -1) {obj->head = 0;}obj->rear = (obj->rear + 1) % obj->size;obj->data[obj->rear] = e;
}int deQueue(Queue *obj) {int a = obj->data[obj->head];if (obj->head == obj->rear) {obj->rear = -1;obj->head = -1;return a;}obj->head = (obj->head + 1) % obj->size;return a;
}int isEmpty(Queue *obj) {return obj->head == -1;
}MyStack *myStackCreate() {MyStack *obj = (MyStack *)malloc(sizeof(MyStack));obj->queue1 = initQueue(LEN);obj->queue2 = initQueue(LEN);return obj;
}void myStackPush(MyStack *obj, int x) {if (isEmpty(obj->queue1)) {enQueue(obj->queue2, x);} else {enQueue(obj->queue1, x);}
}int myStackPop(MyStack *obj) {if (isEmpty(obj->queue1)) {while (obj->queue2->head != obj->queue2->rear) {enQueue(obj->queue1, deQueue(obj->queue2));}return deQueue(obj->queue2);}while (obj->queue1->head != obj->queue1->rear) {enQueue(obj->queue2, deQueue(obj->queue1));}return deQueue(obj->queue1);
}int myStackTop(MyStack *obj) {if (isEmpty(obj->queue1)) {return obj->queue2->data[obj->queue2->rear];}return obj->queue1->data[obj->queue1->rear];
}bool myStackEmpty(MyStack *obj) {if (obj->queue1->head == -1 && obj->queue2->head == -1) {return true;}return false;
}void myStackFree(MyStack *obj) {free(obj->queue1->data);obj->queue1->data = NULL;free(obj->queue1);obj->queue1 = NULL;free(obj->queue2->data);obj->queue2->data = NULL;free(obj->queue2);obj->queue2 = NULL;free(obj);obj = NULL;
}

3.設計循環隊列

循環隊列我們可以理解為一個座位有限的圖書館,當座位滿時就不能再坐人,但是一旦有人離開,就可以再來一個人坐在那個空位。

我們可以直接用筆畫一畫這個結構,在這里rear為隊尾元素的下一個位置。假設這是一個可以存放五個數據的循環隊列。先對這個隊列進行入隊。

?

接著開始出隊?(注意隊列從隊尾入隊,隊頭出隊)

其實從剛剛插入的圖我們就能發現一個比較大的問題,當棧空和棧滿時的front指針和rear指針指向的都是同一個位置。。。他有一個專門的稱為,叫假溢出

為了解決假溢出,可以對要存儲k個數據的循環隊列,初始化k+1個空間。

若此時的k為4,開辟k+1個元素。此時的隊空隊滿如下:

直接來做這道題?

typedef struct {int front;//指向隊頭int rear;//指向隊尾的下一個元素int capacity;//空間容量int *elements;//用數組實現循環隊列
} MyCircularQueue;//建立存k個數據的循環隊列,這里開辟的空間為k+1
MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue *obj = (MyCircularQueue *)malloc(sizeof(MyCircularQueue));obj->capacity = k + 1;obj->rear = obj->front = 0;obj->elements = (int *)malloc(sizeof(int) * obj->capacity);return obj;
}//入隊,成功return true
//若此時隊滿,直接return false
//這時判斷隊滿的條件是rear的下一個元素為隊頭
//注意這里的取模操作,是為了消除rear+1大于capacity,并且也是讓rear和front循環起來的基本操作
//更新rear位置
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if ((obj->rear + 1) % obj->capacity == obj->front) {return false;}obj->elements[obj->rear] = value;obj->rear = (obj->rear + 1) % obj->capacity;return true;
}//出隊,成功return true
//若此時隊空,直接return false
//更新front位置
bool myCircularQueueDeQueue(MyCircularQueue* obj) {if (obj->rear == obj->front) {return false;}obj->front = (obj->front + 1) % obj->capacity;return true;
}//求隊頭元素
//若隊空,return
//返回front處值
int myCircularQueueFront(MyCircularQueue* obj) {if (obj->rear == obj->front) {return -1;}return obj->elements[obj->front];
}//求隊尾元素
//若隊空,return
//rear為隊尾元素的下一個,因此返回rear-1處位置即可
//但此時rear恰好為0,為讓其正確指向隊尾元素,-1加上capacity再模capacity
int myCircularQueueRear(MyCircularQueue* obj) {if (obj->rear == obj->front) {return -1;}return obj->elements[(obj->rear - 1 + obj->capacity) % obj->capacity];
}bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->rear == obj->front;
}bool myCircularQueueIsFull(MyCircularQueue* obj) {return (obj->rear + 1) % obj->capacity == obj->front;
}void myCircularQueueFree(MyCircularQueue* obj) {free(obj->elements);free(obj);
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/77301.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/77301.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/77301.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

openEuler-22.03-LTS-SP3 編譯安裝 Greenplum-db 6.20.0

openEuler-22.03-LTS-SP3 編譯安裝 Greenplum-db 6.20.0 1、配置 yum 華為源2、安裝依賴3、源碼安裝 openssl 1.0.1u3.1、openssl 1.1.1 降級到 openssl 1.0.1 4、源碼安裝 python 2.75、使用 pip3 安裝 Python 相關依賴6、編譯安裝 Greenplum-db 6.20.06.1、修改配置6.2、基于…

機器學習02——概要

一、簡介 機器學習是一門在沒有明確編程的情況下讓計算機學習的科學。 監督學習是有目標的,輸入數據對應明確的輸出;無監督學習則是“探索”型的,模型的目標是從數據中發現潛在的模式或結構,而不需要預先知道標簽。 二、機器學…

swift-08-屬性、匯編分析inout本質

一、Swift中跟實例相關的屬性可以分為2大類 1.1 存儲屬性( Stored Property) 類似于成員變量這個概念 存儲在實例的內存中 結構體、類可以定義存儲屬性 枚舉不可以定義存儲屬性(因為枚舉只存儲關聯值和case) 1.2 計算屬性&am…

【HarmonyOS Next之旅】DevEco Studio使用指南(十二)

目錄 1 -> Code Linter代碼檢查 2 -> 配置代碼檢查規則 3 -> 查看/處理代碼檢查結果 1 -> Code Linter代碼檢查 Code Linter針對ArkTS/TS代碼進行最佳實踐/編程規范方面的檢查。 可根據掃描結果中告警提示手工修復代碼缺陷,或者執行一鍵式自動修復…

前端vue項目打包成桌面端exe應用

主要 使用 Electron將 vue項目打包為 exe 1.首先下載Electron git clone https://github.com/electron/electron-quick-start cd electron-quick-start npm install安裝完依賴之后 npm start運行成功 注意:如果你的項目使用了VueRouter,那么切記&…

基于springcloud的“微服務架構的巡游出租管理平臺”的設計與實現(源碼+數據庫+文檔+PPT)

基于springcloud的“微服務架構的巡游出租管理平臺”的設計與實現(源碼數據庫文檔PPT) 開發語言:Java 數據庫:MySQL 技術:springcloud 工具:IDEA/Ecilpse、Navicat、Maven 系統展示 系統總體結構圖 E-R實體關系圖…

新一代達夢官方管理工具SQLark:可視化建表操作指南

在數據庫管理工作中,新建表是一項基礎且頻繁的操作。SQLark 的可視化建表功能為我們提供了一種高效、便捷且絲滑流暢的建表新體驗。一起來了解下吧。 SQLark 官方下載鏈接:www.sqlark.com 新建表作為常見的功能,相比其他管理工具,…

Scala相關知識學習總結6

1、集合計算高級函數說明 - 過濾:遍歷集合,提取滿足特定條件的元素組成新集合。 - 轉化/映射(map):將集合里的每個元素應用到指定函數進行轉換。 - 扁平化:文檔未詳細闡述其具體含義和操作。 - 扁平化映射&…

pandas.DataFrame.dtypes--查看和驗證 DataFrame 列的數據類型!

查看每列的數據類型,方便分析是否需要數據類型轉換 property DataFrame.dtypes[source] Return the dtypes in the DataFrame. This returns a Series with the data type of each column. The result’s index is the original DataFrame’s columns. Columns with…

計算機中的單位

在計算機科學中,單位用于衡量數據存儲、內存、數據傳輸速率等。以下是一些常見的計算機單位及其含義: ### **1. 數據存儲單位** 數據存儲單位用于衡量計算機存儲設備(如硬盤、內存、閃存等)的容量。 | 單位 | 符號 | 含義…

Spring Boot 自定義配置類(包含字符串、數字、布爾、小數、集合、映射、嵌套對象)實現步驟及示例

Spring Boot 自定義配置類實現步驟及示例 步驟說明 創建配置類:定義一個 POJO 類,使用 ConfigurationProperties 注解指定配置前綴。啟用配置綁定:在啟動類或配置類上添加 EnableConfigurationProperties 注解。配置文件寫法:在 …

Linux: 線程控制

目錄 一 前言 二 線程控制 1. POSIX線程庫(原生線程庫) 2. 創建線程 2.1 pthread_create 2.2pthread_self()獲取線程id 3.線程終止 3.1.return 方式 3.2 pthread_exit 4 線程等待 三 理解線程tid 一 前言 在上一篇文章中我們已經學習了線程的概念,線程的創…

避開養生誤區,擁抱健康生活

在追求健康的道路上,我們常常會陷入一些養生誤區,不僅無法達到預期效果,還可能損害身體健康。只有撥云見日,認清這些誤區,采取正確的養生方式,才能真正擁抱健康生活。? 很多人認為,保健品吃得…

<數據集>蘋果識別數據集<目標檢測>

數據集下載鏈接https://download.csdn.net/download/qq_53332949/90585216數據集格式:VOCYOLO格式 圖片數量:535張 標注數量(xml文件個數):535 標注數量(txt文件個數):535 標注類別數:2 標注類別名稱:…

【補題】P10424 [藍橋杯 2024 省 B] 好數(數位dp)

題意: 一個整數如果按從低位到高位的順序,奇數位(個位、百位、萬位……)上的數字是奇數,偶數位(十位、千位、十萬位……)上的數字是偶數,我們就稱之為“好數”。 給定一個正整數 N…

分布式存儲怎樣提高服務器數據的安全性?

分布式存儲是一種計算機數據存儲架構,主要是將數據信息分布存儲在多臺計算機或者是服務器上,以此來實現高可靠性、可擴展性和高性能,讓每個計算機或服務器可以通過網絡連接相互通信和協作。 分布式存儲系統會定期對重要的數據信息進行完整性檢…

數字IC后端培訓教程系列之PR Innovus工具寫出Calibre LVS用的Netlist詳細步驟

在數字IC后端設計實現chipfinish階段需要寫出很多數據,比如netlist,def,gds,lib和lef等文件。 今天給大家分享PR工具Innovus寫出Calibre物理驗證LVS要用的netlist的詳細步驟。 手把手教你debug解決物理驗證Calibre LVS錯誤 1&a…

TrueNAS scale(23.10) Restful API接口調用

背景 本文主要講解開源的NAS系統--TrueNAS的二次開發。 TrueNAS scale安裝 網上能找到很多類似的文章,本文就不介紹了,這里給一個視頻博主的傳送門: 司波圖 TrueNAS scale Resful API 接口 官網的 Resful API地址:TrueNAS REST…

卡爾曼濾波器淺聊

0 前言: 卡爾曼濾波屬于算法領域的,所以一些基本的數學概念是必須了解的 涉及到的數學基本概念 概念數學符號含義數學期望(Expected Value)E描述隨機變量平均取值的最核心概念概率(Probability)P(X= x i x_i xi?)隨機變量 X 取特定值 x i x_i xi?的概率方差(Varian…

1ll C++

在C++中,1ll 表示 long long 類型的整數常量1。這里的 ll 是 long long 的縮寫。這種寫法主要用于以下幾個方面: 1. 為什么需要 1ll? 在您的代碼中,1ll 主要用于 防止整數溢出 和 確保正確的類型轉換: cpp 復制 p = 1ll * p * i % MOD; f[i + 1] = 1ll * i * (i + 1) …