大话数据结构—栈与队列
棧
一、棧的定義
棧是(stack)是限定盡在表尾進(jìn)行插入和刪除操作的線性表。
棧又稱為后進(jìn)先出(Last In First Out)的線性表,簡(jiǎn)稱LIFO結(jié)構(gòu)。
二、進(jìn)棧出棧變化形式
注意: 并不是最新進(jìn)棧的元素只能最后處棧。如,我們現(xiàn)在有三個(gè)元素一次進(jìn)棧,次序會(huì)有以下5種:
1. 1、2、2進(jìn),再3、2、1出,出棧次序?yàn)?21;
2. 1進(jìn),1出,2進(jìn),2出,3進(jìn),3出,出棧次序?yàn)?23;
3. 1進(jìn),2進(jìn),2出,1出,3進(jìn),3出,出棧次序?yàn)?13;
4. 1進(jìn),1出,2進(jìn),3進(jìn),3出,2出,出棧次序?yàn)?32;
5. 1進(jìn),2進(jìn),2出,3進(jìn),3出,1出,出棧次序?yàn)?31。
三、棧的順序存儲(chǔ)結(jié)構(gòu)及實(shí)現(xiàn)
(一)棧的順序存儲(chǔ)結(jié)構(gòu)
棧是線性表的特例,棧的順序存儲(chǔ)結(jié)構(gòu)也是線性表存儲(chǔ)結(jié)構(gòu)的簡(jiǎn)化。線性表是用數(shù)組實(shí)現(xiàn)的,用下標(biāo)為0的那一端作為棧底使棧變化最小。
我們定義一個(gè)top變量來(lái)指示棧頂元素在數(shù)組中的位置。若存儲(chǔ)棧的 長(zhǎng)度為StackSize,則棧頂位置top必須小于StackSize。當(dāng)棧存在一個(gè)元素時(shí),top=0.因此,通常把空棧的判定條件定位top=-1.
進(jìn)棧:push;出棧:pop。(就像子彈的壓和彈)。
沒(méi)有涉及循環(huán),兩者的時(shí)間復(fù)雜度均為1.
兩棧共享空間:兩個(gè)類型相同的棧,則可以共享存儲(chǔ)空間。讓一個(gè)棧的棧底為數(shù)組的始端,即下標(biāo)為0處,另一個(gè)棧為數(shù)組的末端,即下標(biāo)為數(shù)組長(zhǎng)度n-1處。這樣,兩個(gè)棧如果增加元素,就是兩端點(diǎn)向中間延伸。兩個(gè)棧見(jiàn)面之時(shí),也就是兩個(gè)棧指針相差1,即top1+1==top2為棧滿。
(二)棧的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)
棧的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),簡(jiǎn)稱為鏈棧。
鏈棧的棧頂和單鏈表的頭指針重合,不需要頭結(jié)點(diǎn)。
對(duì)于鏈棧來(lái)說(shuō),不存在棧滿的情況,除非內(nèi)存已經(jīng)沒(méi)有可以使用的空間。如果真的發(fā)生,就是操作系統(tǒng)已經(jīng)面臨死機(jī)崩潰的情況,而不是這個(gè)鏈棧是否真的溢出。
對(duì)于空棧來(lái)說(shuō),鏈表原定義是頭指針指向空,那么鏈棧的空其實(shí)就是top=NULL的時(shí)候。
對(duì)比順序棧和鏈棧,它們?cè)跁r(shí)間復(fù)雜度上是一樣的,均為O(1)。對(duì)于空間性能,順序棧要事先確定一個(gè)固定的長(zhǎng)度,可能會(huì)存在內(nèi)存空間浪費(fèi)的問(wèn)題,但它的優(yōu)勢(shì)是存取時(shí)定位很方便,而鏈棧則要求每個(gè)元素都有指針域,這同時(shí)也增加了一定的內(nèi)存開(kāi)銷,但對(duì)于棧的長(zhǎng)度無(wú)限制。所以它們的區(qū)別和線性表中討論的一樣,如果棧的使用過(guò)程中元素變化不可預(yù)料,有時(shí)候很小,有時(shí)候很大,那么最好是用鏈棧,如果它們的變化在可控范圍內(nèi),建議使用順序棧會(huì)更好一些。
四、棧的作用
有的人可能會(huì)問(wèn),用數(shù)組或鏈表直接實(shí)現(xiàn)功能不就行了嗎?為什么還要引入棧這個(gè)數(shù)據(jù)結(jié)構(gòu)呢?
其實(shí)這和我們明明有兩只腳可以走路,干嘛還要乘汽車、火車、飛機(jī)一樣。理論上,陸地上的任何地方,你都是可以用雙腳走到的,可那需要多長(zhǎng)時(shí)間和精力呢?我們更關(guān)注的是到達(dá)而不是如何去的問(wèn)題。
棧的引入簡(jiǎn)化了程序設(shè)計(jì)的問(wèn)題,劃分了不同關(guān)注層次,使得思考范圍縮小,更加聚焦于我們要解決的核心問(wèn)題。反之,像數(shù)組等,因?yàn)橐稚⒕θタ紤]數(shù)組下標(biāo)的增減問(wèn)題,反而掩蓋了問(wèn)題的本質(zhì)。
所以,現(xiàn)在的許多高級(jí)語(yǔ)言,比如Java,C#等都有對(duì)棧結(jié)構(gòu)的封裝,你可以不關(guān)注它的實(shí)現(xiàn)細(xì)節(jié),就可以直接使用Stack的push和pop方法,非常方便。
五、棧的應(yīng)用
(一) 遞歸
斐波那契數(shù)列
(二)四則運(yùn)算表達(dá)式求值
后綴(逆波蘭)表示法:從左到右遍歷表達(dá)式的每個(gè)數(shù)字和符號(hào),遇到是數(shù)字就進(jìn)棧,遇到是符號(hào),就將處于棧頂兩個(gè)數(shù)字出棧,進(jìn)行運(yùn)算,運(yùn)算結(jié)果進(jìn)棧,一直到最終獲得結(jié)果。
中綴表示法:從左到右遍歷中綴表達(dá)式的每個(gè)數(shù)字和符號(hào),若是數(shù)字就輸出,即成為中綴表達(dá)式的一部分;若是符號(hào),則判斷其與棧頂符號(hào)的優(yōu)先級(jí),是右括號(hào)有優(yōu)先級(jí)不高于棧頂符號(hào)(乘除有限加減)則棧頂元素依次出棧并輸出,并將當(dāng)前符號(hào)進(jìn)棧,直到最終輸出后綴表達(dá)式為止。
隊(duì)列
一、隊(duì)列定義
隊(duì)列(queue)是只允許在一端進(jìn)行插入操作,而在另一端進(jìn)行刪除操作的線性表。
隊(duì)列是一種先進(jìn)先出(First In First Out)的線性表,簡(jiǎn)稱FIFO。允許插入的一端稱為隊(duì)尾,允許刪除的一端稱為隊(duì)頭。
二、隊(duì)列的抽象數(shù)據(jù)類型
同樣是線性表,隊(duì)列也有類似線性表的各種操作,不同的就是插入數(shù)據(jù)只能在隊(duì)尾進(jìn)行,刪除數(shù)據(jù)只能在隊(duì)頭進(jìn)行。
三、循環(huán)隊(duì)列
隊(duì)列也有線性表的兩種存儲(chǔ)方式:順序存儲(chǔ)和鏈?zhǔn)酱鎯?chǔ)。
入隊(duì)操作是在隊(duì)尾增加一個(gè)元素,時(shí)間復(fù)雜度為O(1)。但是,出隊(duì)時(shí),如果規(guī)定隊(duì)列中的元素都放在前n個(gè)位置,則第一個(gè)元素出隊(duì)后,后面的元素都要向前移動(dòng),以保證對(duì)頭不為空,也就是下標(biāo)為0的位置不為空,此時(shí)時(shí)間復(fù)雜度為O(n)。
但是,我們可以想,如果沒(méi)有隊(duì)列的元素都必須放在前n個(gè)位置的規(guī)定,出隊(duì)的性能就會(huì)大大增加。于是,我們因?yàn)閮蓚€(gè)指針,front指向?qū)︻^元素,rear指針指向隊(duì)尾的下一個(gè)元素,當(dāng)front==rear,隊(duì)列為空。這樣,當(dāng)出隊(duì)時(shí),只需移動(dòng)front指針即可。但是,這樣也會(huì)有一個(gè)問(wèn)題,一個(gè)隊(duì)列不可能只有出隊(duì),當(dāng)繼續(xù)進(jìn)行入隊(duì)操作致使隊(duì)尾已經(jīng)填滿,rear指針指向隊(duì)列外時(shí),繼續(xù)入隊(duì)就可能產(chǎn)生數(shù)組越界的錯(cuò)誤。但是,由于前面已經(jīng)進(jìn)行過(guò)出隊(duì)操作,所以這個(gè)隊(duì)列前面會(huì)有空位置,這種現(xiàn)象稱為“假溢出”。這里舉個(gè)例子,現(xiàn)實(shí)生活中,當(dāng)你上了一輛公交車,發(fā)現(xiàn)前面有兩個(gè)空位置,但后排的座位都已經(jīng)滿了。這是,你不會(huì)告訴自己,后面沒(méi)座了,立馬下車,等待下一輛。我們都不會(huì)這么笨,而都會(huì)坐在前面的位置。
這時(shí),就引入了循環(huán)隊(duì)列的概念。循環(huán)隊(duì)列就是首尾相接的順序存儲(chǔ)結(jié)構(gòu)。
那么,問(wèn)題又來(lái)了,前面提到當(dāng)front==rear時(shí),隊(duì)列為空,但在循環(huán)隊(duì)列中,這個(gè)結(jié)論顯然不成立。所以,如何判斷隊(duì)列是空還是滿呢?以下給出兩種方法:
1. 設(shè)置一個(gè)標(biāo)志變量flag,當(dāng)front==rear,且flag=0時(shí)隊(duì)列為空,當(dāng)front==rear,且flag=1時(shí)隊(duì)列滿。
2. 當(dāng)隊(duì)列空時(shí),條件就是front=rear,當(dāng)隊(duì)列滿時(shí),我們修改條件,保留一個(gè)元素空間。也就是說(shuō),隊(duì)列滿時(shí),數(shù)組中還有一個(gè)空閑單元。
我們來(lái)重點(diǎn)討論第二種方法,由于rear可能比f(wàn)ront大,也可能比f(wàn)ront小,所以它們只相差一個(gè)位置時(shí)就是滿的情況,但也可能是相差整整一圈。所以,若隊(duì)列的最大尺寸是QueueSize,那么隊(duì)滿的條件是(rear+1)%QueueSize==front(取模%的目的就是為了整合front和rear大小為一個(gè)問(wèn)題)
通用的計(jì)算隊(duì)列長(zhǎng)度的公式為(rear-front+QueueSize)%QueueSize
循環(huán)隊(duì)列的相關(guān)條件和公式:
1. 隊(duì)空條件:rear==front
2. 隊(duì)滿條件:(rear+1) %QueueSIze==front,其中QueueSize為循環(huán)隊(duì)列的最大長(zhǎng)度
3. 計(jì)算隊(duì)列長(zhǎng)度:(rear-front+QueueSize)%QueueSize
4. 入隊(duì):(rear+1)%QueueSize
5. 出隊(duì):(front+1)%QueueSize
到這里,大家可以發(fā)現(xiàn),但是順序存儲(chǔ),若不是循環(huán)隊(duì)列,算法的時(shí)間性能是不高的,但循環(huán)隊(duì)列有面臨著數(shù)組可能會(huì)溢出的問(wèn)題,所以我們還需要研究一樣不需要擔(dān)心隊(duì)列長(zhǎng)度的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)。
四、隊(duì)列的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)及實(shí)現(xiàn)
隊(duì)列的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),其實(shí)就是線性表的單鏈表,只不過(guò)它只能尾盡頭出而已,稱為鏈隊(duì)列。將隊(duì)頭指針指向鏈隊(duì)列的頭結(jié)點(diǎn),隊(duì)尾指針指向終端結(jié)點(diǎn)。
空隊(duì)列是,front和rear都指向頭結(jié)點(diǎn)。
入隊(duì)操作:就是在鏈尾插入結(jié)點(diǎn)。
出隊(duì)操作,就是頭結(jié)點(diǎn)的后繼結(jié)點(diǎn)出隊(duì),將頭結(jié)點(diǎn)的后繼改為它后面的結(jié)點(diǎn),若鏈表除頭結(jié)點(diǎn)外只剩一個(gè)元素時(shí),則需將rear指向頭結(jié)點(diǎn)。
對(duì)比循環(huán)隊(duì)列和鏈隊(duì)列,時(shí)間上,其實(shí)它們基本操作都是常數(shù)時(shí)間,即都為O(1)的。不過(guò)循環(huán)隊(duì)列是事先申請(qǐng)好空間,使用期間不釋放,而對(duì)于鏈隊(duì)列,每次申請(qǐng)和釋放結(jié)點(diǎn)也會(huì)存在一些時(shí)間開(kāi)銷,如果入隊(duì)出隊(duì)頻繁,則兩者還是存在細(xì)微差異。對(duì)于空間上來(lái)說(shuō),循環(huán)隊(duì)列不存在這個(gè)問(wèn)題,盡管它需要一個(gè)指針域,會(huì)產(chǎn)生一些空間上的開(kāi)銷,但也可以接受。所以在空間上,鏈隊(duì)列更加靈活。
總的來(lái)說(shuō),在可以確定隊(duì)列長(zhǎng)度最大值的情況下,建議使用循環(huán)隊(duì)列,如果無(wú)法預(yù)估隊(duì)列長(zhǎng)度,則用鏈隊(duì)列。
總結(jié)
以上是生活随笔為你收集整理的大话数据结构—栈与队列的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 原神电震试炼需要使用什么角色
- 下一篇: 大话数据结构——树