圖解 | 不就是隊列嗎

點擊下方“IT牧場”,選擇“設爲星標”



今天分享的內容是隊列這種數據結構,主要內容有:

  • 隊列的定義及應用

  • 順序隊列的介紹及實現

  • 循環隊列的介紹及實現

  • 鏈式隊列的介紹及實現



01

隊列的定義及應用

隊列 (queue)是一種先進先出的線性表,只允許在一端進行插入操作,在另一端進行刪除操作。允許數據插入的這一端稱爲隊尾,允許數據刪除的這一端稱爲隊頭。
隊列在生活中的一個應用是客戶服務的排隊等待。比如去移動營業廳辦理業務時,對於剛去的幾個人,由於客戶服務專員都是空閒的,所以可以一對一處理相關業務。在處理過程中,如果有其他客戶過來,那麼就需要排隊等待,當某個客戶服務專員處理完一個業務後,隊首的客戶就可以到相應窗口處理其業務了。


02

順序隊列的介紹及實現

順序隊列是指由數組實現的隊列。
我們說隊列有隊頭和隊尾,對於用數組實現的隊列,在這裏我們規定索引爲0的一端作爲隊頭。那麼,入隊操作就是就是向數組中添加元素,這時不需要移動任何元素。
當隊列滿時,需要做的是擴容,在這裏我們擴容爲原來的2倍。也就是當數組滿時,新建一個容量爲原數組2倍的數組,然後將原數組中的元素依次拷貝到新數組中。這裏入隊操作只有在數組容量不足時,纔會做一次數據移動,根據根據攤還分析法(滿滿的一篇,全是複雜度分析核心知識點),其時間複雜度爲O(1)。
接着,我們看下出隊操作的時間複雜是多少?
出隊操作,是將索引爲0的元素移除。由於,我們規定索引爲0的這一端是隊頭,因此,每次隊頭元素出隊後,都需要將隊列中剩餘的元素向前移動一位。所以出隊操作的時間複雜度是O(n)。
順序隊列的代碼實現如下:



03

循環隊列的介紹及實現

上述順序隊列的實現中,我們把索引爲0的位置當做隊頭,這使得每當元素出隊時,都要將其後面的所有元素向前移動一個位置。
當數據量很大時,這個操作就非常耗時了。那麼,可不可以減少數據移動的次數呢?
可以的。就是我們分別用head和tail指向隊列的隊頭和隊尾。這樣,當有元素入隊時,tail++就可以;
同樣的,當有元素出隊時,head++就可以了。
在這裏我們分別用head和tail指向隊列的隊頭和隊尾,那麼當隊列爲空時,head和tail指向同一個位置,也就是說 head==tail 表示隊列爲空。
那麼什麼時候會觸發數據移動呢?就是當tail的值等於數組容量,head不等於0時。因爲,如果tail的值等於數組容量且head=0,則表示隊列滿了。
隊列不滿時,數據遷移具體動畫演示如下:

這樣出隊操作的時間複雜度就降低了。這時用數組實現隊列的代碼如下:


那麼,能不能更進一步,就是當tail的值等於數組容量且head不等於0時(如下圖所示),在這種情況下,直接將元素添加到空的位置呢?
也就是說tail的值等於數組容量時,將其指向索引爲0的位置,即在從頭開始添加元素。我們把這種頭尾相接的順序存儲結構稱爲循環隊列
在索引爲0的位置添加元素後,tail需要加1,即向後移動一個位置。此時,head==tail,也就是說隊列滿了。
但是,我們在上面已經規定head==tail是表示隊列爲空。這時該怎麼辦呢?我們可以規定 (tail+1)%data.length==head 表示隊列滿了。
接着,我們對(tail+1)%data.length==head表示隊列滿做一個解釋:
  • 如下圖,tail=6時,(6+1)%7=0=head,此時隊列滿。
  • 下圖中,tail再次指向0,此時(0+1)%7=1=head,隊列滿。
  • 下圖中,tail=1,此時(1+1)%7=2=head,隊列滿。
根據上述分析,在循環隊列中,head==tail表示隊列爲空;(tail+1)%data.length==head表示隊列滿,此時,數組中浪費一個存儲空間。
基於數組實現的循環隊列代碼示例如下:


04

鏈式隊列的介紹及實現

鏈式隊列是指用鏈表實現的隊列。常見的鏈表結構如下圖:
對於這樣的鏈表不論是在head這一端添加元素還是刪除元素,都是容易的。
在鏈表頭節點head之前添加元素的時間複雜度是O(1),動畫演示如下:
刪除鏈表頭節點head時,不需要遍歷鏈表,時間複雜度是O(1),動畫演示如下:

也就是說對於鏈表的頭節點head來說,把其當做隊列的隊頭和隊尾都是可以的。
但是,隊列只允許在一端進行插入操作,在另一端進行刪除操作。因此,鏈表的head這一端,要麼做隊頭,要麼做隊尾。
接着,我們看下當有tail指向鏈表尾節點時,添加元素和刪除元素是怎麼樣的。
向tail所指節點,即鏈表尾節點添加元素不需要進行遍歷操作,時間複雜度是O(1)。
在要刪除鏈表尾節點時,要知道tail前一個節點是哪個,就需要從鏈表頭節點開始遍歷鏈表才能確定。因此,如果把鏈表尾節點當做隊列的隊頭,出隊操作的時間複雜度就是O(n)。
根據上述分析,我們應該把head指向的頭節點作爲隊頭,tail指向的尾節點作爲隊尾。這樣,出隊和入隊操作的時間複雜度就都是O(1)。
鏈式隊列的代碼實現如下:


今天的分享就到這裏了

錯誤或不足之處
歡迎留言指出
下一篇我們將學習新的內容,敬請期待


參考資料:
  • 《大話數據結構》——程傑著
  • 《數據結構與算法之美》——極客時間

  • 《玩轉數據結構》——慕課網

乾貨分享

最近將個人學習筆記整理成冊,使用PDF分享。關注我,回覆如下代碼,即可獲得百度盤地址,無套路領取!

001:《Java併發與高併發解決方案》學習筆記;002:《深入JVM內核——原理、診斷與優化》學習筆記;003:《Java面試寶典》004:《Docker開源書》005:《Kubernetes開源書》006:《DDD速成(領域驅動設計速成)》007:全部008:加技術羣討論

近期熱文

LinkedBlockingQueue vs ConcurrentLinkedQueue解讀Java 8 中爲併發而生的 ConcurrentHashMapRedis性能監控指標彙總最全的DevOps工具集合,再也不怕選型了!微服務架構下,解決數據庫跨庫查詢的一些思路聊聊大廠面試官必問的 MySQL 鎖機制

關注我

喜歡就點個"在看"唄^_^

本文分享自微信公衆號 - IT牧場(itmuch_com)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章