本文專門針對笨蛋介紹如何編寫二叉樹,包括二叉樹的結構、如何添加節點、如何刪除節點。java
首先介紹二叉樹的結構。node
二叉樹的結構有三個要點:this
看上面這個例子,就徹底符合這三點。spa
這時候笨蛋就會問了:前面兩點我理解,可是第三點是怎麼作到的?設計
因此接下來介紹下二叉樹是如何 「生長」 起來的:code
如上圖所示,當加入一個新節點時,從根節點開始對它進行比較。若是它比根節點小,則放入根節點的左子樹,若是比根節點大,則放入根節點的右子樹。遞歸
而後再進行下一級節點的比較,直到遇到最後一級節點,纔將新節點加入爲該節點的左或右子節點。圖片
以第四幅圖的節點 25 爲例,它第一次會與根節點 10 比較,結果就是 25 應該放入 10 的右子樹,這就排除了它放入左子樹的可能,即 25 不可能放到 4 的下面。it
而後 25 再和節點 33 比較,結果是它比較小,因此應該放入 33 的左子樹。由於 33 沒有左子節點,那麼 25 就直接做爲 33 的左子節點了。class
經過這種生長方式,咱們不管什麼時候都能獲得知足前面三個要素的二叉樹。
那麼寫代碼該如何實現呢?所謂慢工出細活,咱們一步一步來。
首先咱們建立二叉樹節點的基本結構。每一個二叉樹都有四個成員,以下所示。
public class BasicBTree { public int value; // 節點的值 public BasicBTree left; // 節點的左子節點 public BasicBTree right; // 節點的右子節點 public BasicBTree parent; // 節點的父節點。若是爲 null 則表示該節點是根節點 // 構造方法 public BasicBTree(int value) { this.value = value; } }
回頭看第一張圖,你會發現每一個節點最多有三根線連着,上面的線就表明 BasicBTree
的 parent
,下面兩根線就分別表明 left
和 right
了。而節點中的數字就是 BasicBTree
的 value
。
接下來咱們要爲 BasicBTree
編寫兩個簡單的方法,用來給它添加左子節點和右子節點:
// 將一個節點加爲當前節點的左子節點 public void setLeft(BasicBTree node) { if (this.left != null) { this.left.parent = null; // 解除當前的左子節點 } this.left = node; if (this.left != null) { this.left.parent = this; // 設置新子節點的父節點爲自身 } } // 將一個節點加爲當前節點的右子節點 public void setRight(BasicBTree node) { if (this.right != null) { this.right.parent = null; // 解除當前的右子節點 } this.right = node; if (this.right != null) { this.right.parent = this; // 設置新子節點的父節點爲自身 } }
在上面兩個方法的基礎上,咱們能夠添加一個添加任意值節點的方法:
// 將一個節點加爲當前節點的左或右子節點 public void setChild(BasicBTree node) { if (node == null) { return; } if (node.value < this.value) { setLeft(node); } else if (node.value > this.value) { setRight(node); } }
另外咱們再添加一個刪除左子節點或右子節點的方法:
// 刪除當前節點的一個直接子節點 public void deleteChild(BasicBTree node) { if (node == null) { return; } if (node == this.left) { node.parent = null; this.left = null; } else if (node == right) { node.parent = null; this.right = null; } }
這幾個方法都是很是簡單的,其中 setChild()
和 deleteChild()
這兩個方法,咱們後面介紹刪除節點的時候會用到。
如今咱們正式實現構造樹的方法,就是把一個一個數字加到樹裏面去,讓樹越長越大的方法:
// 向當前節點下面的樹中添加一個值做爲新節點 public void add(int value) { if (value < this.value) { // 表示應該放入左子樹 if (this.left == null) { // 若是左子樹爲空則構建一個節點加進去 setLeft(new BasicBTree(value)); } else { this.left.add(value); // 不然對左子樹一樣調用 add 方法(即遞歸) } } else if (value > this.value) { // 表示應該放入右子樹 if (this.right == null) { // 若是右子樹爲空則構建一個節點加進去 setRight(new BasicBTree(value)); } else { this.right.add(value); // 不然對右子樹一樣調用 add 方法(即遞歸) } } }
這個方法稍微複雜一些,主要是由於邏輯上使用了遞歸。這個方法怎麼用呢?以最開始的樹爲例,演示如何長成這棵樹:
public static void main(String[] args) { // 根節點 BasicBTree tree = new BasicBTree(10); // 第一層子節點 tree.add(4); tree.add(33); // 第二層子節點 tree.add(25); tree.add(46); tree.add(8); tree.add(1); }
你可能會注意到,加入每一層的子節點時,層內節點的添加順序能夠任意調換,構造出來的樹都是同樣的;可是若是將不一樣層的節點順序互換,構造出來的二叉樹就會變樣了。這當中的緣由能夠本身想一想。
最後來介紹二叉樹中最複雜的操做:刪除節點。爲何這個操做最複雜呢?由於刪除一個節點以後,要把它下面的節點接上來,同時要保持這棵樹繼續知足三要素。
如何把下面的節點接上來呢?最笨的方法固然是把被刪節點的全部子節點一個個從新往樹裏面加。可是這樣作效率實在不高。想一想若是被刪節點有上百萬個子節點,那操做步驟就太多了(以下圖所示)。
怎麼作才能效率高呢?有一個辦法,就是從被刪節點的子節點中找到一個合適的,替換掉被刪節點。這樣作的步驟就少得多了。
不過這樣的節點是否存在呢?答案是,除非被刪節點沒有子節點,不然是必定存在的。
並且這樣的節點可能不止一個。原則上講,被刪節點的左子樹的最大值,或右子樹的最小值,都是知足條件的,均可以用來替換被刪節點。好比說,將左子樹的最大值節點替換上去以後,左子樹的剩餘節點的值都仍然小於該位置的節點。下面是一個例子:
好比要刪除節點 33,而該節點左子樹的最大值爲 31,那麼直接將 31 替換到 33 的位置便可,整棵樹仍然知足三要素。
同理,被刪節點右子樹的最小值也能夠用來替換被刪節點。好比上圖中 33 節點的右子節點 46 也能夠用來替換 33,整棵樹仍然知足三要素。
因此這個問題就轉化爲:如何尋找被刪節點的左子樹的最大值和右子樹的最小值。顯然,由於二叉樹全部的左節點都比較小,右節點都比較大,因此要找最大值,順着右節點找便可;要找最小值,順着左節點找便可。下面是實現的代碼:
// 搜索當前節點左子樹中的最大值節點,若是沒有左子節點則返回 null public BasicBTree leftMax() { if (this.left == null) { return null; } BasicBTree result = this.left; // 起始節點 while (result.right != null) { // 順着右節點找 result = result.right; } return result; } // 搜索當前節點右子樹中的最小值節點,若是沒有右子節點則返回 null public BasicBTree rightMin() { if (this.right == null) { return null; } BasicBTree result = this.right; // 起始節點 while (result.left != null) { // 順着左節點找 result = result.left; } return result; }
咱們還剩下兩個準備工做,第一個是實現節點的查找:
// 查詢指定值的節點,若是找不到則返回 null public BasicBTree find(int value) { BasicBTree result = this; // 起始節點 if (result.value == value) { return result; } while (result.left != null || result.right != null) { // 若是查找的值比當前節點小則順着左子樹查找; // 若是比當前節點大則順着右子樹查找。 if (value < result.value && result.left != null) { result = result.left; } else if (value > result.value && result.right != null) { result = result.right; } if (result.value == value) { return result; } } return null; }
第二個是實現節點的替換:
// 將節點 node 替換爲節點 replace public BasicBTree replace(BasicBTree node, BasicBTree replace) { // 1. replace 接管 node 的子節點 replace.setLeft(node.left); replace.setRight(node.right); // 2. replace 從原來的 parent 脫離 if (replace.parent != null) { replace.parent.deleteChild(replace); } // 3. node 原來的 parent 接管 replace if (node.parent != null) { node.parent.setChild(replace); } // 注意 2 必須在 3 以前,1 位置不論 return replace; }
注意這裏用到了以前的 setChild()
和 deleteChild()
兩個方法。而 replace()
方法之因此設計爲返回 replace
參數,是爲了使用方便。
最後咱們就能夠正式實現二叉樹刪除節點的方法了:
// 從樹的子節點中刪除指定的值,並重組剩餘節點 public BasicBTree delete(int value) { BasicBTree node = find(value); if (node == null) { return this; } // 沒有子節點,直接刪除便可 if (node.left == null && node.right == null) { if (node.parent != null) { node.parent.deleteChild(node); return this; } else { // 表示整棵樹惟一的根節點刪了,只能返回 null return null; } } // 若是有子節點,則取左子樹的最大值或者右子樹的最小值均可以, // 來取代該節點。這裏優先取左子樹的最大值 BasicBTree replace; if (node.left != null) { replace = replace(node, node.leftMax()); } else { replace = replace(node, node.rightMin()); } // 若是被刪除的是根節點,則返回用於替換的節點,不然仍是返回根節點 return node == this ? replace : this; }