跳转到内容

草稿:Van Emde Boas 樹

本页使用了标题或全文手工转换
维基百科,自由的百科全书


Van Emde Boas 樹
类型非二元
发明时间1975年
发明者Peter van Emde Boas
大O符号表示的时间复杂度
算法 平均 最差
空间
搜索
插入
删除

Van Emde Boas 樹(或稱vEB樹或van Emde Boas優先隊列)是一個在電腦科學中的資料結構,也是一種關聯陣列,也是一種,由荷蘭電腦科學家 Peter van Emde Boas[1]領導的團隊於 1975 年發明,可以儲存鍵值範圍在 m 位以內的二進制整數,也就是 是樹中可以儲存的最大數字時,它可以在 時間內執行所有種類的基本操作(假設對 的位元操作可以在常數時間內執行),也就是 時間。參數不要與樹中儲存的實際元素數量混淆,其他樹資料結構的效能通常透過該數量來衡量。 標準vEB樹的空間效率不夠。例如,用於儲存 32 位元整數(即當 ),它需要 個儲存位。然而,具有相似的時間效率和空間效率的資料結構可以使用 空間(當 𝑛 是儲存元素的數量),但是也可以修改vEB樹讓它只需要 空間。[2][3][4][5]

結構總覽

[编辑]

一棵 vEB 樹的每個節點包含以下資料:

  • u:紀錄該節點管理的鍵值集合範圍,範圍為
  • min:節點目前儲存的最小元素。若節點為空,則設為NIL標記為空。
  • max:節點目前儲存的最大元素。若節點為空,則設為NIL標記為空。
  • cluster:一個大小為 的陣列,其中每個元素是指向簇的指標,每個簇是一個子 vEB 樹,第 個簇負責管理範圍的資料,在該簇中會被重新映射到 儲存。
  • summary:一棵輔助 vEB 樹,追蹤哪些簇有資料。當且僅當 T.cluster[i] 非空時 T.summary 才會包含值

值得注意的是,若 是最小元素,直接記錄在T.min,不會被記錄在其他地方。

若節點為空,設定 T.min = T.max = NIL,所有找尋操作遇 NIL 都視作「不存在」,部分實作會採用 -1u 來代替。 [2][3][4]

表示法

[编辑]

在後續的演算法描述中,我們會用到以下函數,設整體鍵值範圍為 ,定義:

說明:

  1. 是每個簇的大小;
  2. 給出 在哪個簇(簇編號從 0 開始);
  3. 給出 在該簇內,範圍為 內的鍵值(編號也從 0 開始);
  4. 則把簇編號 與簇內鍵值 重組為全局鍵值。

可以觀察到: [4][5]

基本操作

[编辑]

以下為vEB 樹支援的一些操作:

操作 描述 时间复杂度
插入 在樹中插入一個新值
查詢後繼 給定一個數,查找下一個數
查詢前驅 給定一個數,查找上一個數
刪除 在樹中刪除一個值

插入

[编辑]

將值 x 插入到 vEB 樹 T 的操作 insert(T, x) 過程如下:

  1. 如果 xT.minT.max 相等,操作結束。
  2. 如果 T 為空,則設定 T.min = T.max = x,操作結束。
  3. 如果 x < T.min,則將 T.minx 交換,接下來的操作會把舊的T.min插入。
  4. 如果 x > T.max,則設定 T.max = x
  5. 最後,把 x 插入到負責 x 的簇 T.cluster[high(x)] 中。如果 T.cluster[high(x)] 之前為空,則同時將 high(x) 插入到 T.summary 中。

程式碼如下:

function Insert(T, x)
    if T.min == x || T.max == x then // x 已經被插入了
        return
    if T.min == NIL then // T 是空樹
        T.min = T.max = x; 
        return
   
    if x < T.min then
        swap(x, T.min) // 更新最小點,插入舊的最小點
    if x > T.max then
        T.max = x  // 更新最大點
    i = high(x)
    Insert(T.cluster[i], low(x))
    if T.cluster[i].min == T.cluster[i].max then
        Insert(T.summary, i)
end

此過程的效率關鍵在於,將元素插入到空的 vEB 樹只需 時間,然後在大小為 (即 位二進制數)的子樹上遞迴處理。因此,即使算法有時會進行兩次遞迴調用,這僅在第一次遞迴調用進入空子樹時發生。可以得出這運行時間的遞迴關係是 ,其解為

查詢後繼

[编辑]

查詢後繼的過程如下:

  1. 如果 x<T.min,則搜索結束,回傳 T.min。如果 x≥T.max,則後繼元素不存在,返回NIL。
  2. 否則,計算 i=high(x) 。如果 x < T.cluster[i].max,則要找的值位於 T.cluster[i] 中,因此在 T.cluster[i]遞迴搜尋。
  3. 否則,在 T.summary 中搜索值 i 的後繼,得到包含大於 x 元素的第一個簇的索引 j
  4. 然後,算法返回 T.cluster[j].min。在簇找到的元素需要與高位組合以形成完整的後繼元素。

程式碼如下:

function FindNext(T, x)
    if x < T.min then
        return T.min
    if x ≥ T.max then // 無後繼數
        return u
    i = high(x)
    
    if low(x) < T.cluster[i].max then // 後繼在這個簇中
        return index(i,FindNext(T.cluster[i], low(x))) 
    
    j = FindNext(T.summary, i)  // 後繼在其他簇,搜尋輔助vEB樹
    if j == NIL then  // 檢查是否存在後繼簇
        return NIL
   else
        return index(j, T.cluster[j].min)  // 與高位合併
end

注意,在任何情況下,這個函式本身執行 的工作,然後可能在大小為 的簇上遞迴處理。和之前的遞推關係一樣,所以複雜度仍是

查詢前驅

[编辑]

查詢前驅和查詢後繼的方式大致相似,只有部分地方修改,重點在T.min 也有可能是前驅,但不在子樹的任何地方。

function FindPrev(T, x)
   if x > T.max then
       return T.max
   if x ≤ T.min then  // 無前驅數
       return NIL
   i = high(x)
   
   if low(x) > T.cluster[i].min then  // 前驅在這個簇
       s = FindPrev(T.cluster[i], low(x))
       if s == NIL then // 前驅在T.min
           return T.min
       return index(i,s)  // 與高位合併
   j = FindPrev(T.summary, i)  // 前驅在其他子樹,搜尋輔助vEB樹
   if j == NIL then  // 檢查是否存在前驅子樹
        return NIL
   else
       return index(j, T.cluster[j].max)  // 與高位合併
end

刪除

[编辑]

從vEB樹中刪除節點是最複雜的操作。呼叫 Delete(T, x) 來刪除vEB樹T中的值x,其運作方式如下:

  1. 如果 T.min = T.max = x,則x是樹中唯一的元素。我們將 T.min = NILT.max = NIL 來表示樹為空。
  2. 否則,如果 x == T.min,則需要找到vEB樹中第二小的值y,從其當前位置刪除它,並設定 T.min=y。第二小的值yT.cluster[T.summary.min].min,因此可以在 時間內找到。我們從包含y的子樹中刪除它。
  3. 如果 x≠T.minx≠T.max,則從包含x的子樹 T.cluster[i] 中刪除x
  4. 如果 x == T.max,則需要找到vEB樹中第二大的值y並設定 T.max=y。首先按照前一種情況刪除x。值y要麼是 T.min,要麼是 T.cluster[T.summary.max].max,因此可以在 時間內找到。

在上述任何情況下,如果我們從子樹 T.cluster[i] 中刪除最後一個元素xy,則還需要從 T.summary 中刪除i

程式碼如下:

function Delete(T, x)
   // 刪除最後一個元素
   if T.min == T.max == x then   
       T.min = NIL      // 標記空樹
       T.max = NIL 
       return
   // 刪除當前最小值
   if x == T.min then 
       j = T.summary.min          // 通過輔助樹找最小簇索引
       T.min = x = index(j, T.cluster[j].min)  // 新min = 樹中存在的最小值
       Delete(T.cluster[j], T.cluster[j].min)  // 從子樹中刪除,因為T.min不應在子樹中出現
      return
   // 分解高位和低位
   i = high(x)  // 計算簇索引
   // 遞迴刪除
   Delete(T.cluster[i], low(x)) 
   // 處理空子樹
   if T.cluster[i] is empty then  
       Delete(T.summary, i)         // 從輔助樹刪除該子樹索引
   // 刪除的是當前最大值
   if x == T.max then
       if T.summary is empty then      // 沒有其他子樹
           T.max = T.min        // 只剩min值
       else
           j = T.summary.max //找最大值
           T.max = index(j, T.cluster[j].max)  // 合併
end

同樣,此程序的效率取決於從僅包含一個元素的vEB樹中刪除只需 時間。特別是,只有在刪除前 xT.cluster[i] 中唯一元素時,才會遞迴刪除 T.summary 中對應的元素。[2][3][4]

實際實現

[编辑]

實際上並不需要假設 必須是整數。運算 low(x)high(x) 可以分別替換為只取 x 的高位 位元和低位 位元。在大部分情況下,這都比除法取餘運算更高效。

在實際實現中,特別是在具有「位移 k 位」和「尋找首位零」指令的機器上,當 m 達到字長(或其小倍數)時,可以切換為使用位陣列來進一步提升效能。由於單一字長上的所有操作都是常數時間,這不會影響漸進效能,但能避免多數指標儲存和多次指標解引用,從而顯著節省實際的時間和空間。

vEB 樹的一個優化是捨棄空子樹。這使得 vEB 樹在包含大量元素時非常緊湊,因為只有在需要添加元素時才會創建子樹。最初,每添加一個元素會創建約 個新樹,總共包含約 個指標。隨著樹的增長,越來越多的子樹會被重複使用,尤其是較大的子樹。 上述實現使用指標,並佔用總空間 ,與鍵值空間的大小成正比。這可以通過以下方式理解:遞迴關係為 。解此遞迴關係會得到 。也可以通過數學歸納法證明 [2][3][4]

參考資料

[编辑]
  1. ^ Prof.dr Peter van Emde Boas | Institute for Logic, Language and Computation. www.illc.uva.nl. [2025-04-06]. 
  2. ^ 2.0 2.1 2.2 2.3 van Emde Boas, P.; Kaas, R.; Zijlstra, E. Design and implementation of an efficient priority queue. Mathematical systems theory. 1976-12-01, 10 (1) [2025-04-06]. ISSN 1433-0490. doi:10.1007/BF01683268 (英语). 
  3. ^ 3.0 3.1 3.2 3.3 Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein. Introduction to Algorithms, Third Edition. MIT Press, 2009. ISBN 978-0-262-53305-8. Chapter 20: The van Emde Boas tree, pp. 531–560.
  4. ^ 4.0 4.1 4.2 4.3 4.4 RuntimeErr. 你所不了解的数据结构-van Emde Boas 树. 洛谷专栏. 2021-06-27 [2025-04-06] (中文(中国大陆)). 
  5. ^ 5.0 5.1 Van Emde Boas Tree | Set 1 | Basics and Construction. GeeksforGeeks. 2019-08-02 [2025-04-27] (美国英语). 

參見

[编辑]

Category:数据结构 Category:树结构