1. 程式人生 > >《 常見演算法與資料結構》符號表ST(4)——二叉查詢樹刪除 (附動畫)

《 常見演算法與資料結構》符號表ST(4)——二叉查詢樹刪除 (附動畫)

符號表ST(4)——二叉查詢樹刪除 (附動畫)

本系列文章主要介紹常用的演算法和資料結構的知識,記錄的是《Algorithms I/II》課程的內容,採用的是“演算法(第4版)”這本紅寶書作為學習教材的,語言是java。這本書的名氣我不用多說吧?豆瓣評分9.4,我自己也認為是極好的學習演算法的書籍。

通過這系列文章,可以加深對資料結構和基本演算法的理解(個人認為比學校講的清晰多了),並加深對java的理解。

我們之前介紹了二叉查詢樹和它的基本操作,唯獨一個操作沒介紹,就是刪除操作,這裡,我們重點介紹刪除操作。

1 一種偷懶的方式

還記得我們曾經在符號表的介紹中說過的一種刪除方式嗎?

我們可以不刪除,而是把需要刪除的節點的value設為null(這裡我們叫放“墓碑”)。key還是放那裡,還是可以用來比較(但是不能做匹配操作)

但是如果頻繁刪除,符號表裡會有很多“墓碑”,這不利於我們維護我們的符號表。因此我們不考慮這種方式。

2 刪除的一般情況

由於二叉查詢樹的特殊結構,使得它的刪除操作並沒那麼容易,我們還是採用遞迴的方式去做。很容易想到,刪除操作可能會出三種情況:

2.1 被刪除元素沒有孩子

這個比較簡單,直接返回null,就可以讓它的父親節點指向null,然後自己就可以等著被回收就好了。

2.2 被刪除的元素有一個孩子

這個也還好,直接返回自己的另一個孩子,讓自己的父親節點指向自己的另一個孩子,自己坐等被回收。

情形1和2可以用來實現刪除最小值最大值

 public void deleteMin()
 {  root = deleteMin(root);  }
 private Node deleteMin(Node x)
 {
    if (x.left == null) return x.right;
    x.left = deleteMin(x.left);
    x.count = 1 + size(x.left) + size(x.right);
    return x;
 }

 public void deleteMax()
 {  root = deleteMax(root);  }
 private
Node deleteMax(Node x) { if (x.right == null) return x.left; x.right= deleteMin(x.right); x.count = 1 + size(x.left) + size(x.right); return x; }

2.3 被刪除的元素有兩個孩子

這個是最麻煩的操作,前使用的方案是50年前提出來的Hibbard deletion(50年了,天吶)

找自己右孩子裡面的最小值(最左)然後替換自己和它,然後刪除自己

原理: root的右孩子肯定全大於左孩子,然後它又是右孩子裡面的最小值,所以它做root節點可以滿足左邊比自己小,右邊比自己大的條件。

注意:要同步更新count值

public void delete(Key key)
{
    return delete(root,key);
}
private Node delete(Node x, Key key)
{
    if(x == null) return null;
    /*********找key*************/
    int cmp = key.compareTo(x.key);
    if(cmp > 0)
        x.right = delete(x.right,key);
    if(cmp < 0)
        x.left = delete(x.left,key);
    /**********找到key************/
    else{
    /**********情形1/2************/
        if(x.right == null)   
            return x.left;
        if(x.left == null)   
            return x.right;
    /**********情形3***********/
        Node min = Min(x.right);
        min.right = deleteMin(x.right);
        min.left = x.left;
        return min;
    }
    /**********更新count***********/
    x.count = 1 + size(x.right) + size(x.left);  
}

3 問題

由於刪除操作的特殊性,每次找右孩子替代自己,會導致二叉樹失去平衡,大量隨機測試表明,通過隨機的刪除後的二叉樹深度會退化到N,遠大於log(N),這樣,所有二叉樹的操作效率的會大打折扣。

所以,看似簡單的問題,卻難有好的解決方案。
50年來二叉的樹的刪除仍然是一個研究方向,大量科學家在尋找一個更直觀有效的刪除策略。就像我們本能的覺得應該有更簡單的in place的歸併策略,但是50年過去了,仍然沒人發現。