JS版資料結構第二篇(佇列)

走音發表於2019-04-25
本片部落格參考到銀國徽老師的《Js版資料結構與演算法》

佇列

佇列的定義

相信通過上篇部落格大家對棧有了一個大概的認識,今天我們開始對佇列的學習。

我們還是先看一下百度百科上對佇列的定義:

JS版資料結構第二篇(佇列)

說白了就是佇列是一種受限的線性表,''受限''體現在只能從表的前端(隊頭)進行刪除,只能從表的後端(隊尾)進行插入。接下來我還是用一個比喻更形象的說明一下我對佇列的理解。

佇列的理解

相信大家平時都會路過一些網紅店如‘喜茶’,‘一點點’,‘BONUS’(僱人排隊的行為不值得提倡?),每次路過時都是門庭若市,我家樓下的也有一家烤腸店,每到週末都會排起很長的隊伍。

JS版資料結構第二篇(佇列)

                                                                  圖 1

這個時候排隊的大家就形成了一個佇列

圖片中最前面的藍色衣服的大叔就是隊頭

在最後面拍照片的我呢就是隊尾

這時候當大叔交完錢拿到自己的烤腸後就可以離開我們的'佇列'(稱為出隊,只能從隊頭離開)

如果隔壁家的小孩被饞哭了的話他也只能去我的後面開始排隊(稱為入隊,只能從隊尾入隊)

注意: 大叔是先去排隊的,所以他也是先拿到自己的烤腸離開的,小朋友是後來的,所以他也會後拿到自己的烤腸,這也就是我們常說的'先進先出'。

對比棧的後進先出,是不是還挺有趣的?

js實現一個簡單的順序佇列

首先我們要清楚的是佇列分為順序佇列和迴圈佇列,上邊的例子我們排隊買烤腸就是順序佇列,至於迴圈佇列我會在接下來的例題中進行說明,我們先實現一個簡單的順序佇列。

JS版資料結構第二篇(佇列)

                                                                圖 2

佇列有兩個指標,分別為隊頭指標front和隊尾指標rear,

front指向隊頭元素,rear指向下一個入隊元素的儲存位置。(如圖2)

下方的ArrayQuene函式就實現一個簡單功能的順序佇列:

function ArrayQueue(){  
    var arr = [];  
        //入隊操作  
    this.push = function(element){  
        arr.push(element);  
        return true;  
    }  
        //出隊操作  
    this.pop = function(){  
        return arr.shift();  
    }  
        //獲取隊首  
    this.getFront = function(){  
        return arr[0];  
    }  
        //獲取隊尾  
    this.getRear = function(){  
        return arr[arr.length - 1]  
    }  
        //清空佇列  
    this.clear = function(){  
        arr = [];  
    }  
        //獲取隊長  
    this.size = function(){  
        return length;  
    }  
}  複製程式碼

例題:設計迴圈佇列

迴圈佇列理解

在實際使用佇列時,為了使佇列空間能重複使用,往往對佇列的使用方法稍加改進:無論插入或刪除,一旦rear指標增1或front指標增1時超出了所分配的佇列空間,就讓它指向這片連續空間的起始位置。

這實際上是把佇列空間想象成一個環形空間,環形空間中的儲存單元迴圈使用,用這種方法管理的佇列也就稱為迴圈佇列。除了一些簡單應用之外,真正實用的佇列是迴圈佇列。

在上文中提到過順序佇列中有兩個指標,front指向隊頭元素,rear指標指向下一個入隊的元素的儲存位置。結合下面這張圖我們分析一下迴圈佇列在不同情況下front指標和rear指標的指向。

JS版資料結構第二篇(佇列)

                                                             圖 3

  • (a)在初始空隊時,front和rear指標同時指向隊頭初始的位置(即0的位置,佇列長度為6)
  • (b)a,b,c三個元素入隊時,rear指標會隨著新增元素的個數移動到相應的位置(三個元素,向前移動1位,此時rear由0移動到3)
  • (c)a出隊,front指標也會隨著刪除元素的個數移動到相應的位置(刪除1個元素,向前移動1位,此時front由0移動到1)
  • (d)d,e,f,g入隊,rear向前移動4位,由3移動到1,此時佇列空間全部佔滿,rear和front同時指向1
  • (e)g出隊(由於g處於隊頭的位置,所以可以出隊),front向前移動1位,由0移動到1

相信大家對迴圈佇列有了一個初步的認識,話不多說,我們直接上例題

例題 

原題地址(LeetCode 622題)

設計你的迴圈佇列實現。 迴圈佇列是一種線性資料結構,其操作表現基於 FIFO(先進先出)原則並且隊尾被連線在隊首之後以形成一個迴圈。它也被稱為“環形緩衝器”。

迴圈佇列的一個好處是我們可以利用這個佇列之前用過的空間。在一個普通佇列裡,一旦一個佇列滿了,我們就不能插入下一個元素,即使在佇列前面仍有空間。但是使用迴圈佇列,我們能使用這些空間去儲存新的值。

你的實現應該支援如下操作:

  • MyCircularQueue(k): 構造器,設定佇列長度為 k 。
  • Front: 從隊首獲取元素。如果佇列為空,返回 -1 。
  • Rear: 獲取隊尾元素。如果佇列為空,返回 -1 。
  • enQueue(value): 向迴圈佇列插入一個元素。如果成功插入則返回真。
  • deQueue(): 從迴圈佇列中刪除一個元素。如果成功刪除則返回真。
  • isEmpty(): 檢查迴圈佇列是否為空。
  • isFull(): 檢查迴圈佇列是否已滿。

題目不是很難,我們簡單的分析一下題目裡面需要我們實現的操作:

  • 建立一個長度為k的陣列arr,並宣告front和rear指標,初始值都設定為0,當插入或刪除元素時我們需要手動操作指標的位置。
  • 隊首元素直接返回arr[front]即可。
  • 如果rear不指向0時,隊尾元素是arr[rear-1],如果rear指向0的時候隊尾元素就是arr[k-1]。
  • 插入元素,直接將插入元素賦值給arr[rear],注意要先判斷下佇列是否是滿的狀態。
  • 刪除元素,將元素arr[front]賦值為空,注意要先判斷一下佇列是否是空的狀態。
  • 檢查佇列是否為空,當front和rear的指向相同且隊首元素也為空時,佇列為空。
  • 檢查佇列是否已滿,當front和rear的指向相同且隊首元素有值時,佇列已滿。

程式碼如下:

    class MyCircularQueue{
        // 構造器
        constructor (k) {
            // 建立長度為k的迴圈佇列
            this.arr= Array(k)
            // 宣告隊首指標front
            this.front = 0
            // 宣告隊尾指標rear
            this.rear = 0     
        }
        // 獲取隊首元素
        Front () {
            // 如果佇列為空,返回-1 否則直接返回front指向的值
            return this.isEmpty()? -1 : this.arr[this.front]
        }
        // 獲取隊尾元素
        Rear () {
            // 如果此時rear指標指向0的位置,返回佇列最後一位,否則返回下一位
            let rearItem = rear -1 
            return this.arr[rearItem < 0 ? k-1 : rearItem]
        }
        // 插入元素
        enQuene (value) {
            // 先判斷佇列是否是已滿狀態
            if(this.isFull()){
                return false
            }else{
                // 插入元素
                this.arr[this.rear] = value
                // 移動rear指標位置,考慮到迴圈佇列指標的變動,用取模的方式
                this.rear = (this.rear+1) % k
                return true
            }
        }
        // 刪除元素
        deQuene () {
            // 先判斷佇列是否為空
            if(this.isEmpty()){
                return false
            }else{
                let deleteItem = this.arr[this.front]
                this.arr[this.front] = ''
                this.front = (this.front+1) % k
                return deleteItem
            }
        }    
        // 判斷佇列是否為空
        isEmpty () {
            // 頭尾指標指向同一個地址並且隊首元素為空
            retutn this.rear === this.front && !this.arr[this.front]
        }
        // 判斷佇列是否已滿
        isFull () {
            // 頭尾指標指向同一個地址並且隊首元素不為空
            return this.rear === this.front && !!this.arr[this.front]
        }
    }複製程式碼

總結

棧和佇列是相對簡單的資料結構,相信通過兩篇部落格大家對這兩種資料結構都有了簡單的認識,接下來我將介紹相對複雜一些的資料結構。

上一篇  JS版資料結構第一篇(棧)


相關文章