【javascript實現】幾道題目帶你學習二叉搜尋樹

陳小俊發表於2018-11-26

二叉樹大家都知道,二叉搜尋樹滿足以下特徵:

節點的左子樹只包含小於當前節點的數

節點的右子樹只包含大於當前節點的數

所有左子樹和右子樹自身必須也是二叉搜尋樹

二叉搜尋樹也叫二叉排序樹,中序遍歷二叉搜尋樹的結果就是一次遞增的遍歷。

一、二叉搜尋樹的建立

相關題目:leetcode 108.將有序陣列轉換為二叉搜尋樹 [中等]

那麼如何將一個有序陣列轉換為一顆二叉搜尋樹?

二叉搜尋樹的每一個分支的根節點都是他的中間值。根據這個特徵,用二分法來將有序陣列轉換為一顆二叉搜尋樹。

const sortedArrayToBST = nums => {
    // 邊界條件
    if (nums.length === 0) {
        return null;
    } 
    if (nums.length === 1) {
        return new TreeNode(nums[0]);
    }
    // 向下取整得到中間值
    let mid = Math.floor(nums.length / 2);
    let root = new TreeNode(nums[mid]);
    // 遞迴 二分法
    root.left =  sortedArrayToBST(nums.slice(0, mid));
    root.right =  sortedArrayToBST(nums.slice(mid + 1));
    return root;
};
複製程式碼

接下來我們驗證下一棵樹是否滿足二叉搜尋樹。

二、驗證二叉搜尋樹

相關題目:leetcode 98.驗證二叉搜尋樹 [中等]

思路就是,中序遍歷如果滿足遞增的就行。

用一個max作為驗證值的變數,用中序遍歷前面的值和後面的值作比較,一直遞增則滿足二叉搜尋樹。

const isValidBST = root => {
    let isValidBSTFlag = true;
    let max = -Number.MAX_VALUE;
    const orderSearch = root => {
        if (root) {
            orderSearch(root.left);
            if (root.val > max) {
                max = root.val;
            } else {
                isValidBSTFlag = false;
            }
            orderSearch(root.right);
        }
    }
    orderSearch(root);
    return isValidBSTFlag;
};
複製程式碼

上一個非遞迴解法。

非遞迴中序遍歷的思路就是使用棧,將節點的左子樹壓入直到葉節點,然後操作完左子樹跟根節點後再操作右子樹。

迴圈反覆,直到棧空。

const isValidBST = root => {
    if(!root) return true;
    let stack = [];
    let isValidBSTFlag = true;
    let max = -Number.MAX_VALUE;
    while (1) {
        while(root != null){
            stack.push(root);
            root = root.left;
        }
        if (stack.length === 0) break;
        let node = stack.pop();
        if (node.val > max) {
            max = node.val;
        } else {
            isValidBSTFlag = false;
            break;
        }
        root = node.right;
    }
    return isValidBSTFlag;
}
複製程式碼

三、二叉搜尋樹的插入

相關題目:leetcode 701.二叉搜尋樹中的插入操作 [中等]

將值插入二叉搜尋樹,只要樹在插入後仍保持為二叉搜尋樹即可。

思路:找到大於插入節點值的節點,將要插入的節點作為該節點的左子樹。注意細節。

這裡還是用中序遍歷,中序遍歷能很好地解決一種情況,就是要插入的節點值比樹中的所有節點還大。

這種情況,找到樹中最大值的節點,將插入的節點作為該節點的右節點。

沒用遞迴,方便理解。

const insertIntoBST = (root, val) => {
    let stack = [];
    let r = root;
    let node = null;
    while (1) {
        while(root != null) {
            stack.push(root);
            root = root.left;
        }
        if (stack.length === 0) break;
        node = stack.pop();
        // 找到大於插入節點值的節點
        if (node.val > val) {
            let newNode = new TreeNode(val);
            newNode.left = node.left;
            // 這裡是細節
            node.left = newNode;
            break;
        }
        root = node.right;
    }
    // 要插入的節點值比樹中的所有節點還大
    if (val > node.val) {
        node.right = new TreeNode(val);
    }
    return r;
};
複製程式碼

四、二叉搜尋樹的恢復

相關題目:leetcode 99.恢復二叉搜尋樹 [困難]

要求:二叉搜尋樹中的兩個節點被錯誤地交換。請在不改變其結構的情況下,恢復這棵樹。

思路:利用中序遍歷找到錯誤的兩個節點s1,s2。交換這兩個節點。

用一個陣列儲存遍歷的值,如果前一個節點大於後一個節點,則s1肯定是前一個節點,後一個節點不一定是s2,繼續遍歷尋找找到s2。

const recoverTree = root => {
    let res = [];
    let s1 = s2 = null;
    const orderSearch = root => {
        if (root) {
            orderSearch(root.left);
            if (res.length !== 0) {
                if (res[res.length - 1].val > root.val) {
                    // 第一個找到的才是s1
                    !s1 && (s1 = res[res.length - 1]);
                    // 若有第二次,第二次的才是s2
                    s2 = root;
                }
            }
            
            res.push(root)
            orderSearch(root.right);
        }
    }
    orderSearch(root);
    [s1.val, s2.val] = [s2.val, s1.val];
    return root;
};
複製程式碼

總結:

二叉搜尋樹跟排序相關,總是圍繞著中序遍歷進行操作。

遞迴程式碼簡潔但是不好理解,非遞迴相對容易理解一些,兩者效率差不太大,看場景使用。

相關文章