二叉搜尋樹演算法詳解與Java實現

王下邀月熊_Chevalier發表於2016-07-31

二叉查詢樹可以遞迴地定義如下,二叉查詢樹或者是空二叉樹,或者是滿足下列性質的二叉樹:

(1)若它的左子樹不為空,則其左子樹上任意結點的關鍵字的值都小於根結點關鍵字的值。

(2)若它的右子樹不為空,則其右子樹上任意結點的關鍵字的值都大於根節點關鍵字的值。

(3)它的左、右子樹本身又是一個二叉查詢樹。

從效能上來說如果二叉查詢樹的所有非葉子結點的左右子樹的結點數目均保持差不多(平衡),那麼二叉查詢樹的搜尋效能逼近二分查詢;但它比連續記憶體空間的二分查詢的優點是,改變二叉查詢樹結構(插入與刪除結點)不需要移動大段的記憶體資料,甚至通常是常數開銷。二叉查詢樹可以表示按順序序列排列的資料集合,因此二叉查詢樹也被稱為二叉排序樹,並且同一個資料集合可以表示為不同的二叉查詢樹。二叉查詢樹的結點的資料結構定義為:

struct celltype{

    records data; 

    celltype * lchild, * rchild;

}

typedef celltype * BST;

在Java中,節點的資料結構定義如下:

package wx.algorithm.search.bst;

/**

 * Created by apple on 16/7/29.

 */

/**

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

 */

public class Node {

    //存放節點資料

    int data;

    //指向左子節點

    Node left;

    //指向右子節點

    Node right;

    /**

     * @function 預設建構函式

     * @param data 節點資料

     */

    public Node(int data) {

        this.data = data;

        left = null;

        right = null;

    }

}

查詢

而二叉查詢樹的查詢過程為從根結點開始,如果查詢的關鍵字與結點的關鍵字相等,那麼就命中;否則,如果查詢關鍵字比結點關鍵字小,就進入左兒子;如果比結點關鍵字大,就進入右兒子;如果左兒子或右兒子的指標為空,則報告找不到相應的關鍵字。

BST Search(keytype k, BST F){

    //在F所指的二叉查詢樹中查詢關鍵字為k的記錄。若成功,則返回響應結點的指標,否則返回空

    if(F == NULL) //查詢失敗

        return NULL;

    else if(k == F -> data.key){ //查詢成功

        return F;

    }

    else if (k < F -> data.key){ //查詢左子樹

        return Search(k,F -> lchild);    

    }

    else if (k > F -> data.key){ //查詢右子樹

        return Search(k,F -> rchild);

    }

}

插入

把一個新的記錄R插入到二叉查詢樹,應該保證在插入之後不破壞二叉查詢樹的結構性質。因此,為了執行插入操作首先應該查詢R所在的位置。查詢時,仍然採用上述的遞迴演算法。若查詢失敗,則把包含R的結點插在具有空子樹位置,若查詢成功,則不執行插入,操作結束。

void Insert(records R, BST &F){

        //在F所指的二叉查詢樹中插入一個新紀錄R

        if(F == NULL){

             F = new celltype;

             F -> data = R;

             F -> lchild = NULL;

             F -> rchild = NULL;

        }

        else if (R.key < F -> data.key){

             Insert(R,F -> lchild);

            }else if(R.key > F -> data.key){

             Insert(R,F -> rchild);

        }

        //如果 R.key == F -> data.key 則返回

    }

刪除

刪除葉節點

刪除只有一個子節點的內部節點

刪除有兩個子節點的內部節點

如果我們進行簡單的替換,那麼可能碰到如下情況:

因此我們要在子樹中選擇一個合適的替換節點,替換節點一般來說會是右子樹中的最小的節點:

Java 實現

BinarySearchTree的Java版本程式碼參考BinarySearchTree:

package wx.algorithm.search.bst;

/**
 * Created by apple on 16/7/29.
 */

/**
 * @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 wx.algorithm.search.bst;

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

/**
 * Created by apple on 16/7/30.
 */
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();

    }

}

相關文章