一次搞懂各種樹之二叉搜索樹


樹是一種非常常見且非常重要的數據結構。與線性數據結構不同,樹是非線性數據結構,因而也比鏈表、數組等要複雜。但是學好樹也不難,學不好的話,那就只能找棵樹自掛東南枝了。
樹的定義:樹是由結點或頂點和邊組成的(可能是非線性的)且不存在着任何環的一種數據結構。沒有結點的樹稱爲空(null或empty)樹。一棵非空的樹包括一個根結點,還(很可能)有多個附加結點,所有結點構成一個多級分層結構。

1. 二叉樹

1.1定義

二叉樹(Binary Tree)是n(n>=0)個節點的有限集合,該集合或者空集(稱爲空二叉樹),或者由一個根節點和兩棵互不相交的,分別稱爲根節點的左子樹和右子樹的二叉樹組成。
滿二叉樹除最後一層無任何子節點外,每一層上的所有結點都有兩個子結點。也可以這樣理解,除葉子結點外的所有結點均有兩個子結點。節點數達到最大值,所有葉子結點必須在同一層上。
完全二叉樹 若設二叉樹的深度爲h,除第 h 層外,其它各層 (1~(h-1)層) 的結點數都達到最大個數,第h層所有的結點都連續集中在最左邊,這就是完全二叉樹。

1.2 性質

  • 二叉樹性質

    • 在非空二叉樹中,第i層的結點總數不超過2i-1, i>=1;
    • 深度爲h的二叉樹最多有2h-1個結點(h>=1),最少有h個結點;
    • 對於任意一棵二叉樹,如果其葉結點數爲N0,而度數爲2的結點總數爲N2,則N0=N2+1;
    • 具有n個結點的完全二叉樹的深度爲log2(n+1);
    • 有N個結點的完全二叉樹各結點如果用順序方式存儲,則結點之間有如下關係:
        若I爲結點編號則 如果I>1,則其父結點的編號爲I/2;
        如果2I<=N,則其左兒子(即左子樹的根結點)的編號爲2I;若2I>N,則無左兒子;
        如果2I+1<=N,則其右兒子的結點編號爲2I+1;若2I+1>N,則無右兒子。
    • 給定N個節點,能構成h(N)種不同的二叉樹,其中h(N)爲卡特蘭數的第N項,h(n)=C(2*n, n)/(n+1)。
    • 設有i個枝點,I爲所有枝點的道路長度總和,J爲葉的道路長度總和J=I+2i。
  • 滿二叉樹的性質

    • 一顆樹深度爲h,最大層數爲k,深度與最大層數相同,k=h;
    • 葉子數爲2h;
    • 第k層的結點數是:2k-1;
    • 總結點數是:2k-1,且總節點數一定是奇數
  • 完全二叉樹

    • 完全二叉樹是效率很高的數據結構,堆是一種完全二叉樹或者近似完全二叉樹,所以效率極高,像十分常用的排序算法、Dijkstra算法、Prim算法等都要用堆才能優化,二叉排序樹的效率也要藉助平衡性來提高,而平衡性基於完全二叉樹。

2. 二叉查找樹

2.1 定義

又稱爲是二叉排序樹(Binary Sort Tree)或二叉搜索樹。可能是一棵空樹,或者是具有下列性質的二叉樹:
  1) 若左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
  2) 若右子樹不空,則右子樹上所有結點的值均大於或等於它的根結點的值;
  3) 左、右子樹也分別爲二叉排序樹;
  4) 沒有鍵值相等的節點。

2.2 性質

  • 對二叉查找樹進行中序遍歷,即可得到有序的數列。
    這裏補充一下二叉樹的遍歷順序,首先看圖:二叉樹
    • 前序遍歷:規則是若二叉樹爲空,則空操作返回,否則先訪問根結點,然後前序遍歷左子樹,再前序遍歷右子樹。如圖所示,遍歷的順序爲:ABDECF。
    • 中序遍歷:規則是若樹爲空,則空操作返回,否則從根結點開始(注意並不是先訪問根結點),中序遍歷根結點的左子樹,然後是訪問根結點,最後中序遍歷右子樹。如圖所示,遍歷的順序爲:DBEAFC。
    • 後序遍歷:規則是若樹爲空,則空操作返回,否則從左到右先葉子後結點的方式遍歷訪向左右子樹,最後是訪問根結點。如圖所示,遍歷的順序爲:DEBFCA。
  • 二叉查找樹的時間複雜度:它和二分查找一樣,插入和查找的時間複雜度均爲O(logn),但是在最壞的情況下仍然會有O(n)的時間複雜度。原因在於插入和刪除元素的時候,樹沒有保持平衡。我們追求的是在最壞的情況下仍然有較好的時間複雜度,這就是平衡查找樹設計的初衷。
  • 二叉查找樹的高度決定了二叉查找樹的查找效率。

2.3二叉查找樹的插入過程:

  1. 若當前的二叉查找樹爲空,則插入的元素爲根節點;
  2. 若插入的元素值小於根節點值,則將元素插入到左子樹中;
  3. 若插入的元素值不小於根節點值,則將元素插入到右子樹中。

2.4 二叉查找樹的刪除

2.4.1 刪除葉子節點

p爲葉子節點,直接刪除該節點,再修改其父節點的指針(注意分是根節點和不是根節點)
二叉搜索樹
刪除葉子節點25,則
刪除節點

2.4.2 刪除單支節點

p爲單支節點(即只有左子樹或右子樹)。讓p的子樹與p的父親節點相連,刪除p即可(注意分是根節點和不是根節點)
刪除上圖的值爲16節點
刪除單支節點

2.4.3 節點完整的點

刪除有左右子樹的節點,有兩個辦法,即用左子樹的最大節點取代待刪除節點,或者用最小右子樹的節點取代待刪除節點。

  • 用左子樹的最大節點取代待刪除節點
    delete node- 用右子樹的最小節點取代待刪除節點
    那就是用36取代33。即
    二叉樹
    這兩種方法的所產生的二叉樹形態看起來似乎不一樣,那麼爲什麼有這兩種結果呢?其實這隻有一種結果,判斷其是否正確的依據是刪除該節點之後,二叉樹的中序遍歷結果是否保持相對一致。在刪除之前,二叉搜索樹的中序遍歷結果是
    1 -> 3 -> 5 -> 10 -> 12 -> 15 -> 17 -> 28-> 33-> 36-> 38
    當我們刪除33節點時,中序遍歷的結果就是28後面是36。對於這兩個方法產生的中序遍歷結果,都是這樣的。

3.代碼實現

alk is cheap, show me the code.

Node.java

public class Node<T extends Comparable> {
    public T val;
    public Node left;
    public Node right;

    public Node(T val){
        this.val = val;
    }

    @Override
    public String toString() {
        return "Node{" +
                "val=" + val +
                '}';
    }
}

BInarySearchTree.java

public class BinarySearchTree {
    //二叉搜索樹根節點
    public static Node root;

    public BinarySearchTree() {
        this.root = null;
    }

    /**
     * 查找
     * * 樹深(N) O(lgN)
     * * 1. 從root節點開始
     * * 2. 比當前節點值小,則找其左節點
     * * 3. 比當前節點值大,則找其右節點
     * * 4. 與當前節點值相等,查找到返回TRUE
     * * 5. 查找完畢未找到,     *     *
     *
     * @param key * @return
     */
    public Node search(Node key) {
        Node current = root;
        int res = 0;
        while (current != null && ((res
                = key.val.compareTo(current.val)) != 0)) {
            if (res < 0) current = current.left;
            else current = current.right;
        }
        return current;
    }

    /**
     * 插入節點
     * @param key
     * @return
     */
    public Node insert(Node key) {
        //當前節點
        Node current = root;
        //上個節點
        Node parent = null;
        //如果根節點爲空,那麼插入的節點爲跟節點
        if (current == null) {
            root = key;
            return key;
        }
        while (true) {
            parent = current;
            int res = key.val.compareTo(current.val);
            if (res < 0) {
                current = current.left;
                if (current == null) {
                    parent.left = key;
                    return key;
                }
            } else {
                current = current.right;
                if (current == null) {
                    parent.right = key;
                    return key;
                }
            }
        }
    }

    /**
     * 1.找到刪除節點
     * 2.如果刪除節點左節點爲空 , 右節點也爲空;
     * 3.如果刪除節點只有一個子節點 右節點 或者 左節點
     * 4.如果刪除節點左右子節點都不爲空
     * @param key
     * @return
     */
    public Node delete(Node key) {
        Node parent = root;
        Node current = root;
        boolean isLeft = false;
        //找到待刪除節點,以及是否是左子樹
        while (!key.val.equals(current.val)) {
            parent = current;
            int res = key.val.compareTo(current.val);
            if (res < 0) {
                isLeft = true;
                current = current.left;
            } else {
                isLeft = false;
                current = current.right;
            }
            if (current == null) return null;
        }        //如果刪除的左右節點都爲空
        if (current.left == null && current.right == null) {
            if (current == root)
                root = null;
            //該節點在左子樹
            if (isLeft) parent.left = null;
            else parent.right = null;
        } else if (current.left == null) {
            // 該節點爲右子樹
            if (current == root) root = current.right;
            else if (isLeft) {
                parent.left = current.right;
            } else {
                parent.right = current.right;
            }
        } else if (current.right == null) {
            if (current == root) root = current.left;
            else if (isLeft) parent.right = current.left;
            else parent.left = current.left;
        } else if (current.right != null && current.left != null) {
            Node successor = getDeleteSuccessor(current);
            if (current == root) {
                root = successor;
            } else if (isLeft) {
                parent.left = successor;
            } else {
                parent.right = successor;
            }
            successor.left = current.left;
        }
        return current;
    }

    private Node getDeleteSuccessor(Node deleteNode) {
        Node successor = null;
        Node successorParent = null;
        Node current = deleteNode.right;
        while (current != null) {
            successorParent = successor;
            successor = current;
            current = current.left;
        }
        // 檢查後繼者(不可能有左節點樹)是否有右節點樹
        // 如果它有右節點樹,則替換後繼者位置,加到後繼者父親節點的左節點.
        if (!successor.equals(deleteNode.right)) {
            successorParent.left = successor.right;
            successor.right = deleteNode.right;
        }
        return successor;
    }

    public void toString(Node root) {
        if (root != null) {
            toString(root.left);
            System.out.print("value = " + root.val + " -> ");
            toString(root.right);
        }
    }

}

測試類,首先是
Person.java

public class Person implements Comparable<Person> {
    public int age;
    public String name;

    public Person() {
    }

    public Person(int age,String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(age);
    }

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }
}

BSTTest.java

public class BSTTest {

    public static void main(String[] args) {
        BinarySearchTree b = new BinarySearchTree();
        b.insert(new Node(new Person(3, "zhangsan")));
        b.insert(new Node(new Person(8, "lisi")));
        b.insert(new Node(new Person(1, "wangwu")));
        b.insert(new Node(new Person(4, "zhaoer")));
        b.insert(new Node(new Person(6, "qianba")));
        b.insert(new Node(new Person(2, "sunjiu")));
        b.insert(new Node(new Person(10, "guyi")));
        b.insert(new Node(new Person(9, "chenqi")));
        b.insert(new Node(new Person(20, "wuda")));
        b.insert(new Node(new Person(25, "qiao")));
        // 打印二叉樹
        b.toString(b.root);
        System.out.println();
        // 是否存在節點值10
        Node node01 = b.search(new Node(new Person(10, "guyi")));
        System.out.println("是否存在節點值爲10 => " + node01.val);
        // 是否存在節點值11
        Node node02 = b.search(new Node(new Person(11, "ergou")));
        System.out.println("是否存在節點值爲11 => " + node02);
        // 刪除節點8
        Node node03 = b.delete(new Node(new Person(8, "lisi")));
        //Node node03 = b.delete1(b.root, new Node(new Person(8,"lisi")));
        System.out.println("刪除節點8 => " + node03.val);
        b.toString(b.root);
    }
}

運行結果如圖
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章