在有序數(shù)組中,可以快速找到特定的值,但是想在有序數(shù)組中插入一個(gè)新的數(shù)據(jù)項(xiàng),就必須首先找出新數(shù)據(jù)項(xiàng)插入的位置,然后將比新數(shù)據(jù)項(xiàng)大的數(shù)據(jù)項(xiàng)向后移動(dòng)一位,來給新的數(shù)據(jù)項(xiàng)騰出空間,刪除同理,這樣移動(dòng)很費(fèi)時(shí)。顯而易見,如果要做很多的插入和刪除操作和刪除操作,就不該選用有序數(shù)組。
另一方面,鏈表中可以快速添加和刪除某個(gè)數(shù)據(jù)項(xiàng),但是在鏈表中查找數(shù)據(jù)項(xiàng)可不容易,必須從頭開始訪問鏈表的每一個(gè)數(shù)據(jù)項(xiàng),直到找到該數(shù)據(jù)項(xiàng)為止,這個(gè)過程很慢。
樹這種數(shù)據(jù)結(jié)構(gòu),既能像鏈表那樣快速的插入和刪除,又能想有序數(shù)組那樣快速查找。這里主要實(shí)現(xiàn)一種特殊的樹——二叉(搜索)樹。二叉搜索樹有如下特點(diǎn):一個(gè)節(jié)點(diǎn)的左子節(jié)點(diǎn)的關(guān)鍵字值小于這個(gè)節(jié)點(diǎn),右子節(jié)點(diǎn)的關(guān)鍵字值大于或等于這個(gè)節(jié)點(diǎn)。插入一個(gè)節(jié)點(diǎn)需要根據(jù)這個(gè)規(guī)則進(jìn)行插入。
刪除節(jié)點(diǎn)時(shí)二叉搜索樹中最復(fù)雜的操作,但是刪除節(jié)點(diǎn)在很多樹的應(yīng)用中又非常重要,所以詳細(xì)研究并總結(jié)下特點(diǎn)。刪除節(jié)點(diǎn)要從查找要?jiǎng)h的節(jié)點(diǎn)開始入手,首先找到節(jié)點(diǎn),這個(gè)要?jiǎng)h除的節(jié)點(diǎn)可能有三種情況需要考慮:
·該節(jié)點(diǎn)是葉節(jié)點(diǎn),沒有子節(jié)點(diǎn)
·該節(jié)點(diǎn)有一個(gè)子節(jié)點(diǎn)
·該節(jié)點(diǎn)有兩個(gè)子節(jié)點(diǎn)
第一種最簡(jiǎn)單,第二種也還是比較簡(jiǎn)單的,第三種就相當(dāng)復(fù)雜了。下面分析這三種刪除情況:
要?jiǎng)h除葉節(jié)點(diǎn),只需要改變?cè)摴?jié)點(diǎn)的父節(jié)點(diǎn)對(duì)應(yīng)子字段的值即可,由指向該節(jié)點(diǎn)改為null就可以了。垃圾回收器會(huì)自動(dòng)回收葉節(jié)點(diǎn),不需要自己手動(dòng)刪掉;當(dāng)節(jié)點(diǎn)有一個(gè)子節(jié)點(diǎn)時(shí),這個(gè)節(jié)點(diǎn)只有兩個(gè)連接:連向父節(jié)點(diǎn)和連向它唯一的子節(jié)點(diǎn)。需要從這個(gè)序列中剪斷這個(gè)節(jié)點(diǎn),把它的子節(jié)點(diǎn)直接連到它的父節(jié)點(diǎn)上即可,這個(gè)過程要求改變父節(jié)點(diǎn)適當(dāng)?shù)囊茫ㄗ笞庸?jié)點(diǎn)還是右子節(jié)點(diǎn)),指向要?jiǎng)h除節(jié)點(diǎn)的子節(jié)點(diǎn)即可;第三種情況最復(fù)雜,如果要?jiǎng)h除有兩個(gè)子節(jié)點(diǎn)的節(jié)點(diǎn),就不能只用它的一個(gè)子節(jié)點(diǎn)代替它,比如要?jiǎng)h除節(jié)點(diǎn)25,如果用35取代它,那35的左子節(jié)點(diǎn)是15呢還是30?
因此需要考慮另一種方法,尋找它的中序后繼來代替該節(jié)點(diǎn)。下圖顯示的就是要?jiǎng)h除節(jié)點(diǎn)用它的后繼代替它的情況,刪除后還是有序的。(這里還有更麻煩的情況,即它的后繼自己也有右子節(jié)點(diǎn),下面再討論。)
那么如何找后繼節(jié)點(diǎn)呢?首先得找到要?jiǎng)h除的節(jié)點(diǎn)的右子節(jié)點(diǎn),它的關(guān)鍵字值一定比待刪除節(jié)點(diǎn)的大。然后轉(zhuǎn)到待刪除節(jié)點(diǎn)右子節(jié)點(diǎn)的左子節(jié)點(diǎn)那里(如果有的話),然后到這個(gè)左子節(jié)點(diǎn)的左子節(jié)點(diǎn),以此類推,順著左子節(jié)點(diǎn)的路徑一直向下找,這個(gè)路徑上的最后一個(gè)左子節(jié)點(diǎn)就是待刪除節(jié)點(diǎn)的后繼。如果待刪除節(jié)點(diǎn)的右子節(jié)點(diǎn)沒有左子節(jié)點(diǎn),那么這個(gè)右子節(jié)點(diǎn)本身就是后繼。尋找后繼的示意圖如下:
找到了后繼節(jié)點(diǎn),現(xiàn)在開始刪除了,先看第一種情況,后繼節(jié)點(diǎn)是delNode右子節(jié)點(diǎn)的做后代,這種情況要執(zhí)行以下四個(gè)步驟:
·把后繼父節(jié)點(diǎn)的leftChild字段置為后繼的右子節(jié)點(diǎn);
·把后繼的rightChild字段置為要?jiǎng)h除節(jié)點(diǎn)的右子節(jié)點(diǎn);
·把待刪除節(jié)點(diǎn)從它父節(jié)點(diǎn)的leftChild或rightChild字段刪除,把這個(gè)字段置為后繼;
·把待刪除的左子節(jié)點(diǎn)移除,將后繼的leftChild字段置為待刪除節(jié)點(diǎn)的左子節(jié)點(diǎn)。
如下圖所示:如果后繼節(jié)點(diǎn)就是待刪除節(jié)點(diǎn)的右子節(jié)點(diǎn),這種情況就簡(jiǎn)單了,因?yàn)橹恍枰押罄^為跟的子樹移到刪除的節(jié)點(diǎn)的位置即可。如下圖所示:
看到這里,就會(huì)發(fā)現(xiàn)刪除時(shí)相當(dāng)棘手的操作。實(shí)際上,因?yàn)樗浅?fù)雜,一些程序員都嘗試著躲開它,他們?cè)贜ode類中加了一個(gè)Boolean字段來標(biāo)識(shí)該節(jié)點(diǎn)是否已經(jīng)被刪除,在其他操作之前會(huì)先判斷這個(gè)節(jié)點(diǎn)是不是已經(jīng)刪除了,這樣刪除節(jié)點(diǎn)不會(huì)改變樹的結(jié)構(gòu),。當(dāng)然樹中還保留著這種已經(jīng)刪除的節(jié)點(diǎn),對(duì)存儲(chǔ)造成浪費(fèi),但是如果沒有那么多刪除的話,這也不失為一個(gè)好方法。下面是二叉搜索樹的主要代碼:
聯(lián)系客服