首頁 > 軟體

Java資料結構之二元搜尋樹詳解

2022-06-06 10:01:24

前言

今天leetcode的每日一題450是關於刪除二元搜尋樹節點的,題目要求刪除指定值的節點,並且需要保證二元搜尋樹性質不變,做完之後,我覺得這道題將二元搜尋樹特性凸顯的很好,首先需要查詢指定節點,然後刪除節點並且保持二元搜尋樹性質不變,就想利用這個題目講講二元搜尋樹。

二元搜尋樹作為一個經典的資料結構,具有連結串列的快速插入與刪除的特點,同時查詢效率也很優秀,所以應用十分廣泛,例如在檔案系統和資料庫系統一般會採用這種資料結構進行高效率的排序與檢索操作。同時因為實現也簡單,作為一些公司演演算法題入門題目也是常有的事情,所以很需要被掌握哦~

所有原始碼已經放在我的github中,其中包括之前實現演演算法及每日一題,可以檢視Data-Structures-and-Algorithms哦~

性質

二元搜尋樹或者是一棵空樹,或者是具有下列性質的一棵二元樹,如果當前節點具有左子樹,則左子樹上的每一個節點值均小於等於當前節點值,如果當前節點具有右子樹,則右子樹上的每一個節點值均大於等於當前節點值。依據這個性質,當我們前序遍歷二元搜尋樹的時候,得到的序列應該是從小到大的非遞減序列。同時搜尋指定值時,只需要與當前節點比較,根據相對大小在左子樹或者右子樹上進行搜尋。

實現

根據二元搜尋樹的性質我們接下來需要實現插入節點,查詢節點,刪除節點功能。

節點結構

public class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;

    public TreeNode() {
    }

    public TreeNode(int val) {
        this.val = val;
    }

    public TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

初始化

這裡我們假設所有節點值大於0,初始化一個頭節點。ps:對於樹,連結串列這類資料結構,為了使第一個節點操作與其他節點保持一致,方便操作,常見的方法是新增一個額外的頭節點,指向第一個節點。

TreeNode head;
    private void init() {
        //新增一個頭節點
        head = new TreeNode(-1);
    }

插入節點

從頭節點開始我們遍歷二元搜尋樹,如果當前節點值小於等於插入節點值,則插入節點在當前節點的右子樹上,否則在左子樹上,一直深度遍歷知道當前節點的右子樹(左子樹)為空,則插入。

/**
     * 插入新節點,假設新節點均大於0
     * @param val 插入節點值
     * @return 插入的節點
     */
    public TreeNode insert(int val) {
        TreeNode temp = head;
        while (true) {
            if (temp.val < val) {
                //val應該在右子樹上
                if (null != temp.right) {
                    temp = temp.right;
                    continue;
                } else {
                    temp.right = new TreeNode(val);
                    return temp.right;
                }
            }
            //應該在左子樹上
            if (null != temp.left) {
                temp = temp.left;
                continue;
            }
            temp.left = new TreeNode(val);
            return temp.left;
        }
    }

查詢節點

查詢節點的步驟其實在插入節點的時候已經有體現,其實就是將查詢值與當前節點比較,大於當前節點走右子樹,小於當前節點走左子樹,直到值匹配返回節點,或者沒有找到返回null。ps:這裡為了後面方便實現刪除,同時返回了當前節點以及當前節點的父節點,這裡使用了commons-lang3包下的Pair工具。

/**
     * 搜尋節點值
     * @param val
     * @return
     */
    public Pair<TreeNode, TreeNode> find(int val) {
        TreeNode temp = head.right;
        TreeNode parent = head;
        while (null != temp) {
            if (temp.val == val) {
                return Pair.of(temp, parent);
            }
            parent = temp;
            if (temp.val < val) {
                //在右子樹上
                temp = temp.right;
                continue;
            }
            temp = temp.left;
        }
        return null;
    }

刪除節點

刪除節點時候我們需要先找到刪除節點的位置,然後做對應操作。有三種情況:

1.如果刪除的是葉子節點直接刪除

2.如果刪除的節點只有左子樹或者右子樹,則直接將左子樹或者右子樹節點放在刪除節點位置

3.如果刪除節點同時有左子樹和右子樹,則將右子樹節點放在原來節點位置,將左子樹放在右子樹最左邊節點左子樹上(反之將左子樹放在原來節點位置,右子樹放在左子樹最右邊節點右子樹上也可)

/**
     * 1.如果刪除的是葉子節點直接刪除,
     * 2.如果刪除的節點只有左子樹或者右子樹,則直接將左子樹或者右子樹節點放在刪除節點位置
     * 3.如果刪除節點同時右左子樹和右子樹,則將右子樹節點放在原來節點位置,將左子樹放在右子樹最左邊節點左子樹上
     * @param val
     */
    public void delete(int val) {
        //找到刪除節點,刪除節點父節點
        Pair<TreeNode, TreeNode> curAndParent = this.find(val);
        TreeNode cur = curAndParent.getLeft();
        TreeNode parent = curAndParent.getRight();
        //記錄刪除當前節點後,當前節點位置放置哪個節點
        TreeNode changed;
        if (null == cur.left && null == cur.right) {
            changed = null;
        } else if (null != cur.left && null != cur.right) {
            TreeNode tempRight = cur.right;
            while (null != tempRight.left) {
                //找到最左側節點
                tempRight = tempRight.left;
            }
            tempRight.left = cur.left;
            changed = cur.right;
        } else if (null != cur.left) {
            changed = cur.left;
        } else {
            changed = cur.right;
        }
        if (parent.left == cur) {
            parent.left = changed;
            return;
        }
        parent.right = changed;
    }

最後

二元搜尋樹易於實現,思想簡單,被廣泛應用,平均查詢,插入,刪除時間均為O(logn),但是在刪除或者插入節點的過程中,可能因為資料的特點,使得二元搜尋樹極端情況下退化為一棵僅有左子樹或者右子樹的,這時候就跟普通順序查詢無異,時間複雜度變為O(n),因此後面出現了平衡二元搜尋樹,左右子樹高度相差不超過1,通過旋轉將二元樹高度降低,使得查詢、插入、刪除在平均和最壞情況下都是O(logn)。比如常見的AVL自平衡二元搜尋樹,紅黑樹等等。

到此這篇關於Java資料結構之二元搜尋樹詳解的文章就介紹到這了,更多相關Java二元搜尋樹內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com