1. 程式人生 > >java二叉搜尋樹原理與實現

java二叉搜尋樹原理與實現

計算機裡面的資料結構 樹 在計算機儲存領域應用作用非常大,我之前也多次強調多磁碟的存取速度是目前計算機飛速發展的一大障礙,計算機革命性的的下一次飛躍就是看硬碟有沒有質的飛躍,為什麼這麼說?因為磁碟是永久性儲存裝置(在相當長的時間內都可以用),就這一點雖然記憶體在效能方面優勢巨大但是儲存資訊和資料還是要靠磁碟。

數最成功的要數B+tree和LSM-tree了,在關係型資料庫和非關係型資料庫(Nosql)可謂是處於主導地位,RocksDB目前在nosql和newsql中都大放光彩,其儲存引擎就是LSM-tree,還有hbase,levelDB等。作為樹的變種在儲存領域不斷突破效能的瓶頸。

/**
* 二叉樹的節點
* @author www.mojxtang.website  
*
*/
public class Node {
 
    /**
     * 關鍵字/索引(識別資料用)
     */
    private int id;
    /**
     * 資料項(可以是任意物件T,也可以表示多個數據項)
     */
    private int data;
    
    private Node leftChild;
    private Node rightChild;
    
    public Node(int id, int data) {
        
super(); this.id = id; this.data = data; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getData() { return data; } public void setData(int data) { this.data = data; } public
Node getLeftChild() { return leftChild; } public void setLeftChild(Node leftChild) { this.leftChild = leftChild; } public Node getRightChild() { return rightChild; } public void setRightChild(Node rightChild) { this.rightChild = rightChild; } @Override public String toString() { return "Node [id=" + id + ", data=" + data + ", leftChild=" + leftChild + ", rightChild=" + rightChild + "]"; } }
/**
* 二叉搜尋樹操作(節點的左邊小於節點值,而右邊大於節點值)
* @author www.mojxtang.website 
*
*/
public class BinaryTree {
 
    /**
     * 根節點
     */
    private Node root;
    /**
     * 查詢一個節點
     * @param key 關鍵字 ID值
     * @return
     */
    public Node find(int key) {
        Node current = root;
        while(current.getId() != key) {
            //如果key小於當前節點,就去找左邊比當前小的節點
            if (current.getId() > key) {
                current = current.getLeftChild();
            //如果key大於當前節點,就去找右邊比當前大的節點
            }else if (current.getId() < key) {
                current = current.getRightChild();
            }
            if (current == null) {
                return null;
            }
        }
        return current;
    }
    /**
     * 插入節點
     * @param id
     * @param data
     */
    public void insert(int id,int data) {
        Node newNode = new Node(id, data);
        if (root == null) {
            root = newNode;
        }else {
            //為什麼這裡要存放current=root和parent=null這兩個節點物件?
            Node current = root;
            Node parent = null;
            while (true) {
                parent = current;
                //如果新節點小於當前節點,我們就去左子節點找
                if (id < current.getId()) {//左邊
                    current = current.getLeftChild();
                    //如果沒有左子節點,說明我們找到了,可以將新節點插入到此
                    if (current == null) {
                        parent.setLeftChild(newNode);
                        return;
                    }
                }else {//右邊
                    current = current.getRightChild();
                    //如果沒有右子節點,說明我們找到了,可以將新節點插入到此
                    if (current == null) {
                        parent.setRightChild(newNode);
                        return;
                    }
                }
            }
            
        }
        
    }
    /**
     * 前序---獲取節點資料
     * @param node
     */
    public void preOrder(Node node) {
        if (node !=null) {
            System.out.println(node.getId()+" - ");
            preOrder(node.getLeftChild());
            preOrder(node.getRightChild());
        }
    }
    /**
     * 中序--獲取節點資料
     * @param node
     */
    public void inOrder(Node node) {
        if (node != null) {
            inOrder(node.getLeftChild());
            System.out.println(node.getId()+" - ");
            inOrder(node.getRightChild());
        }
    }
    /**
     * 後序--獲取節點資料
     * @param node
     */
    public void aftOrder(Node node) {
        if (node != null) {
            aftOrder(node.getLeftChild());
            aftOrder(node.getRightChild());
            System.out.println(node.getId()+" - ");
        }
    }
    /**
     * 獲取最小節點資料(使勁往左邊找)
     * @param node
     */
    public Node getMinNode() {
        Node current = root;
        Node minNode = null;
        while (current != null) {
            minNode = current;
            current = current.getLeftChild();
        }
        return minNode;
    }
    /**
     * 獲取最大節點資料(使勁往右邊找)
     * @param node
     */
    public Node getMaxNode() {
        Node current = root;
        Node maxNode = null;
        while (current != null) {
            maxNode = current;
            current = current.getRightChild();
        }
        return maxNode;
    }
    /**
     * 刪除一個節點(刪除節點有兩個子節點的時候,要用它的中序後繼來代替該節點)
     * 演算法是:找到被刪除節點的右子節點,然後查詢這個右子節點下的最後一個左子節點,
     * 也就是這顆子樹的最小值節點,這就是被刪除節點的中序後繼節點。
     * 三種情況:
     *   1.沒有子節點
     *   2.只有一個子節點
     *   3.有兩個子節點
     * @param key
     * @return
     */
    public boolean delete(int key) {
        //先找到需要刪除的節點
        Node current = root;
        Node parent = root;
        boolean isLeftNode = true;
        while (current.getId() != key) {//沒有找到
            parent = current;
            if (current.getId() > key) {//當前節點大於key,往左找
                isLeftNode = true;
                current = current.getLeftChild();
            }else if (current.getId() < key) {//當前節點小於key,往右找
                isLeftNode = false;
                current = current.getRightChild();
            }
            if (current == null) {
                return false;
            }
        }
        
        //1.沒有子節點
        if (current.getLeftChild() == null && current.getRightChild() == null) {
            this.noChild(parent, current, isLeftNode);
        }
        //2.只有一個節點
        else if (current.getRightChild() == null) {
            this.oneLeftNode(parent, current, isLeftNode);
        }
        else if (current.getLeftChild() == null) {
            this.oneRightNode(parent, current, isLeftNode);
        }
        //3.有兩個子節點
        else {
            //找到中序後繼節點
            Node successor = this.getSuccessor(current);
            if (current == root) {
                root = successor;
            }else {
                if (isLeftNode) {
                    parent.setLeftChild(successor);
                }else {
                    parent.setRightChild(successor);
                }
            }
            //設定後繼節點的左節點
            successor.setLeftChild(current.getLeftChild());
            
        }
        return true;
    }
    /**
     * 找到要刪除節點的中序後繼節點
     * 演算法是:找到被刪除節點的右子節點,然後查詢這個右子節點下的最後一個左子節點,
     *    也就是這顆子樹的最小值節點,這就是被刪除節點的中序後繼節點。
     * @param current
     * @return
     */
    private Node getSuccessor(Node delNode) {
        //這裡為什麼記錄三個節點物件?
        Node successor = delNode;
        Node successorParent = delNode;
        Node current = delNode.getRightChild();
        //查詢最後一個左子節點
        while (current != null) {
            successorParent = successor;
            successor = current;
            current = current.getLeftChild();
        }
        if (successor != delNode.getLeftChild()) {
            successorParent.setLeftChild(successor.getRightChild());
            successor.setRightChild(delNode.getRightChild());
        }
        return successor;
    }
    private void oneRightNode(Node parent,Node current,boolean isLeftNode) {
        if (current == root) {
            root = current.getRightChild();
        }else {
            if (isLeftNode) {
                parent.setLeftChild(current.getRightChild());
            }else {
                parent.setRightChild(current.getRightChild());
            }
        }
    }
    private void oneLeftNode(Node parent,Node current,boolean isLeftNode) {
        if (current == root) {
            root = current.getLeftChild();
        }else {
            //這裡為什麼設定父節點的左右都設定為current的左節點?
            if (isLeftNode) {
                parent.setLeftChild(current.getLeftChild());
            }else {
                parent.setRightChild(current.getLeftChild());
            }
        }
    }
    /**
     * 沒有子節點
     * @param parent
     * @param current
     * @param isLeftNode
     */
    private void noChild(Node parent,Node current,boolean isLeftNode) {
        //如果是根節點
        if (current == root) {
            root = null;
        }else {
            //這裡為什麼把父節點的左右值空?
            if (isLeftNode) {
                parent.setLeftChild(null);
            }else {
                parent.setRightChild(null);
            }
        }
    }
    
    public static void main(String[] args) {
        BinaryTree tree = new BinaryTree();
        tree.insert(6, 212);
        tree.insert(5, 211);
        tree.insert(8, 221);
        tree.insert(3, 321);
        tree.insert(7, 421);
        tree.insert(9, 521);
        
        System.out.println(tree.root.toString());
        
        tree.inOrder(tree.find(6));
        
        System.out.println(tree.getMinNode());
        System.out.println(tree.getMaxNode());
        
        tree.delete(5);
        System.out.println(tree.root.toString());
    }
}

二叉搜尋樹是所有樹結構的開始模型,它最明顯的特性就是節點的左子節點比該節點大,比該節點的右子節點小,形成的樹大家可以想想,我們可以說他“有序”。

大家不妨思考為什麼在計算機裡面會用這樣的資料結構來做索引其實二叉樹是沒有節點的左右大小之分的,那為什麼我們非要把左右節點搞出來個大小呢???

二叉搜尋樹複雜的就是刪除大家看完程式碼會發現,資料的刪除其實就是替換和制空的過程,這個過程有個關鍵性的操作就是存放臨時變數,我在程式碼塊裡面備註的幾個為什麼大家思考思考是不是這個道理。

 

 

文章地址:java二叉搜尋樹原理

 

* 二叉搜尋樹操作(節點的左邊小於節點值,而右邊大於節點值) * @author www.mojxtang.website * */ public class BinaryTree {   /** * 根節點 */ private Node root ; /** * 查詢一個節點 * @param key 關鍵字 ID值 * @return */ public Node find ( int key ) { Node current = root ; while ( current . getId ( ) != key ) { //如果key小於當前節點,就去找左邊比當前小的節點 if ( current . getId ( ) > key ) { current = current . getLeftChild ( ) ; //如果key大於當前節點,就去找右邊比當前大的節點 } else if ( current . getId ( ) < key ) { current = current . getRightChild ( ) ; } if ( current == null ) { return null ; } } return current ; } /** * 插入節點 * @param id * @param data */ public void insert ( int id , int data ) { Node newNode = new Node ( id , data ) ; if ( root == null ) { root = newNode ; } else { //為什麼這裡要存放current=root和parent=null這兩個節點物件? Node current = root ; Node parent = null ; while ( true ) { parent = current ; //如果新節點小於當前節點,我們就去左子節點找 if ( id < current . getId ( ) ) { //左邊 current = current . getLeftChild ( ) ; //如果沒有左子節點,說明我們找到了,可以將新節點插入到此 if ( current == null ) { parent . setLeftChild ( newNode ) ; return ; } } else { //右邊 current = current . getRightChild ( ) ; //如果沒有右子節點,說明我們找到了,可以將新節點插入到此 if ( current == null ) { parent . setRightChild ( newNode ) ; return ; } } }   }   } /** * 前序---獲取節點資料 * @param node */ public void preOrder ( Node node ) { if ( node != null ) { System . out . println ( node . getId ( ) + " - " ) ; preOrder ( node . getLeftChild ( ) ) ; preOrder ( node . getRightChild ( ) ) ; } } /** * 中序--獲取節點資料 * @param node */ public void inOrder ( Node node ) { if ( node != null ) { inOrder ( node . getLeftChild ( ) ) ; System . out . println ( node . getId ( ) + " - " ) ; inOrder ( node . getRightChild ( ) ) ; } } /** * 後序--獲取節點資料 * @param node */ public void aftOrder ( Node node ) { if ( node != null ) { aftOrder ( node . getLeftChild ( ) ) ; aftOrder ( node . getRightChild ( ) ) ; System . out . println ( node . getId ( ) + " - " ) ; } } /** * 獲取最小節點資料(使勁往左邊找) * @param node */ public Node getMinNode ( ) { Node current = root ; Node minNode = null ; while ( current != null ) { minNode = current ; current = current . getLeftChild ( ) ; } return minNode ; } /** * 獲取最大節點資料(使勁往右邊找) * @param node */ public Node getMaxNode ( ) { Node current = root ; Node maxNode = null ; while ( current != null ) { maxNode = current ; current = current . getRightChild ( ) ; } return maxNode ; } /** * 刪除一個節點(刪除節點有兩個子節點的時候,要用它的中序後繼來代替該節點)      * 演算法是:找到被刪除節點的右子節點,然後查詢這個右子節點下的最後一個左子節點,      * 也就是這顆子樹的最小值節點,這就是被刪除節點的中序後繼節點。      * 三種情況:      *   1.沒有子節點      *   2.只有一個子節點      *   3.有兩個子節點 * @param key * @return */ public boolean delete ( int key ) { //先找到需要刪除的節點 Node current = root ; Node parent = root ; boolean isLeftNode = true ; while ( current . getId ( ) != key ) { //沒有找到 parent = current ; if ( current . getId ( ) > key ) { //當前節點大於key,往左找 isLeftNode = true ; current = current . getLeftChild ( ) ; } else if ( current . getId ( ) < key ) { //當前節點小於key,往右找 isLeftNode = false ; current = current . getRightChild ( ) ; } if ( current == null ) { return false ; } }   //1.沒有子節點 if ( current . getLeftChild ( ) == null && current . getRightChild ( ) == null ) { this . noChild ( parent , current , isLeftNode ) ; } //2.只有一個節點 else if ( current . getRightChild ( ) == null ) { this . oneLeftNode ( parent , current , isLeftNode ) ; } else if ( current . getLeftChild ( ) == null ) { this . oneRightNode ( parent , current , isLeftNode ) ; } //3.有兩個子節點 else { //找到中序後繼節點 Node successor = this . getSuccessor ( current ) ; if ( current == root ) { root = successor ; } else { if ( isLeftNode ) { parent . setLeftChild ( successor ) ; } else { parent . setRightChild ( successor ) ; } } //設定後繼節點的左節點 successor . setLeftChild ( current . getLeftChild ( ) ) ;   } return true ; } /** * 找到要刪除節點的中序後繼節點 * 演算法是:找到被刪除節點的右子節點,然後查詢這個右子節點下的最後一個左子節點,      *    也就是這顆子樹的最小值節點,這就是被刪除節點的中序後繼節點。 * @param current * @return */ private Node getSuccessor ( Node delNode ) { //這裡為什麼記錄三個節點物件? Node successor = delNode ; Node successorParent = delNode ; Node current = delNode . getRightChild ( ) ; //查詢最後一個左子節點 while ( current != null ) { successorParent = successor ; successor = current ; current = current . getLeftChild ( ) ; <