二叉排序樹(Java)--一個假程序員的理解

不能忘本,要經常寫點算法
今天來個經典的二叉排序樹
也叫二叉搜索樹,兩年前的記憶有點模糊
有事還得問度娘(下面是鏈接)
二叉排序樹
簡單用遞歸定義下,可以這樣描述
一顆二叉排序樹是滿足下列性質的二叉樹:
(1)若它的左子樹不爲空,則其左子樹上任意結點的關鍵字的值都小於根結點關鍵字的值。
(2)若它的右子樹不爲空,則其右子樹上任意結點的關鍵字的值都大於根節點關鍵字的值。
(3)它的左、右子樹本身又是一個二叉查找樹。
二叉排序樹應該是一顆平衡二叉樹(好像是這麼定義的),意思就是左右節點差不多。
然後他的搜索性能貌似是趨近二分查找(寫到這我就想到0。618比二分快)
但是因爲對數據操作比較方便,比如插入刪除數據,不會做太大的變化,相比連續存儲空間,要少操作很大一段的數據。所以還是很好用的(雖然我現在也沒用到,可能是個假的程序員吧)。
節點的數據結構可以定義爲

package bstSearch;

/**
 * Created by zhangguanlong on 2017/12/14.
 */
/**

 * @function 二叉搜索樹中的節點

 */

public class Node {

    //存放節點數據

    int data;

    //指向左子節點

    Node left;

    //指向右子節點

    Node right;

    /**

     * @function 默認構造函數

     * @param data 節點數據

     */

    public Node(int data) {

        this.data = data;

        left = null;

        right = null;

    }

}

查找
而二叉查找樹的查找過程爲從根結點開始,如果查詢的關鍵字與結點的關鍵字相等,那麼就命中;否則,如果查詢關鍵字比結點關鍵字小,就進入左子樹;如果比結點關鍵字大,就進入右子樹;如果左子樹或右子樹的指針爲空,則報告找不到相應的關鍵字。
插入
把一個新的記錄R插入到二叉查找樹,應該保證在插入之後不破壞二叉查找樹的結構性質。因此,爲了執行插入操作首先應該查找R所在的位置。查找時,仍然採用上述的遞歸算法。若查找失敗,則把包含R的結點插在具有空子樹位置,若查找成功,則不執行插入,操作結束。
刪除
刪除不太好描述
借用一下大神們畫的圖 刪葉子節點 內部節點
刪葉子節點
內部節點
這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述
後兩幅應該是要刪18這個節點,這是有左右根節點的子節點
要在子樹中選擇一個合適的替換節點,替換節點一般來說會是右子樹中的最小的節點。
然後給出樹結構代碼,寫註釋是個好習慣,畢竟菜呢

package bstSearch;

/**
 * Created by zhangguanlong on 2017/12/14.
 */
/**
 * @function 二叉搜索樹的示範代碼
 */
public class BinarySearchTree {

    //指向二叉搜索樹的根節點
    private Node root;

    //默認構造函數
    public BinarySearchTree() {
        this.root = null;
    }

    /**
     * @param id 待查找的值
     * @return
     * @function 默認搜索函數
     */
    public boolean find(int id) {

        //從根節點開始查詢
        Node current = root;

        //當節點不爲空
        while (current != null) {

            //是否已經查詢到
            if (current.data == id) {
                return true;
            } else if (current.data > id) {
                //查詢左子樹
                current = current.left;
            } else {
                //查詢右子樹
                current = current.right;
            }
        }
        return false;
    }

    /**
     * @param id
     * @function 插入某個節點
     */
    public void insert(int id) {

        //創建一個新的節點
        Node newNode = new Node(id);

        //判斷根節點是否爲空
        if (root == null) {
            root = newNode;
            return;
        }

        //設置current指針指向當前根節點
        Node current = root;

        //設置父節點爲空
        Node parent = null;

        //遍歷直到找到第一個插入點
        while (true) {

            //先將父節點設置爲當前節點
            parent = current;

            //如果小於當前節點的值
            if (id < current.data) {

                //移向左節點
                current = current.left;

                //如果當前節點不爲空,則繼續向下一層搜索
                if (current == null) {
                    parent.left = newNode;
                    return;
                }
            } else {

                //否則移向右節點
                current = current.right;

                //如果當前節點不爲空,則繼續向下一層搜索
                if (current == null) {
                    parent.right = newNode;
                    return;
                }
            }
        }
    }

    /**
     * @param id
     * @return
     * @function 刪除樹中的某個元素
     */
    public boolean delete(int id) {

        Node parent = root;
        Node current = root;

        //記錄被找到的節點是父節點的左子節點還是右子節點
        boolean isLeftChild = false;

        //循環直到找到目標節點的位置,否則報錯
        while (current.data != id) {
            parent = current;
            if (current.data > id) {
                isLeftChild = true;
                current = current.left;
            } else {
                isLeftChild = false;
                current = current.right;
            }
            if (current == null) {
                return false;
            }
        }

        //如果待刪除的節點沒有任何子節點
        //直接將該節點的原本指向該節點的指針設置爲null
        if (current.left == null && current.right == null) {
            if (current == root) {
                root = null;
            }
            if (isLeftChild == true) {
                parent.left = null;
            } else {
                parent.right = null;
            }
        }

        //如果待刪除的節點有一個子節點,且其爲左子節點
        else if (current.right == null) {

            //判斷當前節點是否爲根節點
            if (current == root) {
                root = current.left;
            } else if (isLeftChild) {

                //掛載到父節點的左子樹
                parent.left = current.left;
            } else {

                //掛載到父節點的右子樹
                parent.right = current.left;
            }
        } else if (current.left == null) {
            if (current == root) {
                root = current.right;
            } else if (isLeftChild) {
                parent.left = current.right;
            } else {
                parent.right = current.right;
            }
        }

        //如果待刪除的節點有兩個子節點
        else if (current.left != null && current.right != null) {

            //尋找右子樹中的最小值
            Node successor = getSuccessor(current);
            if (current == root) {
                root = successor;
            } else if (isLeftChild) {
                parent.left = successor;
            } else {
                parent.right = successor;
            }
            successor.left = current.left;
        }
        return true;
    }

    /**
     * @param deleleNode
     * @return
     * @function 在樹中查找最合適的節點
     */
    private Node getSuccessor(Node deleleNode) {
        Node successsor = null;
        Node successsorParent = null;
        Node current = deleleNode.right;
        while (current != null) {
            successsorParent = successsor;
            successsor = current;
            current = current.left;
        }
        if (successsor != deleleNode.right) {
            successsorParent.left = successsor.right;
            successsor.right = deleleNode.right;
        }
        return successsor;
    }

    /**
     * @function 以中序順序遍歷樹
     */
    public void display() {
        display(root);
    }

    private void display(Node node) {

        //判斷當前節點是否爲空
        if (node != null) {

            //首先展示左子樹
            display(node.left);

            //然後展示當前根節點的值
            System.out.print(" " + node.data);

            //最後展示右子樹的值
            display(node.right);
        }
    }

}

測試代碼

package bstSearch;

import org.junit.Before;
import org.junit.Test;


/**
 * Created by zhangguanlong on 2017/12/14.
 */
public class BinarySearchTreeTest {

    BinarySearchTree binarySearchTree;

    @Before
    public void setUp() {
        binarySearchTree = new BinarySearchTree();
        binarySearchTree.insert(3);
        binarySearchTree.insert(8);
        binarySearchTree.insert(1);
        binarySearchTree.insert(4);
        binarySearchTree.insert(6);
        binarySearchTree.insert(2);
        binarySearchTree.insert(10);
        binarySearchTree.insert(9);
        binarySearchTree.insert(20);
        binarySearchTree.insert(25);
        binarySearchTree.insert(15);
        binarySearchTree.insert(16);
        System.out.println("原始的樹 : ");
        binarySearchTree.display();
        System.out.println("");

    }

    @Test
    public void testFind() {

        System.out.println("判斷4是否存在樹中 : " + binarySearchTree.find(4));

    }

    @Test
    public void testInsert() {

    }

    @Test
    public void testDelete() {

        System.out.println("刪除值爲2的節點 : " + binarySearchTree.delete(2));
        binarySearchTree.display();

        System.out.println("\n 刪除有一個子節點值爲4的節點 : " + binarySearchTree.delete(4));
        binarySearchTree.display();

        System.out.println("\n 刪除有兩個子節點的值爲10的節點 : " + binarySearchTree.delete(10));
        binarySearchTree.display();

    }

}

還可以吧,長夜漫漫,陪伴我的只有代碼和音樂
好了,不耍(zhuang)帥(bi)了,其實還沒下班。。。

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