數據結構與算法之搜索二叉樹

二叉查找樹(Binary Search Tree),(又:二叉搜索樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 它的左、右子樹也分別爲二叉排序樹。

二叉搜索樹對節點所包含的數據進行了一些約束。使得二叉搜索樹最壞情況下的平均搜索時間複雜度降低至logn 這是我們使用二叉搜索樹的重要原因之一

二叉搜索樹的性質
1.一個節點的左子樹只能包含小於該節點鍵值得節點
2.一個節點的右子樹只能包含大於該節點鍵值得節點
3.左子樹和右子樹也必須是二叉搜索樹

瞭解了這麼多二叉搜索樹的性質 我們開始實現它吧

樹的節點類


//樹的節點
public class TreeNode<T> {
    private T data;
    public TreeNode<T> leftNode;
    public TreeNode<T> rightNode;
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
}

二叉搜索樹的基礎功能實現


//二叉樹基礎功能
//節點的左子節點的值都小於這個節點的值,節點的右子節點的值都大於等於這個節點的值
//參考書籍<<數據庫結構與算法分析>> mark allen weiss 
public abstract class BinaryTree<T extends Comparable<? super T>> {
    // 定義根節點
    private TreeNode<T> root;

    // 初始化二叉樹
    public BinaryTree() {
        root = null;
    }

    // 刪除二叉樹
    public void delete() {
        root = null;
    }

    // 判斷二叉樹是否爲空
    public boolean isEmpty() {
        return root == null;
    }

    // 判斷節點是否在此樹中
    public boolean contain(T x) {
        return contain(x, root);
    }

    private boolean contain(T x, TreeNode<T> root) {
        // 空樹
        if (root == null)
            return false;
        // 節點的左子節點的值都小於這個節點的值,節點的右子節點的值都大於等於這個節點的值
        int compareResult = x.compareTo(root.getData());
        // 若被查找值比節點小 則訪問左節點 反之訪問右節點
        if (compareResult > 0) {
            contain(x, root.rightNode);
        } else if (compareResult < 0)
            contain(x, root.leftNode);
        return true;
    }

    // 查找樹中最大的節點
    public TreeNode<T> findMax() throws Exception {
        if (isEmpty())
            throw new Exception("空樹");
        return findMax(root);
    }

    private TreeNode<T> findMax(TreeNode<T> root) throws Exception {
        // 判斷是否爲空樹
        if (root == null)
            return null;
        // 遞歸出口
        else if (null == root.rightNode)
            return root;
        // 節點的右子節點的值都大於等於這個節點的值
        return findMax(root.rightNode);

    }

    // 查找樹中最小的數節點
    public TreeNode<T> findMin() throws Exception {
        if (isEmpty())
            throw new Exception("空樹");
        return findMin(root);
    }

    private TreeNode<T> findMin(TreeNode<T> root2) {
        // 判斷是否爲空樹
        if (root == null)
            return null;
        // 遞歸出口
        else if (null == root.leftNode)
            return root;
        // 節點的左子節點的值都小於這個節點的值
        return findMin(root.leftNode);
    }

    // 插入數據
    public void insert(T x) {
        root = insert(x, root);
    }

    private TreeNode<T> insert(T x, TreeNode<T> root) {
        // 遞歸出口
        if (null == root) {
            root = new TreeNode<>();
            root.setData(x);
            return root;
        }
        int compareResult = x.compareTo(root.getData());
        // 節點的左子節點的值都小於這個節點的值
        if (compareResult < 0) {
            return insert(x, root.leftNode);
        }
        // 節點的右子節點的值都大於等於這個節點的值
        return insert(x, root.rightNode);

    }

    // 刪除數據
    public void remove(T x) {
        root = remove(x, root);
    }

    private TreeNode<T> remove(T x, TreeNode<T> root) {
        // 不存在x
        if (null == root)
            return root;
        int compareResult = x.compareTo(root.getData());
        if (compareResult < 0)
            remove(x, root.leftNode);
        else if (compareResult > 0)
            remove(x, root.rightNode);
        /*
         * 找到了刪除的節點但是他有兩個子節點 一般的策略是將其右節點的最小值替換這個節點
         */
        else if (root.leftNode != null && root.rightNode != null) {
            // 將右節點的最小值替換當前節點
            root.setData(findMin(root.rightNode).getData());
            // 刪除右邊最小值
            root.rightNode = remove(root.getData(), root.rightNode);
        } else
            // 只有一個子節點的節點
            root = (root.leftNode == null) ? root.rightNode : root.leftNode;
        return root;
    }

    // 遍歷二叉樹
    // 前序遍歷
    public abstract void preOrder(TreeNode<T> root);

    // 中序遍歷
    public abstract void inOrder(TreeNode<T> root);

    // 後序遍歷
    public abstract void postOrder(TreeNode<T> root);
}

二叉樹的遍歷分爲遞歸方法和非遞歸法

二叉搜索樹遞歸遍歷

//遞歸遍歷二叉樹
public class NonRecursiveTraversalBinaryTree<T extends Comparable<? super T>> extends BinaryTree<T> {

    /*
     * 前序遍歷
     * 1.訪問根節點
     * 2.按照前序遍歷的方式遍歷左子樹
     * 3.按照前序遍歷的方式遍歷右字樹
     * @see com.tree.binarytree.BinaryTree#preOrder(com.tree.binarytree.TreeNode)
     */
    @Override
    public void preOrder(TreeNode<T> root) {
        if (null != root) {
            //訪問根節點
            System.out.println(root.getData());
            //訪問左子樹
            preOrder(root.leftNode);
            //訪問右子樹
            preOrder(root.rightNode);           
        }

    }
    /*
     *中序遍歷
     *1.按照中序遍歷的方式遍歷左子樹
     *2.訪問根節點
     *3.按照中序遍歷的方式遍歷右子樹
     * @see com.tree.binarytree.BinaryTree#inOrder(com.tree.binarytree.TreeNode)
     */
    @Override
    public void inOrder(TreeNode<T> root) {
        inOrder(root.leftNode);
        System.out.println(root.getData());
        inOrder(root.rightNode);

    }
    /*
     * 後序遍歷
     * 1.按照後序遍歷的方式遍歷左子樹
     * 2.按照後序遍歷的方式遍歷右子樹
     * 3.訪問根節點
     * @see com.tree.binarytree.BinaryTree#postOrder(com.tree.binarytree.TreeNode)
     */
    @Override
    public void postOrder(TreeNode<T> root) {
        postOrder(root.leftNode);
        postOrder(root.rightNode);
        System.out.println(root.getData());
    }

}

非遞歸遍歷二叉搜索樹

//非遞歸遍歷二叉樹
public class RecursiveTraversalBinaryTree<T extends Comparable<? super T>> extends BinaryTree<T> {
    /*
     * 非遞歸的前序遍歷 在遞歸方法中需要採用一個棧來記錄當前的節點以便完成了左子樹後可以返回到右子樹
     * 
     * @see
     * com.tree.binarytree.BinaryTree#preOrder(com.tree.binarytree.TreeNode)
     */
    @Override
    public void preOrder(TreeNode<T> root) {
        if (root != null) {
            // 創建棧
            Stack<TreeNode<T>> stack = new Stack<>();
            // 利用無限循環來模擬遞歸
            while (true) {
                while (root != null) {
                    // 訪問根節點
                    System.out.println(root.getData());
                    stack.push(root);
                    // 訪問左節點
                    root = root.leftNode;
                }
                // 如果棧爲空退出
                if (stack.isEmpty())
                    break;
                // 彈棧來依次訪問右節點
                root = stack.pop();
                root = root.rightNode;
            }

        }

    }

    @Override
    public void inOrder(TreeNode<T> root) {
        if (root != null) {
            // 創建棧
            Stack<TreeNode<T>> stack = new Stack<>();
            // 利用無限循環來模擬遞歸
            while (true) {
                while (root != null) {
                    stack.push(root);
                    // 訪問左節點
                    root = root.leftNode;
                }
                // 如果棧爲空退出
                if (stack.isEmpty())
                    break;
                // 彈棧來依次訪問右節點
                root = stack.pop();
                // 訪問根節點
                System.out.println(root.getData());

                root = root.rightNode;
            }
        }

    }

    /*
     * 前序和後序的遍歷 當元素出棧後就不需要再次訪問,但是在後序遍歷中每個節點要訪問兩次 這就意味着 在遍歷完左子樹後需要訪問當前節點
     * 之後遍歷完右子樹後還得訪問該當前節點 解決方法 當從棧中出棧一個元素時 檢查這個元素與棧頂元素的右子節點是否相同 若相同 則完成了左右樹的遍歷
     * 此時只需將棧頂元素出棧一次並輸出該節點數據即可
     * 
     * @see
     * com.tree.binarytree.BinaryTree#postOrder(com.tree.binarytree.TreeNode)
     */
    @Override
    public void postOrder(TreeNode<T> root) {
        if (root != null) {
            // 創建棧
            Stack<TreeNode<T>> stack = new Stack<>();
            // 利用無限循環來模擬遞歸
            while (true) {
                if (root != null) {
                    stack.push(root);
                    // 訪問左節點
                    root = root.leftNode;
                } else {
                    // 如果棧爲空退出
                    if (stack.isEmpty())
                        return;
                    // 如果該節點的右節點爲null 則已到底 否則還有右節點沒有訪問
                    if (stack.peek().rightNode == null) {
                        // 則彈棧
                        root = stack.pop();
                        // 訪問節點
                        System.out.println(root.getData());
                        // 如果該節點是父節點的右節點則證明其父節點的左右子樹都訪問完成
                        if (root == stack.peek().rightNode) {
                            root = stack.pop();
                            System.out.println(root.getData());
                        }
                    }
                    // 棧不爲空則訪問其右節點
                    if (!stack.isEmpty())
                        root = stack.peek().rightNode;
                }
            }
        }
    }

    /*
     * 層次遍歷 層次遍歷定義 1.訪問根節點 2.在訪問 i層時將i+1層的結點按照順序保存在隊列中 3.進入下一層並訪問所有節點
     * 4.重複上述操作直至所有層訪問完
     */
    public void LevelOrder(TreeNode<T> root) {
        // 創建隊列
        Queue<TreeNode<T>> queue = new ArrayDeque<>();
        if (root == null)
            return;
        // 利用隊列先進先出的原則來實現層次遍歷
        queue.add(root);
        while (!queue.isEmpty()) {
            //彈出隊列首節點
            TreeNode<T> node = queue.remove();
            System.out.println(node.getData());
            //將節點的左右子節點加入隊列
            if(node.leftNode != null)
                queue.add(node.leftNode);
            if(node.rightNode != null)
                queue.add(node.rightNode);
        }
    }

至此二叉搜索樹的功能基本完成

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