笨蛋都看得懂的二叉樹介紹(Java)

本文專門針對笨蛋介紹如何編寫二叉樹,包括二叉樹的結構、如何添加節點、如何刪除節點。java

首先介紹二叉樹的結構。node

圖片描述

二叉樹的結構有三個要點:this

  1. 每一個節點最多有兩個子節點,分別稱做左子節點和右子節點。
  2. 每一個節點的左子節點的值比它小,右子節點的值比它大。
  3. 每一個節點的左子樹每一個節點的值都比它小,右子樹每一個節點的值都比它大。

看上面這個例子,就徹底符合這三點。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;
    }
}

回頭看第一張圖,你會發現每一個節點最多有三根線連着,上面的線就表明 BasicBTreeparent,下面兩根線就分別表明 leftright 了。而節點中的數字就是 BasicBTreevalue

接下來咱們要爲 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;
}
相關文章
相關標籤/搜索