數據結構與算法-棧與隊列

使用抽象數據類型能夠幫助咱們更好的理解數據所需的操做,以後再進行具體的數據類型實現。實際上,每每是操做影響着咱們決定數據類型該如何實現,這裏有兩種典型的數據結構-棧和隊列。前端

本質上,棧和隊列都是線性表,只是根據操做的需求咱們人爲地在線性表上加上限制,造成了兩種具備獨特功能的數據結構。
算法

一、棧數組

首先,普通的線性表實現是有兩個端口能夠訪問的,可是若是做爲棧就要封閉一端,只能訪問另外一端。這固然不是自討苦吃,棧是一種抽象數據結構,是對現實世界對象的模擬。好比,自助餐廳中的一疊盤子,新盤子放在這一疊盤子的最上面,取得時候也是從最上面取。將其抽象出來就是棧,這是最合適的抽象方式。bash

基於棧的操做很是簡單:數據結構

  • 將數據壓入棧頂-push
  • 將棧頂數據彈出-pop
  • 查看棧頂數據-top

棧的實現不是難點,基於棧的操做也很簡單,重點是棧的運用。post

動態圖:spa

棧的先進後出規則,本質上表明着數據的次序,常見的二叉樹先序、中序、後序非遞歸遍歷,其實就是借用這種規則實現的。想要深刻理解棧,沒有取巧的方法,見多識廣用在這裏再合適不過了,這裏使用一個簡單的例子來加深對棧的理解。設計

大整數加法,如今有個需求,將1856845129568452684和8948756841235879相加,怎麼處理?這兩個整數太大了,尋常的整數類型根本沒法存儲他們,更別說他們相加的結果。爲了解決這個問題,能夠將這種很是大的數當作一串數字,分別存到兩個棧中,而後從棧中彈出數,進行加法操做。code

僞代碼以下:cdn

largeNumAdd()
{
    讀第一個數的數字,並將這些數字壓入到一個棧中;
    讀第二個數的數字,並將這些數字壓入到另外一個棧中;
    carry = 0;    //表明進位
    while(至少有一個棧不爲空)
        從每一個非空的棧中彈出一個數,將這兩個數字與進位相加;
        將和的個位數字壓入到結果棧中;
        將和的進位存到carry中;
    若是進位不爲0,將其壓入到結果棧中;
    從結果棧中彈出數字並顯示;
}複製代碼

簡單起見,這裏給出456和7891相加時棧的結構:

這不就是咱們學過的加法計算公式嘛,是的,這裏使用棧模擬了加法過程。

將數字壓入棧中,其實維持了千位、百位、十位、個位之間的次序,正是這個緣由才能保證棧彈出的時候數字相加是合理的。這只是棧簡單的一種運用,在現實生活中,全部須要保持次序的數據,均可以使用棧這種先進後出的結構,經過巧妙的設計完成算法邏輯。

二、隊列

隊列是一種簡單的等待序列,在尾部加入元素時隊列加長,在前端刪除數據時隊列縮短。與棧不一樣,隊列是一種使用兩端的結構:一端用來加入新元素,另外一端用來刪除元素。隊列是先進先出的結構。

隊列的操做與棧操做類似:

  • 在隊列尾部加入元素-enqueue(el)
  • 取出隊列的第一個元素-dequeue()
  • 查看隊列頭部元素-firstEI()

動態圖:

隊列的實現:

隊列的一種可能實現方式是使用數組,但這並不是最佳選擇。元素從隊尾加入而從隊首刪除,這會釋放數組中的某些單元,這些單元不該該浪費。一種可能的作法是使用循環數組,若是隊尾已滿而隊首有空的單元,能夠將新加元素放入隊首,造成循環數組,這種作法是空間比較緊張時的無奈之舉,由於它破壞了隊列的簡單易用性,因此不推薦。

隊列的另外一種可能實現是使用雙向鏈表,那麼執行入隊列和出隊列操做僅須要常數時間,而且沒有數組實現中空間的浪費,所以,推薦這種方法。

隊列的變種:

  • 優先隊列

在許多狀況下,簡單的隊列結構是不夠的,先入先出機制須要使用某些優先規則來完善。在郵政局中,殘疾人應該比其餘人享有必定的優先權。在進程隊列中,因爲系統的功能需求,即便在等待隊列中進程P1在P2以前,P2也須要在P1以前執行。以此類推,須要一種修正的隊列,這就是所謂的優先隊列。

優先隊列能夠用兩種鏈表的變種實現。一種變種是全部的元素按照進入順序排序,出隊時按照優先級。另外一種是根據元素的優先級決定新增元素的位置。在這兩種狀況下,總的執行時間都是O(n),在標準庫中使用後一種方式實現的,由於咱們但願在元素出隊時能夠儘量的快。

  • 雙端隊列

顧名思義,雙端隊列就是能夠在隊列的兩端壓入、彈出元素。這就有問題了,雙端隊列和普通的數組、鏈表有什麼區別?不均可以兩端訪問嘛。固然是有區別的,雙端隊列的產生是基於如下需求的。衆所周知,數組和鏈表是線性表的兩種實現方式,數組的優點在於能夠常數時間內隨機訪問元素,鏈表的優點在於能夠常數時間內在兩端插入數據。那麼,有沒有一種實現方式能夠綜合這兩個特色呢?答案是雙端隊列。

一切的奧妙在於雙端隊列的實現方式。首先從數組講起,咱們定義了數組A,數組A自己是支持常數時間內隨機訪問元素的,可是若是在頭部插入數據,就會形成大量元素後移,這是不能容忍的。怎麼解決呢?那就再定義一個數組B,若是在A頭部插入新元素a,就將a放到數組B的尾端,這時候數組A和數組B都是被封裝在雙端隊列中的,而且雙端隊列維護了一段鏈式結構,其中每一個節點指向一個數組。看到這裏想必你們已經明白,雙端隊列經過維護多個數組來避免頭部插入操做形成的大量數據後移,儘管雙端隊列的實現比較複雜,可是做爲使用者,既能夠常數時間內隨機訪問元素,又能夠常數時間內在隊列兩端插入數據,這對於某些場景下很是合適。

雙端隊列並不能取代數組和鏈表,由於數組和鏈表的實現簡單、直觀,能夠知足大部分需求,只有在特殊場景下才去考慮雙端隊列,這就是所謂的對症下藥。

三、標準庫實現

這裏簡單介紹下標準庫中的棧和隊列。

在標準庫中棧和隊列是一種容器適配器。什麼叫作容器適配器呢?其實就是拿一種已有的容器,在上面從新封裝對外暴漏的接口,拼裝成一種新的特殊容器。

標準庫首先實現了雙端隊列,它是一種真正的容器,不是容器適配器。

標準庫中的棧是一種容器適配器,默認是基於雙端隊列實現的,咱們在使用過程當中能夠指定新的底層容器,好比向量或者鏈表。

標準庫中的隊列也是一種容器適配器,默認也是基於雙端隊列實現的,可是咱們只能選擇鏈表做爲新的底層容器,不能選擇向量。這是由於隊列是容許在頭部刪除數據的,而向量沒有實現這種操做。

標準庫中的優先隊列也是一種容器適配器,默認是基於向量實現的,可是咱們也能選擇使用雙端隊列做爲底層容器。注意,這裏不能選擇鏈表,由於標準庫中的優先隊列要求底層容器提供隨機訪問迭代器,而鏈表並無提供。所謂的隨機訪問迭代器是指經過該迭代器能夠訪問容器中任一元素,而鏈表的迭代器只能自增或者自減,並不能隨機訪問任一元素。

到此爲止,棧和隊列的相關概念已經探討完畢,本文也只是淺嘗輒止,更加深刻的知識須要在實踐中摸索獲取,畢竟,成就在於我的。

數據結構與算法-二叉樹性質

相關文章
相關標籤/搜索