日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

数据结构杂谈(五)——栈

發布時間:2023/12/9 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据结构杂谈(五)——栈 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文的所有代碼均由C++編寫

引用及參考資料:

  • 王道數據結構
  • 大話數據結構
  • 超硬核十萬字!全網最全 數據結構 代碼,隨便秒殺老師/面試官,我說的_hebtu666-CSDN博客

5 棧

5.1 引入

在前面學習線性表的時候,我們給出了線性表的定義:

線性表是具有相同數據類型的n個數據元素的有限序列。其中n為表長,當n=0的時候線性表是一個空表。若用L命名線性表,即:L=(a1,a2,a3….,an)L = (a_1,a_2,a_3….,a_n)L=(a1?,a2?,a3?.,an?)。

實際上,棧(Stack)是一類特殊的線性表,它只允許在一端進行插入或刪除操作。棧在一些其他科目中也被叫做堆棧。這樣從數學角度來看仿佛很抽象,能不能有一個形象地比喻呢?

這里我們引入的是《大話數據結構》中的例子。

假設現在有一把手槍,從結構上看,一顆子彈打出去后,另一顆子彈就會上膛,但是如果上膛的是一顆壞掉的子彈,那手槍能夠換下一顆嗎?如果在不自己手動拆出彈夾更換的情況下是做不到的。

這里引入的第二個例子是王道數據結構視頻的例子。

當在不抱起所有盤子且不移動其他盤子的情況下,如果我們要拿起一個盤子或者放一個盤子,只能在最上面下手吧。同樣的,如果我們要吃一支烤串,在不咬爛肉的情況下想要吃到第二個肉,必須先吃第一個再吃第二個吧。

由上面的例子可以總結出,棧實際上是一個先進后出的結構,且只能在一端進行操作。

5.2 重要術語說明

如果棧內沒有任何一個元素,我們稱其為空棧。

我們把允許插入和刪除的那一端叫做棧頂,另一端叫做棧底,不含任何數據元素的棧叫做空棧。由此我們可以發現,第一個進去的元素在最底下,所以,棧又稱為后進先出(Last In First Out)的線性表。我們也把棧的結構叫做LIFO結構。

棧的插入操作我們也叫進棧,也稱壓?;蛉霔?。類似子彈入彈夾。

棧的刪除操作我們也叫出棧,也稱彈棧。如同彈夾中子彈出夾。

5.3 進棧出棧變化形式

有些人受到固定思維,意味棧中最先進棧的元素一定是最后出棧的,其實不一定。比如123依次入棧,那會有很多種結果。

  • 第一種:1、2 、3進,再1 、2 、3出。
  • 第二種:1進1出2進2出3進3出。
  • 第三種:1進2進2出1出3進3出。
  • 第四種:1進1出2進3進3出2出。
  • 第五種:1進2進2出3進3出1出。

從這里我們可以看出,三個元素就有5種變化,那么當元素數目變多時,變化就有可能更多。在考研或者其他面試中,最喜歡的就是考查棧的合法出棧順序,所以這里一定要搞清楚。

5.4 棧的抽象數據類型

前面說過,棧是一種特殊的線性表,所以線性表中有的操作棧都有。需要注意的是,前面我們在談論基本操作的名稱寫法時說過名稱一定要能讓人看懂。在嚴蔚敏一版的教材中,我們采用如下抽象數據結構。

ADT 棧(stack) Data同線性表。元素具有相同的類型,相鄰元素具有前驅和后繼關系。 OperationInitStack(*S):初始化操作,建立一個空棧s。DestroyStack(*S):若棧存在,則銷毀它。ClearStack(*S):將棧清空。StackEmpty(S):若棧為空,返回true,否咋返回false。GetTop(S,*e):若棧存在且非空,用e返回s的棧頂元素。Push(*S,e):若棧S存在,插入新元素e則棧S中并成為棧頂元素。Pop(*S,e):刪除棧S中棧頂元素,并用e返回其值。StackLength(S):返回棧S的元素個數。 endADT

5.5 順序棧

5.5.1 順序棧的定義

#define MaxSize 10 //定義棧中元素的最大個數 typedef struct{ElemType data[MaxSize]; //靜態數組存放棧中元素int top; //棧頂指針 }SqStack;

在棧的定義中,我們用了top來指明棧頂元素在數組中的位置,top就如同老師用的游標卡尺中的游標一般,其可以來回移動,但是游標不能超過尺,即棧頂指針不能超過定義的數組容量。一般來說,如果空棧,意味著棧中沒有元素,此時我們的top = -1。

在這里要科普一些關于棧頂指針和棧的一些知識。

棧頂指針也叫堆棧指針,一般堆棧的棧底不能動,所以數據入棧前要先修改堆棧指針,使它指向新的空余空間然后再把數據存進去,出棧的時候相反。

堆棧指針,隨時跟蹤棧頂地址,按"先進后出"的原則存取數據。

計算機中的堆棧主要用來保存臨時數據,局部變量和中斷/調用子程序程序的返回地址。

ARM處理器中通常將寄存器R13作為堆棧指針(SP)。ARM處理器針對不同的模式,共有 6 個堆棧指針(SP),其中用戶模式和系統模式共用一個SP,每種異常模式都有各自專用的R13寄存器(SP)。它們通常指向各模式所對應的專用堆棧,也就是ARM處理器允許用戶程序有六個不同的堆棧空間。這些堆棧指針分別為R13、R13_svc、R13_abt、R13_und、R13_irq、R13_fiq。

5.5.2 初始化以及判空

如果要對棧進行初始化,根據上面說過top=-1為空棧的情況,我們可以寫出如下所示的初始化代碼:

void InitStack(SqStack &S) {S.top = -1; }

根據初始化的特點,如果我們想要判斷棧是否為空,只需:

bool StackEmpty(SqStack S) {if(S.top==-1)return true;elsereturn false; }

5.5.3 進棧操作

對于進棧操作push,我們可以:

bool Push(SqStack &S,ElemType x) {if(S.top == MaxSize-1) //棧滿,報錯return false;S.data[++S.top] = x; //移動堆棧指針,并且給堆棧指針指向的棧頂元素賦值return true; }

5.5.4 出棧操作

對于出棧操作pop,我們可以:

bool Pop(SqStack &S,ElemType &x) {if(S.top==-1)return false;x = S.data[S.top--]; //先返回元素給x,再移動堆棧指針return true; }

這里要注意的是,我們并沒有把出棧元素的數據清空,所以雖然出棧,但是只是邏輯上被刪除了,實際上數據還殘留在內存中。

5.5.5 讀棧操作

對于讀棧,一般我們都是讀棧頂元素。如下所示:

bool GetTop(SqStack S,ElemType &x) {if(S.top == -1)return false;x = S.data[S.top];return true; }

5.5.6 關于棧頂指針的指向問題

有時候,棧頂指針會在初始化的時候不是指向-1而是指向0,而當棧滿的時候不是指向棧頂元素而是指向棧頂元素的下一位,此時上面的判空、初始化、讀棧、進棧操作和出棧操作中代碼都會有些小小的不一樣,需要大家注意,這在考試中可能會出現。

5.5.7 共享棧

顧名思義,共享棧就是兩個棧共用一個??臻g。

在順序棧的設計過程中,我們可以發現其用的是靜態數組來分配的??臻g,這就導致可能??臻g內存不足,如果要使用動態數組的方式去分配內存,這么做很麻煩。

但是兩個棧就不一樣了,我們可以把兩個棧用一個很大的數組放著,其中這個數組是兩個棧的共享??臻g。如果你用的多,我用的少,那我就多給你一點空間,這樣很容易協調空間的分配。

兩個棧的空間放置如下,我們可以看成是兩個棧頂面對面放置。

兩個棧的棧指針移動時如下所示:

共享棧代碼如下:

//定義共享棧 #define MaxSize 10 typedef struct {ElemType data[MaxSize];int top0;int top1; }shstack;//初始化共享棧 void InitStack(ShStack &S) {S.top0 = -1;S.top1 = MaxSize; }

如果棧滿了,其條件是兩指針對應的棧誰再進棧一次都會重合。

top0 +1 == top1

5.6 鏈棧

5.6.1 概述

我們把棧的鏈式存儲結構叫做鏈棧。

相對于鏈棧來說,我們需要考慮第一個問題:

我們要把棧頂放在鏈表的頭部還是尾部。

對于鏈表來說,鏈表不管是帶頭結點還是不帶頭結點,都帶有一個頭指針,如果棧頂不處于鏈表頭部的話,那么我們就要再聲明一個指針作為棧頂指針,這顯然很麻煩。既然這樣,我們不如把棧頂指針和頭指針合二為一即可。需要注意的是,合二為一的前提是棧頂實際上就是第一個結點,如果你帶頭結點,那么第一個結點就不能放元素了,這不太符合棧的定義了,所以在鏈棧中,我們一般不會再用到學習單鏈表所用到的頭結點了。

對于鏈棧,就不存在順序棧那樣空間不足的問題了,除非是內存不足。

5.6.2 鏈棧的定義

鏈棧中的定義在不同的書上有不同的方式,如果你想記錄棧的長度,那么我推薦你使用下面的方式:

//結點定義 typedef struct StackNode {int data;StackNode* next; }StackNode,*LinkStackptr;//鏈棧定義 typedef struct LinkStack {LinkStackptr top;//指針int Length; }LinkStack

5.6.3 鏈棧的初始化

對于鏈棧初始化,就是把傳入的鏈棧指向空,并且設置棧長為0。

//初始化 bool LinkStackInit(LinkStack &S) {S.top = NULL;S.Length = 0;return true; }

5.6.4 鏈棧的進棧

對于鏈棧的進棧,考慮帶頭結點的情況,我們可以發現實際上就是指定只能在頭結點后插,這樣的話,根據之前在單鏈表學過的后插操作,可以自行寫出代碼,這里不再贅述。

但是如果是不帶頭結點,那么我們可以如下所示:

//進棧 bool push(LinkStack &S, int e) { //生成新節點并且把添加值加入StackNode *newnode = new StackNode;newnode->data = e;//開始添加結點,變換指針順序newnode->next = S.top;S.top = newnode;//添加完成,計入表長S.Length++;return true; }

5.6.5 鏈表的出棧

對于不包含頭結點鏈表的出棧,無非就是對棧頂指針后的結點刪除,需要注意的是當棧中結點剩余0和1的時候,我們需要設置額外情況。

//彈棧 bool pop(LinkStack& S, int& e) {if (S.Length == 0) {return false;}//生成指針用于記錄刪除結點所在地址StackNode *p = new StackNode;//返回刪除結點中的值e = S.top->data;//將指針移向棧頂p = S.top;//棧頂位置改變S.top = S.top->next;//釋放原棧頂delete(p);//表長改變S.Length--;return false; }

5.6.6 輸出鏈棧

輸出鏈棧也很容易,只需指定一根指針從棧頂掃向棧底即可。掃的時候依次輸出每個結點中的數據。

bool CoutStack(LinkStack S) {StackNode* p = S.top;cout << "由棧頂到棧底:" ;while (p) {cout << p->data << endl;p = p->next;}cout << endl;return true; }

5.6.7 輸出棧頂元素

//輸出棧頂元素 bool GetTop(LinkStack& S, int e) {if (S.Length == 0)return false;e = S.top->data;return true; }

5.7 鏈棧和順序棧的對比

對比順序棧和鏈棧,它們的時間復雜度一樣,均為O(1)。對于空間性能,順序棧的空間是死的,是固定的;而對于鏈棧,雖然空間沒有限制,但是也可能面臨內存不足的問題。

總結

以上是生活随笔為你收集整理的数据结构杂谈(五)——栈的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。