C語言中的數據結構--棧和隊列(1)

前言

本屆開始我們將對數據結構中棧的內容進行講解,那么廢話不多說,我們正式進入今天的學習

棧是一種很特殊的線性表,它只能在固定的一端進行插入和刪除操作,進行數據的插入和刪除的一端叫做棧頂,另外一端叫做棧底,棧中的元素遵守后進先出的原則。

壓棧:即數據的插入操作,在棧頂進行

出棧:即數據的刪除操作,在棧頂進行

棧在內存中普遍采用順序的存儲結構,但是鏈式的存儲結構也同樣可行。因為在鏈式結構中尋找尾節點的時間較長,所以一般采取反向+頭插入的操作來完成鏈式的存儲,或者采用雙向鏈表,但是不推薦,因為多一個向前的指針,會更加麻煩。本節僅對順序存儲結構進行講解。兩種存儲方式各有優缺點(鏈式:沒有擴容的消耗,更加節省空間? ? ? ? 順序:CPU高速緩存,效率更高)

「順序表數據存儲在連續的物理空間中,當CPU訪問數據時,整個數據塊可一次性加載到高速緩存中」

棧的基本存儲結構

棧的基本存儲結構如下所示(動態):

其中的top(棧頂)也可以稱作size,a是棧的指針,capacity是棧的容量

棧的初始化和銷毀

通過在C語言時期我們對指針的學習,我們可以知道:在初始化的函數之中,如果僅僅采取傳值操作,形式參數的改變不會影響到實際參數,所以我們在初始化和銷毀棧的過程之中要采取傳地址的操作

在初始化棧的開頭,我們需要加入assert進行斷言,若為空則不采取操作

在數組開空間這一個步驟上,我們可以采取兩種措施:第一種是開幾個小的空間,后續再擴容;第二種是在初始化階段先不進行空間的開辟,后續在處理的時候在開辟空間

(這里的代碼采取的是不開辟空間,采用兩種方法都是可以的)

棧的銷毀代碼非常簡單,這里就不做過多的解釋:

??

棧的入棧和出棧

棧的數據插入不像順序表和鏈表一樣,有頭插入和尾插入。因為棧只能夠在一端進行插入和刪除,所以棧的操作只有壓棧操作

對于入棧的函數,我們需要用到的參數有:一個結構體指針pst、待插入的數據x

在開始書寫棧的入棧代碼之前,我們需要先明確一下top初始化時的指向。top的指向有兩種:一種是指向棧頂元素,另外一種是指向棧頂數據下一個數據。(模擬實現的演示代碼采取的是指向下一位)

當top指向當前元素的時候,如果棧中沒有任何元素的時候,top的值應該要賦值為-1

這兩種指向的方式都是可以的,采取不同的指向方式時初始化top對應的賦值情況也不同:

假設我們采用的是第二種方法,那么我們在進行入棧時的操作就是:

pst->a[pst->top] = x;
pst->top++;

而如果我們采取的是第一種方法,我們就需要讓top先++再放入數據x?

接下來就是判斷空間是否滿了,滿了的話就執行擴容操作。同樣的,top在初始化時的指向不同時,判滿的條件也不同。演示代碼采取的是指向下一個元素的指向方法,所以判滿的條件就是top等于capacity時:

void STPush(ST* pst, STDataType x)
{assert(pst);//擴容if(pst->top == pst->capacity){int newcapacity = 2 * pst->capacity;STDataType* tmp = realloc(pst->a, newcapacity * sizeof(STDataType));if(tmp == NULL){perror("realloc fail");}pst->a = tmp;pst->capacity = newcapacity;}pst->a[pst->top++] = x;
}

這里我故意提供了一個錯誤的代碼,看看能不能發現問題

這里的代碼存在的問題是:我們在初始化棧的時候采取了兩種方式,第一種就是先給棧開辟一點小空間,此時采用這樣的代碼是不存在問題的,因為此時的capacity有數值;但是當我們采取第二種方法時,即不給棧開辟空間時,此時的capacity大小就是0,此時我們在newcapacity中的*2操作就會無效,所以我們在對new capacity進行操作的時候先需要判斷一下capacity當前的大小:

int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;

那么假設棧一開始為空,此時的a也指向的是NULL,這樣使用realloc會不會出現問題呢?答案是不會的,我們來查看一下realloc的使用說明:

從文檔中我們可以清晰的看出,當指向的指針是空的時候,realloc函數相當于malloc函數

那么到此為止,入棧的代碼就已經全部實現完成了,接下來我們來完成一下出棧的代碼:

void STPop(ST* pst)
{assert(pst);pst->top--;
}

?到這里我們可能會有疑問,為什么STPop這短短的幾行代碼還需要封裝成一個函數,為什么不能在使用的時候直接將代碼寫出來呢?就如同下面的代碼一樣:

    ST st;STInit(&st);STPush(&st, 1);STPush(&st, 2);STPush(&st, 3);STPush(&st, 4);STPop(&st);STPop(&st);STPop(&st);STPop(&st);//st.top--;

采取這樣的方法其實也是可以的,但是這樣會影響程序的可讀性,所以這里不建議采取這樣的形式.包括訪問棧頂元素STTop中也建議采取封裝的形式,不要直接訪問:

STDataType STTop(ST* pst)
{assert(pst);assert(pst->top > 0);return pst->a[pst->top - 1];
}

我們自己直接去訪問棧頂元素可能會出現錯誤,我們可能會忘記一開始設置top指針設置初始化的指向,導致不知道到底top是棧頂元素還是top-1是棧頂元素,如果直接將它封裝成一個函數,就不會存在這樣的問題

棧的其他接口實現

接下來我們來實現一下棧的判空操作,由于實現的邏輯非常簡單,這里就不做過多地講解了:

bool STEmpty(ST* pst)
{assert(pst);if(pst->top == 0) return true;return false;
}

接下來是返回棧的元素個數的接口:

int STSize(ST* pst)
{assert(pst);return pst->top;
}

因為棧的邏輯結構非常特殊,是采取的先進先出的結構,我們就不能采用類似于順序表和鏈表那樣的訪問方式來訪問棧.要想訪問棧中的所有元素,我們就需要先用STTop來訪問棧頂元素,然后執行STPop來刪除棧頂元素,接著繼續訪問棧頂元素,一直循環到棧的元素為空為止

    while(!STEmpty(&st)){printf("%d ", STTop(&st));STPop(&st);}

但是采取這樣的訪問方式仍然存在一個問題:當我們訪問完一次棧以后,棧里面所有的元素都被清空了,不便于我們后續的操作

其實這樣的操作是合理的,我們期望的就是訪問完棧中的某一個元素以后,后續就不能訪問到這個元素了,這一點我們在后續的習題中會體現出來

練習題:有效的括號

我們來看一下這個練習題的題目:

剛看到這個題目的時候,我們最直接的思路就是數不同括號的數量,但其實這么做是沒有用的,我們來舉一個反例:"[([)]]",這組反例的數量是匹配的,但是它并不有效.所以這樣的思路顯然是不行的,在滿足數量匹配的同時我們也要滿足順序的匹配

我們先來考慮一下什么時候需要考慮到括號匹配的問題:當我們取到右括號的時候我們才會需要考慮到括號匹配的問題,我們取到左括號的時候只需讓他存入即可

結合這樣的特征我們就可以考慮用棧來解決這個問題,當讀取到左括號的時候只需要將其存入棧中即可.當讀取到右括號的時候,因為最近的左括號在棧頂,我們就要它和最近的左括號匹配一下,如果匹配成功就讓左括號也出棧

在寫代碼的時候我們需要注意,我們在確定是否匹配的時候判斷的條件是不匹配就跳出,而不是判斷匹配,因為就算是匹配了也需要進入下一次的檢查:

            if((top == '(' && *s != ')') || (top == '[' && *s != ']') || (top == '{' && *s != '}'))return false;

到這里根據思路我們就可以初步完成代碼:

bool isValid(char* s)
{ST st;STInit(&st);while(*s){if(*s == '(' || *s == '[' || *s == '{') STPush(&st, *s);else{char top = STTop(&st);STPop(&st);if((top == '(' && *s != ')') || (top == '[' && *s != ']') || (top == '{' && *s != '}'))return false;}++s;}STDestory(&st);return true;
}

當我們在提交代碼的時候可以發現在某些情況下代碼還是存在一定的問題:

?在這樣的情況之下,確實是不存在不匹配的問題,但是棧里面還有數據沒有處理完,此時就意味著棧中還有左括號沒有任何右括號供它匹配,說明左右括號的數量是不相等的,所以我們此時還需要加上判空的操作:

    //棧不為空bool ret = STEmpty(&st);STDestory(&st);return ret;

我們再運行一下代碼,發現還是存在問題,這里出現了一個斷言錯誤:

這里的意思是棧為空了以后我們還在調用棧,我們結合這里的測試用例來看一下:

這里給的用例是單獨的一個右括號,所以會直接在棧中向前查找,尋找最近的左括號與之匹配,但由于棧中只有一個元素,所以此時再去向前查找就會導致越界,所以這里發生了斷言錯誤.所以我們在判斷右括號的時候還要加入一個判空的條件:

bool isValid(char* s)
{ST st;STInit(&st);while(*s){if(*s == '(' || *s == '[' || *s == '{') STPush(&st, *s);else{if(STEmpty(&st)) return false;char top = STTop(&st);STPop(&st);if((top == '(' && *s != ')') || (top == '[' && *s != ']') || (top == '{' && *s != '}'))return false;}++s;}//棧不為空bool ret = STEmpty(&st);STDestory(&st);return ret;
}

修改完以后程序就沒有問題了,但是如果我們仔細觀察還是可以發現一些小的問題:可能會存在內存泄漏

假設棧中左括號的數量多于右括號的數量,在程序結束的時候就走不到后面的STDestory就已經直接返回了,此時就會存在內存泄漏.雖然這個并不會有太大的影響,但是我們還是要養成良好的習慣.

所以在這里,只要提前就false返回了,我們就對其執行STDestory:

            if(STEmpty(&st)){STDestory(&st);return false;}
            if((top == '(' && *s != ')') || (top == '[' && *s != ']') || (top == '{' && *s != '}')){STDestory(&st);return false;}

結尾

那么到此為止有關棧的內容就已經結束了,希望該文章可以給你帶來幫助,下一節將對隊列的內容進行梳理,謝謝你的瀏覽!!!!

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

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

相關文章

字符串是數據結構還是數據類型?

比較糾結的一個問題,以下是在網上查到后總結的,不知道對不對,歡迎討論。這是個觸及計算機科學核心概念的精妙問題!字符串既可以被視為一種數據類型,也可以被視為一種數據結構,這取決于你觀察的視角和討論的…

Cline與Cursor深度實戰指南:AI編程助手的革命性應用

引言 在AI編程工具快速發展的今天,Cline和Cursor作為兩款備受矚目的AI編程助手,正在重新定義開發者的工作方式。作為一名深度使用這兩款工具的開發者,我在過去一年的實踐中積累了豐富的經驗和獨到的見解。本文將從技術角度深入分析Cline和Cur…

根本是什么

根本是什么 根本沒有了,枝葉還在么? 沒有了內涵,外延還有么? 丟棄了根本,再嗨也是無意義,無根據空虛之樂罷了。 人之所行所言所思所想所念皆欲念、歷程感懷,情思。所謂得失過往,時空…

springboot基于Java的人力資源管理系統設計與實現

管理員:登錄,個人中心,部門管理,員工管理,培訓信息管理,員工獎勵管理,員工懲罰管理員工考核管理,調薪信息管理,員工調動管理,員工工資管理員工:注…

金字塔降低采樣

文章目錄image_scale.hppimage_scale.cppmainimage_scale.hpp #ifndef IMAGE_SCALE_HPP #define IMAGE_SCALE_HPP#include <vector> #include <cstdint> #include <utility> // for std::pair #include <algorithm> #include <string> enum cl…

Filament引擎(四)——光照渲染Froxelizer實現分析

Froxelizer主要是用于filament光照效果的實現&#xff0c;生成光照渲染時所需的必要信息&#xff0c;幫助渲染過程中明確哪些區域受哪些光源所影響&#xff0c;是Filament中保證光照效果渲染效率的核心所在。這部分的源碼&#xff0c;可以結合filament官方文檔中Light Path部分…

2025 環法對決,VELO Angel Glide 坐墊輕裝上陣

2025環法第16賽段的風禿山之巔&#xff0c;當最后一縷夕陽沉入云層&#xff0c;山風裹挾著礫石的氣息掠過賽道&#xff0c;一場足以載入史冊的激戰正酣。帕雷-潘特的肌肉在汗水里賁張&#xff0c;鏈條與齒輪的咬合聲混著粗重喘息&#xff0c;在171.5公里賽程的最后3公里陡坡上&…

Linux程序->進度條

進度條最終效果&#xff1a; 目錄 進度條最終效果&#xff1a; 一&#xff1a;兩個須知 1&#xff1a;緩沖區 ①&#xff1a;C語言自帶緩沖區 ②&#xff1a;緩沖區的刷新策略 2&#xff1a;回車和換行的區別 二&#xff1a;倒計時程序 三&#xff1a;入門板進度條的實…

Python爬蟲實戰:研究tldextract庫相關技術構建新聞網站域名分析爬蟲系統

1. 引言 網絡爬蟲作為一種自動獲取互聯網信息的技術,在數據挖掘、信息檢索、輿情分析等領域有著廣泛的應用。Python 因其豐富的庫和簡潔的語法,成為了開發爬蟲的首選語言。tldextract 是 Python 中一個強大的域名解析庫,能夠準確地從 URL 中提取頂級域名、二級域名等關鍵信…

【算法-華為機試-火星基地改造】

基地改造題目描述目標輸入輸出代碼實現題目描述 在2XXX年&#xff0c;人們發現了一塊火星地區&#xff0c;這里看起來很適合建設新家園。但問題是&#xff0c;我們不能一次性將這片地區的空氣變得適合人類居住&#xff0c;得分步驟來。 把這片火星地區想象成一個巨大的棋盤。棋…

C++入門自學Day1-- C語言的宏函數和C++內聯函數

一、函數調用開銷函數調用會涉及&#xff1a;參數壓棧&#xff08;或寄存器傳參&#xff09;跳轉到函數體返回值處理棧幀銷毀這個過程對小函數來說可能非常浪費&#xff0c;因此&#xff0c;宏函數和內聯函數的目的就是避免“函數調用的開銷”&#xff0c;通過代碼展開&#xf…

Pytorch混合精度訓練最佳實踐

混合精度訓練&#xff08;Mixed Precision Training&#xff09;是一種通過結合單精度&#xff08;FP32&#xff09;和半精度&#xff08;FP16/FP8&#xff09;計算來加速訓練、減少顯存占用的技術。它在保持模型精度的同時&#xff0c;通常能帶來 2-3 倍的訓練速度提升&#x…

Qt C++動態庫SDK在Visual Studio 2022使用(C++/C#版本)

01 將C SDK 集成到 IDE 中以下是在 Microsoft Visual Studio 平臺下 SDK 的集成。2.1 Visual Studio 平臺下 C/C環境配置及集成到 IDE 中xxx.lib 和 xxx.dll 適合在 Windows 操作系統平臺使用&#xff0c;這里以 VS2022 環境為例。2.1.1 C/C 工程環境配置與集成1、C# SDK 接口…

大語言模型 LLM 通過 Excel 知識庫 增強日志分析,根因分析能力的技術方案(2):LangChain + LlamaIndex 實現

文章大綱 1 技術原理總覽 2 詳細實現步驟(含代碼) 2.1 環境準備 2.2 Excel → LlamaIndex 節點 2.3 構建向量索引(FAISS 本地) 2.4 Google Cloud 向量檢索(可選替換 FAISS) 2.5 LangChain 問答鏈 A. RAG 模式(向量檢索 + LLM 生成) B. SQL 模式(無 RAG,直接查表) 2.…

提升ARM Cortex-M系統性能的關鍵技術:TCM技術解析與實戰指南

文章目錄引言一、TCM基礎架構與工作原理1.1 TCM的物理特性1.2 與緩存機制的對比1.3 ARM Cortex-M系列對TCM的支持二、TCM的典型應用場景2.1 實時中斷處理2.2 低功耗模式下的待機代碼2.3 高性能算法執行2.4 系統初始化階段的關鍵代碼三、實戰指南&#xff1a;在STM32H7上配置和優…

大數據之路:阿里巴巴大數據實踐——大數據領域建模綜述

為什么需要數據建模 核心痛點 數據冗余&#xff1a;不同業務重復存儲相同數據&#xff08;如用戶基礎信息&#xff09;&#xff0c;導致存儲成本激增。計算資源浪費&#xff1a;未經聚合的明細數據直接參與計算&#xff08;如全表掃描&#xff09;&#xff0c;消耗大量CPU/內存…

實戰演練1:實戰演練之命名實體識別

實戰演練1:實戰演練之命名實體識別 命名實體識別簡介 代碼 命名實體識別簡介 什么是命名實體識別任務 命名實體識別(Named Entity Recognition,簡稱NER)是指識別文本中具有特定意義的實體,主要包括人名、地名、機構名、專有名詞等。通常包括兩部分: (1)實體邊界識別。(2)確定…

數據結構基礎內容(第七篇:堆、哈夫曼樹)

# 堆 Heap 優先隊列(Priority Queue) 結構性:用 *數組* 表示的完全二叉樹; 有序性:任一結點的關鍵字是其子樹所有結點的最大值(或最小值) * “最大堆(MaxHeap)”,也稱“大頂堆”:最大值 * “最小堆(MinHeap)”,也稱“小頂堆” :最小值 主要操作有: ? MaxHeap Create( i…

CS231n-2017 Lecture7訓練神經網絡(二)筆記

本節主要是神經網絡的動態部分&#xff0c;也就是神經網絡學習參數和搜索最優超參數的過程梯度檢查&#xff1a;進行梯度檢查&#xff0c;就是簡單地把解析梯度與數值計算梯度進行比較&#xff0c;防止反向傳播的邏輯出錯&#xff0c;僅在調試過程中使用。有如下技巧 &#xff…

IntelliJ IDEA 中左上方未顯示項目根目錄問題

問題&#xff1a; 在IDEA中編寫代碼時&#xff0c;發現左上方只顯示項目的子模塊&#xff0c;未顯示根項目名稱。 如圖所示&#xff0c;未顯示子模塊的根項目&#xff1a;問題分析 頂層根目錄未被識別為項目根目錄&#xff0c;需要手動添加識別。 問題解決 進入File – Project…