1. 程式人生 > >Van Emde Boas Tree

Van Emde Boas Tree

後繼 imu pan target 優先級隊列 ret 所在 完成 值範圍

van Emde Boas trees 支持所有優先級優先級隊列的操作,並且巧妙的是它對於SEARCH, INSERT,DELETE,MINIMUM,MAXMUN,SUCCESSOR,和PREDECESSOR這些操作的支持都在最壞復 雜度O(lglgn)之內。不過有些限制的是,所有的Kye值都必須在 0…n?1之間,且不能有重復值。換言之,他的算法復雜度不由數據的規模 有多 大而決定,而由key值的取值範圍而決定。

算導上這一章的講述方式我非常喜歡,循序漸進,從最基礎最簡單的一個結構開始,最終 演化成Van Emde Boas trees,這樣的方式更能讓人捕捉到發明人思路發展的一個演變過 程,毫無疑問,這一章得多記點筆記。

## 基礎方法 ##

這部分會有三種方法來存儲動態集合,盡管它們沒有一個能達到O(lglgu), 但是通過這三種方法卻能讓我們得到理解Van Emde Boas trees的真知灼見

## Direct Address  ##

Direct Addressing提供一種最簡單的方法來存儲一組數據。Direct Addressing直接用 一個大小為u的位數組A[0..u?1]來存儲這組屬於集合{0,1,2,…,u?1}的數。如果這組數中有x,那麽A[x]的值存1, 否則存0。

這種簡單粗暴的方法,顯然不能達到預期。對於INSERT、DELETE、MEMBER這些操作復雜 度在O(1)內。而對於MINIMUM、MAXMIMUM、SUCCESSOR、PREDECESSOR這些操作 每一個都要θ(u)。我們不妨對Direct Addressing進行改進一下。幾個 θ(u)的操作皆因必須對數組進行叠代才能得到結果的緣故。那麽如果我 們在Direct Addressing之上建立一棵二叉樹想必有所改善。

## Superimposing a binary tree structure ##

技術分享

二叉樹中的結點存儲著0或1,當兩個孩子中有一個為1時它為1,只有當兩個孩子都為0 的時候它才為0。

在Direct Addressing中花費θ(u)的操作,依賴於這棵二叉樹現在都有了 一個更低的上限O(lgu)。但這個上限還並不理想,不然我們不如使用一棵 紅黑樹,好歹人家還是O(lgn)的上限。如果把這棵樹的高度降低,說不定 是一個突破口,姑且試試。

    ## Superimposing a tree of constant height ##

要降低樹的高度,我們可以直接通過增加樹的度數來實現,將二叉樹將變為多叉樹。假 設全域的大小是u=22k,其中k是一個整數,那麽u√ 也是一個整數。

和在位數組上建立一棵二叉樹不同,現在我們在位數組上建立一棵度數為u√ 的樹,如下圖(a)。這棵樹的高度是2。樹中的每個結點存儲了各子樹的邏輯與結果。

如下圖(b)所示,可以將那些結點看作是一個數組summary[0..u√?1],當 且僅當A[iu√..(i+1)u√?1]含1,summary[i]才為1。

技術分享
我們把A中的u√位的子數組稱為簇,對於一個給定值x, 位A[x]存在於第?x/u√?個簇中。現在INSERT將在 O(1)內完成——對於插入一個x,分別將A[x]和?x/u√?設為1。對於MINIMUM、MAXIMUM、SUCCESSOR、PREDECESSOR和 DELETE這些操作將花費O(u√)。具體操作不做贅述。

比起Superimposing a binary tree,現在的結構似乎反而更糟了,前者最差勁的操作 也只要lgu,現在都要u√了,但是透過這個結構帶來了一點 新的想法,如果我們把數組summary也變作一個Superimposinga tree of constant height怎麽樣,一路遞歸下去情況會如何?

## A recursive structure  ##

前面用u√度數的樹給了我們一個啟示,假如我們能夠把問題的規模以開平 方的規模縮小的話,會有一個什麽效果?假設我們能做到以開平方的規模遞歸減小一個 數據結構的規模,而且每個操作在每一級遞歸上只產生一次新的遞歸調用,那麽 對於一 個大小為u的數據結構的操作有: [ T(u) = T(\sqrt{u}) + O(1) ] 令m=lgu, 有u=2m。那麽可以有: [ T(2^m) = T(2^{m/2}) + O(1)] 設S(m)=T(2m),可得新方程: [ S(m) = S(m/2) + O(1)] 可以得出S(m)=O(lgm),回到T(u)上來,那麽 T(u)=T(2m)=S(m)=O(lgm)=O(lglgu)。

這個假設說明如果我們能夠以遞歸方式以開平方的規模來縮小數據的規模大小,並在每 一級遞歸上花費O(1)的時間的話,我們這個假設的數據結構的操作的復雜度將 是O(lglgu)。

圍繞方程 T(u)=T(u√+O(1)),我們來設計一個遞歸的數據結構,以開 平方的規模來減小每一次遞歸的大小。當然,我們可能不能一步達到讓所有的操作都達 到O(lglgu),但是我們還是可以先設計出一個原型。自u起,我們 用一個u√=u1/2項的數據結構來持有u1/4項的,以 u1/4項的遞歸持有u1/8項的,以u1/8項的遞歸持有 … u=22k,如此,u1/2,u1/4,u1/8,…都為整數。

對於一個全域為{0,1,2,…u?1}的van Emde Boas數據結構為原 型,我們簡稱為proto-vEB(u)。它遵循以下這些規則:

如果u=2,那麽已是基礎大小,那麽它包含兩個標誌位A[0…1]
否則,u=22k, 且整數k≥1, 所以u≥4, 這時,proto-vEB(u)包含兩個屬性:

一個名為summary指向proto-vEB(u√)的指針。
一個u√大的指針數組cluster[0…1],其中每一個指 針指向一個proto-vEB(u√)。
如果cluster的第i個指針所指向的的集合含有元素,那麽i也存在於summary所指向 的集合中,否則i也不存在於summary中。

對於一個u=16的集合{2,3,4,5,7,14,15}情況就是 下面這樣的:
技術分享

在proto-VEB(u)中,一個給定值x,那麽他應該儲存在proto-VEB(u)中cluster的 第?x/u√?個指針指向的proto-VEB(u√) 的第xmodu√個值。鑒於此,在分析各項操作之前,先定義幾個有用 的工具函數: [ high(x) = \lfloor{x} / \sqrt{u} \rfloor ] [ low(x) = x \mod \sqrt{u} ] [ index(x, y) = x\sqrt{u} + y ]

## 判斷一個值是否存在 ##
PROTO-VEB-MEMBER(V, x)
    if V.u == 2
        return V.A[x]
    else
        return PROTO-VEB-MEMBER(V.cluster[high(x)], low(x))

1

## 找最小值 ##

從summary中得到最小值i,那麽最小值必定存在於cluster[i]所表示的集合中, 在從cluster[i]表示的集合中得到最小值,結合i值可以得到全局的最小值。偽 碼:

PROTO-VEB-MINIMUMN(V)
    if V.u == 2
        if V.A[0] == 1
            return 0
        else if V.A[1] == 1
            return 1
        else
            return NIL
    else
        min-cluster = PROTO-VEB-MINIMUM(V.summary)
        if min-cluster == NIL
            return NIL
        else
            offset = PROTO-VEB-MINIMUN(V.cluster[min-cluster])
            return index(min-cluster, offset)

復雜度 [ T(u) = 2T(\sqrt u) + O(1)] 設u=2m [ T(2^m) = 2T(2^{m/2}) + O(1)] 設S(m)=T(2m),得: [S(m) = 2S(m/2) + O(1)] [T(u) = S(m) = \theta(m) = \theta(lg u)]

## 找X的後繼 ##

1.從x所在的子集中找後繼
2.找不到的話,先從summary中得到下一個子集的索引,從下一個子集中找最小值。
3.轉換成全局的值。

PROTO-VEB-SUCCESSOR(V, x)
    if V.u == 2
        if x == 0 and V.A[1] == 1
            return 1
        else 
            return NIL
    else
        offset = PROTO-VEB-SUCCESSOR(V.cluster[high(x)], low(x))

復雜度: [ T(u) = 2T(\sqrt u) + \theta(\lg{\sqrt u})] [ = 2T(\sqrt u) + \theta(\lg u)] 用與前文類似的方法可以化得T(u)=θ(lgulglgu)

## 插入元素 ##

一路向下遞歸插入,並將summary相應設為1即可。

PROTO-VEB-INSERT(V, x)
    if V.u == 2
        V.A[x] = 1
    else
        PROTO-VEB-INSERT(V.summary, high(x))
        PROTO-VEB-INSERT(V.cluster[high(x)], low(x))
VEB-EMPTY-TREE-INSERT(V, x)
    V.min = x
    V.max = x
VEB-TREE-INSERT(V, x)
    if V.min == NIL
        VEB-EMPTY-TREE-INSERT(v, x)
    else if x < V.min
        exchange x with V.min
        if V.u > 2
            if VEB-TREE-MINIMUN(V.cluster[high(x)]) == NIL
                VEB-TREE-INSERT(V.summary, high(x))
                VEB-EMPTY-TREE-INSERT(V.cluster[high(x)], low(x))
            else
                VEB-TREE-INSERT(V.cluster[high(x)], low(x))
        if x > V.max 
            V.max = x

復雜度和PROTO-VEB-MINIMUN一樣 [ T(u) = 2T(\sqrt u) + O(1)] 即 θ(lgu)

## 刪除元素##

相對於插入,刪除要麻煩一點,因為不能直接從summary中刪除元素。要確保相對應的 cluser所代表的子集不包含任何元素才能在smmary中置0。探查一個proto-VEB是否只 包含一個元素,以目前的結構可以有幾種方式,但沒有一種快於 θ(lgu) 的方式。也就是說PROTO-VEB-DELETE註定要超過 θ(lgu)。這兒先別急 著實現proto-VEB的刪除操作,先來回顧一下,所有的基本操作我們都已經分析過一遍 了。拿最大值、最小值很慢,拿前驅、後繼很慢,插入刪除也要比預期的慢,似乎除 了MEMBER操作,所有的操作都很慢,但是仔細分析發現,找前驅慢是因為取最小值太 慢了,找後繼慢是因為取最大值太慢了。插入慢是因為要額外對summary執行一次插入, 刪除慢是因為對這個結構的判空慢,實際上插入和刪除慢是因為同一個問題,沒法快 速知道一個proto-VEB的尺寸和極限值。歸根結底,這些操作慢的癥結在於,沒法快速 知道最大值最小值,因為知道最大值最小值以後,尺寸便能在θ(1)之內得 到了。既然如此,我們不如直接將最大值,最小值直接記錄在proto-VEB的結構中。 Van Emde Boas Trees的最終模型就得到了,給proto-VEB添加記錄最大值和最小值的 兩個屬性。

The van Emde Boas tree

先約定,將van Emde Boas tree簡稱為vEb。

在給proto-VEB加上min和max兩個屬性之前,還得有一個問題要解決,proto-VEB 要求 u=22k,這個要求顯然有點太過苛刻了,現在我們把這個範圍放寬到 u=2k。放寬要面對的第一個問題就是u√不一定是整數了,解決的 辦法便是,我們的規模不再要求以開平方的規模來縮小了,而是以接近開平方的規模縮 小,簡言之,原來將proto-VEB(u)分解為u√個proto-VEB(u√), 現在則是將vEB(u)分解成?(lgu)/2?個 vEB(?(lgu)/2?)。直觀起見,將 ?(lgu)/2?用u√↑表示,將 ?(lgu)/2?以u√↓表示, u=u√↑?u√↓.

相較於proto-VEB結構上有以下兩個變化:

增加min和max兩個屬性
對於u=2的VEB來說,不需要數組A[0..1]了,因為min和max足以來 記錄兩個值。
對於u>2的VEB來說,min不儲存在任何一個cluster中,但是max值要, 為什麽這麽做,可以讓在空集合中插入元素和刪除集合中唯一元素的操作都為 O(1)。
重新定義一下幾個方法: [high(x) = \lfloor x / \sqrt[\downarrow] u \rfloor] [low(x) = x \mod \sqrt[\downarrow] u] [index(x, y) = x \sqrt[\uparrow] u + y]

VEB-TREE-DELETE(V, x)
    if V.min == V.max
        V.min == NIL
        V.max == NIL
    else if V.u == 2
        if x == 0
            V.min = 1
        else
            V.min = 0
        V.max = V.min
    else if x == V.min
            first-cluster = VEB-TREE-MINIMUN(V.summary)
            x = index(first-cluster, VEB-TREE-MINIMUN(V.cluster[first-cluster]))
            V.min = x
        VEB-TREE-DELETE(V.cluster[high(X)], low(x))
        if VEB-TREE-MINIMUN(V.cluster[high(x)]) == NIL
            VEB-TREE-DELETE(V.summary, high(x))
        if x == V.max
            summary-max = vEB-TREE-MAXIMUN(V.summary)
            if summary-max == NIL
                V.max = V.min
            else
                V.max = index(summary-max, vEB-TREE-MAXMIMUN(V.cluster[summary-max]))  

Van Emde Boas Tree