《 常見演算法與資料結構》符號表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 問題
由於刪除操作的特殊性,每次找右孩子替代自己,會導致二叉樹失去平衡,大量隨機測試表明,通過隨機的刪除後的二叉樹深度會退化到
所以,看似簡單的問題,卻難有好的解決方案。
50年來二叉的樹的刪除仍然是一個研究方向,大量科學家在尋找一個更直觀有效的刪除策略。就像我們本能的覺得應該有更簡單的in place
的歸併策略,但是50年過去了,仍然沒人發現。