RxSwift PriorityQueue 優(yōu)先級隊列的實(shí)現(xiàn)

在 RxSwift 框架中,在 PriorityQueue.swift 文件中权谁,使用數(shù)組實(shí)現(xiàn)了一個優(yōu)先級隊列 PriorityQueue

優(yōu)先級隊列(priority queue)是0個或者多個元素的集合憋沿,每個元素有一個優(yōu)先級旺芽,可以在集合中查找或刪除優(yōu)先級最高的元素。對優(yōu)先級隊列執(zhí)行的操作有:

  1. 查找辐啄。一般情況下采章,查找操作用來搜索優(yōu)先權(quán)最大的元素。
  2. 插入一個新元素壶辜。
  3. 刪除悯舟。一般情況下,刪除操作用來刪除優(yōu)先權(quán)最大的元素砸民。
    對于優(yōu)先權(quán)相同的元素抵怎,可按先進(jìn)先出次序處理或按任意優(yōu)先權(quán)進(jìn)行。

RxSwift 是通過數(shù)組實(shí)現(xiàn)優(yōu)先級隊列的岭参。在有元素入隊列和出隊列的之后必須對數(shù)組中的元素進(jìn)行排序便贵,才能在獲取隊列中優(yōu)先級最高的元素時非常快速冗荸。RxSwift 使用的是最大堆最小堆)的排序算法,對數(shù)組中的元素進(jìn)行排序的利耍。

堆樹

堆樹(最大堆或者最小堆)的定義如下:

  1. 堆樹是一棵完全二叉樹蚌本;
  2. 堆樹中某個節(jié)點(diǎn)的值總是不大于(或不小于)其子節(jié)點(diǎn)的值盔粹;
  3. 堆樹中每個節(jié)點(diǎn)的子樹都是堆樹;

當(dāng)父節(jié)點(diǎn)的值總是大于或等于任何一個子節(jié)點(diǎn)的值時為最大堆程癌,當(dāng)父節(jié)點(diǎn)的值總是小于或等于任何一個子節(jié)點(diǎn)的值時為最小堆舷嗡。

最大堆示例
最小堆示例

構(gòu)造最大樹

怎樣將一個未排序的數(shù)組構(gòu)造成堆樹呢?現(xiàn)在以最大堆為例進(jìn)行講解(最小堆同理)嵌莉。
加入我們拿到的未排序的數(shù)組為:

var numbers = [6, 2, 5, 4, 20, 13, 14, 15, 9, 7]

數(shù)組對應(yīng)的完全二叉樹如下圖所示:

未排序數(shù)組對應(yīng)的完全二叉樹

如果堆樹中的每個非子節(jié)點(diǎn)的值都大于它的兩個(或一個)相近的子節(jié)點(diǎn)的值进萄,那么整個堆樹就滿足了每個節(jié)點(diǎn)的值總是不小于其子節(jié)點(diǎn)的值。所以構(gòu)造堆樹的基本思路是:首先找到最后一個節(jié)點(diǎn)的父節(jié)點(diǎn)锐峭,從這個父節(jié)點(diǎn)開始調(diào)整樹中鼠,保證父節(jié)點(diǎn)的值大于子節(jié)點(diǎn)的值。然后從這個父節(jié)點(diǎn)繼續(xù)向前調(diào)整樹沿癞,直到所有的非子節(jié)點(diǎn)的值都不小于于它的相鄰的子節(jié)點(diǎn)的值援雇,這樣最大堆就構(gòu)造完畢了

假設(shè)樹中有n個節(jié)點(diǎn)椎扬,從0開始給節(jié)點(diǎn)編號惫搏,到n-1結(jié)束,對于編號為i的節(jié)點(diǎn)蚕涤,其父節(jié)點(diǎn)為(i-1)/2筐赔;左子節(jié)點(diǎn)的編號為i2+1,右子節(jié)點(diǎn)的編號為i2+2揖铜。最后一個節(jié)點(diǎn)的編號為n-1茴丰,其父節(jié)點(diǎn)的編號為(n-2)/2,所有從編號為(n-2)/2的節(jié)點(diǎn)開始調(diào)整樹蛮位。

如下圖所示较沪,最后一個節(jié)點(diǎn)為7,其父節(jié)點(diǎn)為20失仁,從20這個節(jié)點(diǎn)開始構(gòu)造最大堆尸曼;構(gòu)造完畢之后晋柱,轉(zhuǎn)移到下一個父節(jié)點(diǎn)借浊,直到所有父節(jié)點(diǎn)都構(gòu)造完畢薄湿。


最大堆構(gòu)造過程1

最大堆構(gòu)造過程2

思路已經(jīng)梳理完成蜓竹,下面我們看 RxSwift 具體是怎樣實(shí)現(xiàn)的驻粟。

PriorityQueue的初始化方法中傳入對比兩個元素優(yōu)先級和判斷元素是否相等的方法换吧。_elements用于保存隊列中的元素管引。

struct PriorityQueue<Element> {

    private let _hasHigherPriority: (Element, Element) -> Bool
    private let _isEqual: (Element, Element) -> Bool

    fileprivate var _elements = [Element]()

    init(hasHigherPriority: @escaping (Element, Element) -> Bool, isEqual: @escaping (Element, Element) -> Bool) {
        _hasHigherPriority = hasHigherPriority
        _isEqual = isEqual
    }
}

獲取優(yōu)先級最高的元素梆靖,即返回 _elements 最前的元素:

func peek() -> Element? {
    return _elements.first
}

有元素進(jìn)入隊列時冒签,首先將元素添加到數(shù)組_elements的末尾在抛,相當(dāng)于在堆樹的末尾又添加了一個節(jié)點(diǎn),這時需要根據(jù)這個節(jié)點(diǎn)的優(yōu)先級和其父節(jié)點(diǎn)的優(yōu)先級調(diào)整數(shù)組萧恕。因為在添加元素之前的樹結(jié)構(gòu)已經(jīng)滿足最大堆的要求刚梭,所以現(xiàn)在只需要關(guān)注最后一個節(jié)點(diǎn)和它的父節(jié)點(diǎn)的優(yōu)先級肠阱,如果最后一個節(jié)點(diǎn)的優(yōu)先級較高,則將最后一個節(jié)點(diǎn)和它的父節(jié)點(diǎn)進(jìn)行調(diào)整朴读。以此類推屹徘,一直向上調(diào)整到樹的頂端。

mutating func enqueue(_ element: Element) {
    _elements.append(element)
    bubbleToHigherPriority(_elements.count - 1)
}

// 從下標(biāo)為 initialUnbalancedIndex 處向高的優(yōu)先級處調(diào)整元素
private mutating func bubbleToHigherPriority(_ initialUnbalancedIndex: Int) {
    // 確保 initialUnbalancedIndex 在 _elements 的索引范圍內(nèi)
    precondition(initialUnbalancedIndex >= 0)
    precondition(initialUnbalancedIndex < _elements.count)

    var unbalancedIndex = initialUnbalancedIndex

    while unbalancedIndex > 0 {
        // unbalancedIndex 為未排序的索引
        // parentIndex 為未排序節(jié)點(diǎn)的父節(jié)點(diǎn)的索引
        let parentIndex = (unbalancedIndex - 1) / 2
        guard _hasHigherPriority(_elements[unbalancedIndex], _elements[parentIndex]) else { break }
        #if swift(>=3.2)
        _elements.swapAt(unbalancedIndex, parentIndex)
        #else
        swap(&_elements[unbalancedIndex], &_elements[parentIndex])
        #endif
        unbalancedIndex = parentIndex
    }
}

將優(yōu)先級最高的元素出隊列衅金,也就是將數(shù)組中的第一個元素移除噪伊,同時我們也有可能需要移除數(shù)組中的任意一個的元素。進(jìn)而我們可以將問題歸結(jié)為:怎樣移除指定索引處的元素氮唯。

當(dāng)要移除指定索引的元素時鉴吹,首先將指定索引處的元素和數(shù)組的最后一個元素交換位置,再將最后一個元素移除您觉,這樣原本在數(shù)組最后的元素移到了指定的索引處拙寡,這樣有可能破壞了的最大堆的規(guī)則,所以要將指定位置的元素根據(jù)其優(yōu)先級向下浮動琳水,讓它的子節(jié)點(diǎn)中值最大的一個和其交換位置肆糕,確保指定索引的優(yōu)先級大于它所有子節(jié)點(diǎn)的優(yōu)先級,然后將指定索引處的元素根據(jù)優(yōu)先級向上浮動在孝,確保指定索引處的元素優(yōu)先級小于或等于其父節(jié)點(diǎn)的優(yōu)先級诚啃。代碼如下:

// 優(yōu)先級最高的元素出隊列
mutating func dequeue() -> Element? {
    guard let front = peek() else {
        return nil
    }

    removeAt(0)

    return front
}

// 移除任意一個元素
mutating func remove(_ element: Element) {
    for i in 0 ..< _elements.count {
        if _isEqual(_elements[i], element) {
            removeAt(i)
            return
        }
    }
}

// 移除指定索引的元素
private mutating func removeAt(_ index: Int) {

    let removingLast = index == _elements.count - 1
    if !removingLast {
        #if swift(>=3.2)
        _elements.swapAt(index, _elements.count - 1)
        #else
        swap(&_elements[index], &_elements[_elements.count - 1])
        #endif
    }

    _ = _elements.popLast()

    if !removingLast {
        bubbleToHigherPriority(index)
        bubbleToLowerPriority(index)
    }
}


// 向低優(yōu)先級冒泡
private mutating func bubbleToLowerPriority(_ initialUnbalancedIndex: Int) {
    precondition(initialUnbalancedIndex >= 0)
    precondition(initialUnbalancedIndex < _elements.count)

    var unbalancedIndex = initialUnbalancedIndex
    while true {
        //
        let leftChildIndex = unbalancedIndex * 2 + 1
        let rightChildIndex = unbalancedIndex * 2 + 2

        var highestPriorityIndex = unbalancedIndex

        if leftChildIndex < _elements.count && _hasHigherPriority(_elements[leftChildIndex], _elements[highestPriorityIndex]) {
            highestPriorityIndex = leftChildIndex
        }

        if rightChildIndex < _elements.count && _hasHigherPriority(_elements[rightChildIndex], _elements[highestPriorityIndex]) {
            highestPriorityIndex = rightChildIndex
        }

        guard highestPriorityIndex != unbalancedIndex else { break }

        #if swift(>=3.2)
        _elements.swapAt(highestPriorityIndex, unbalancedIndex)
        #else
        swap(&_elements[highestPriorityIndex], &_elements[unbalancedIndex])
        #endif
        unbalancedIndex = highestPriorityIndex
    }
}

到此,RxSwift 的優(yōu)先級隊列 PriorityQueue 的所有功能已經(jīng)實(shí)現(xiàn)私沮,它使用最大堆排序始赎,插入和刪除元素的時間復(fù)雜度都是O(log(n))。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仔燕,一起剝皮案震驚了整個濱河市造垛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晰搀,老刑警劉巖五辽,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異外恕,居然都是意外死亡杆逗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門鳞疲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來罪郊,“玉大人,你說我怎么就攤上這事尚洽』陂希” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長橄维。 經(jīng)常有香客問我尺铣,道長,這世上最難降的妖魔是什么争舞? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮澈灼,結(jié)果婚禮上竞川,老公的妹妹穿的比我還像新娘。我一直安慰自己叁熔,他們只是感情好委乌,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荣回,像睡著了一般遭贸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上心软,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天壕吹,我揣著相機(jī)與錄音,去河邊找鬼删铃。 笑死耳贬,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的猎唁。 我是一名探鬼主播咒劲,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诫隅!你這毒婦竟也來了腐魂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤逐纬,失蹤者是張志新(化名)和其女友劉穎蛔屹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體风题,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡判导,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了沛硅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眼刃。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖摇肌,靈堂內(nèi)的尸體忽然破棺而出擂红,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布昵骤,位于F島的核電站树碱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏变秦。R本人自食惡果不足惜成榜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹦玫。 院中可真熱鬧赎婚,春花似錦、人聲如沸樱溉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽福贞。三九已至撩嚼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挖帘,已是汗流浹背完丽。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肠套,地道東北人舰涌。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像你稚,于是被迫代替她去往敵國和親瓷耙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容