JSデータ構造とアルゴリズム-Jianzhiは、バイナリツリーアルゴリズムの質問の要約を提供します

❗❗必見の体験

ブロガーのブラッシングの質問の間、基本的にはバイナリツリーに遭遇してもバイナリツリーに遭遇しません。時には午後に質問に参加することもあります。他の人の問題を解決するという考えを理解できたとしても、自分で書いてください。私はそれを一度にブラッシングせず、代わりに最初にバイナリツリーの基本的なアルゴリズムを理解しました。次に、ソードオファーでバイナリツリーに関するすべての質問を選びました。少なくなるほど、崩壊の端まで自分を虐待することになります。 。言うまでもなく、これはさわやかです。今まで基本的な準備が無かったのも不思議ではないので、直接質問バンクに行ってチャレンジしました。

ここで私が言いたいのは、質問をブラッシングする前に、知識の予備力がなければならないということです。たとえば、初期データ構造がそれを理解できなければならない、または基本的なデータ構造にいくつかの重要な点があります。私のようなことを準備しないでください。質問をブラッシングするほど、人生を疑うほど、すべての質問が打撃になります。バイナリツリーの走査を行うと、走査の再帰的な結果がどのように現れるかさえわかりません。このアルゴリズムがバックアップされていても、まだ理解できていません。コードは3行しかないのです。ただ理解してもらえませんか?質問をブラッシングするプロセスにおいて、多くの質問は、トラバーサルが焦点である3行のコードです。

したがって、バイナリツリーの最初のステップは、基本的なアルゴリズムを紙に描くことから始めることです。再帰的な場合は、終了条件から1つずつ戻って、バイナリツリーとスタックの関係、および再帰なしのスタックを理解します。ポータル-バイナリツリーの基本的なアルゴリズム

ディレクトリ

Portal-Niu Ke.comオファーのオファー

二分木構造:

function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
}

質問4:バイナリツリーの再構築

難易度:♡♡

プリミドルオーダー

//pre:[1, 2, 4, 7, 3, 5, 6, 8]
//vin: [4, 7, 2, 1, 5, 3, 8, 6]
function reConstructBinaryTree(pre, vin) {
    let tree = null
    if (pre.length > 1) {
        const root = pre.shift() //从前序遍历头中取出一个的父节点
        const index = vin.indexOf(root)  //父节点位于中序遍历中的位置
        tree = new TreeNode(root)
        tree.left = reConstructBinaryTree(pre.slice(0, index), vin.slice(0, index)) //递归父节点左边的节点
        tree.right = reConstructBinaryTree(pre.slice(index), vin.slice(index + 1))  //递归父节点右边的节点
    } else if (pre.length === 1) {
        tree = new TreeNode(pre[0])
    }
    return tree
}

ポストミドル

//post:[7, 4, 2, 5, 8, 6, 3, 1]
//vin: [4, 7, 2, 1, 5, 3, 8, 6]
function reConstructBinaryTree(post, vin) {
    let tree = null
    if (post.length > 1) {
        const root = post.pop()   //从后序遍历尾中取出一个的父节点
        const index = vin.indexOf(root)  //父节点位于中序遍历中的位置
        tree = new TreeNode(root)
        tree.left = reConstructBinaryTree(post.slice(0, index), vin.slice(0, index)) //递归父节点左边的节点
        tree.right = reConstructBinaryTree(post.slice(index), vin.slice(index + 1))  //递归父节点右边的节点
    } else if (post.length == 1) {
        tree = new TreeNode(post[0])
    }
    return tree
}

質問17:ツリーの部分構造

難易度:♡♡♡♡

下部構造

タイトル:2つのバイナリツリーAとBを入力して、BがAのサブ構造かどうかを判断します。(追記:空のツリーはツリーの下位構造ではないことに同意します)

アイデア:このDoesTreeHaveTree関数は、プレオーダートラバーサルの再帰に少し似ています。親ノードの値の比較を取得し、等しい場合は、左ノードと右ノードの値を比較します。

function HasSubtree(pRoot1, pRoot2) {
    let result = false
    if (pRoot1 != null && pRoot2 != null) {
        if (pRoot1.val == pRoot2.val) { //判断父节点
            result = DoesTreeHaveTree(pRoot1, pRoot2)
        }
        if (!result) {//父节点不满足,看看它左节点是否满足
            result = HasSubtree(pRoot1.left, pRoot2)
        }
        if (!result) {//左节点不满足,从其右节点是否满足
            result = HasSubtree(pRoot1.right, pRoot2)
        }
    }
    return result
}
function DoesTreeHaveTree(pRoot1, pRoot2) {
    if (pRoot2 == null) { //root2比到底了,则一定是子结构
        return true
    }
    if (pRoot1 == null) { //root2还没比完,root1就到底了,则一定不是子结构
        return false
    }
    if (pRoot1.val != pRoot2.val) { //节点值不相等
        return false
    }
    //节点值相等,继续比较它们的左右节点值是否相等
    return DoesTreeHaveTree(pRoot1.left, pRoot2.left) && DoesTreeHaveTree(pRoot1.right, pRoot2.right) 
}

3本の木を反転させる

元のタイトル:バックル572。別の木のサブツリー

function HasSubtree(pRoot1, pRoot2) {
    let result = false
    if (pRoot1 != null && pRoot2 != null) {
        if (pRoot1.val == pRoot2.val) { //判断父节点
            result = DoesTreeHaveTree(pRoot1, pRoot2)
        }
        if (!result) {
            result = HasSubtree(pRoot1.left, pRoot2)
        }
        if (!result) {
            result = HasSubtree(pRoot1.right, pRoot2)
        }
    }
    return result
}

function DoesTreeHaveTree(pRoot1, pRoot2) {
    //同时到达底部null,才是子树
    if (!pRoot2 && !pRoot1) {
        return true
    }
    //此时已经排除了两者都为null的情况,只要有一个为null则不是
    if (!pRoot2 || !pRoot1) {
        return false
    }
    //没到达底部的时候,没有一个为null
    if (pRoot1.val != pRoot2.val) {
        return false
    }
    //节点值相等,继续比较它们的左右节点值是否相等
    return DoesTreeHaveTree(pRoot1.left, pRoot2.left) && DoesTreeHaveTree(pRoot1.right, pRoot2.right)
}

質問18:二分木の鏡像

難易度:♡♡

アイデア:順序トラバーサル、ノードの現在のラウンドの左ノードと右ノードを毎回交換する

function Mirror(root) {
    if (root === null) {
        return
    }
    const temp = root.left
    root.left = root.right
    root.right = temp
    Mirror(root.left)
    Mirror(root.right)
}

質問22:バイナリツリーを上から下に印刷します

難易度:♡♡♡♡♡

アイデア:バイナリツリーの階層トラバーサル(幅優先トラバーサル、キューを使用できます)

function PrintFromTopToBottom(root) {
    // write code here
    let tempTree = []
    let rs = []
    if (root) tempTree.push(root)
    while (tempTree.length) {
        root = tempTree.shift()
        rs.push(root.val)
        if (root.left) tempTree.push(root.left)
        if (root.right) tempTree.push(root.right)
    }
    return rs
}

質問23:二分探索木のポストオーダ走査シーケンス

難易度:♡♡♡♡

問題:整数の配列を入力して、配列が二分探索木をたどった結果であるかどうかを判別します。はいの場合は「はい」、それ以外の場合は「いいえ」を出力します。入力配列内の2つの数値が互いに異なると想定します。

アイデア:法律を見つけてください。ポストオーダートラバーサルの最後のノードはルートノードであり、配列はルートノード値より小さい部分とルートノードより大きい部分に分割できます。その後、再帰的に。例:(3 6 5) (9) 7
重要なのは再帰の終了条件です。最初はsequence.length <= 1、[6、5、9、7]のように、配列の左または右の部分が空であることを無視して、1で十分であると思いました] [6,5]に再帰的である場合、左は[]、右は[6]です。

//sequence:[3, 6, 5, 9, 7]
//sequence:[6, 5, 9, 7]
//sequence:[3, 6, 4, 5, 9, 7]
function VerifySquenceOfBST(sequence) {
    if (sequence.length) {
        return helpVerify(sequence)
    }
    return false
}

function helpVerify(sequence) {
    if (sequence.length <= 1) {//此条件下,递归结束。
        return true
    }
    let index = 0
    const key = sequence[sequence.length - 1]  //后序遍历最后一个是根节点
    while (sequence[index] < key) {   //在数组中查找比根节点小和比根节点大的分界点
        index++
    }
    const pos = index   //记录分界点,此时分界点左边全是小于根节点值的
    while (sequence[index] > key) {   //判断根节点右边是否全部大于根节点值
        index++
    }
    if (index != (sequence.length - 1)) {  //接while
        return false
    }
    //现在有左右两个部分,递归执行
    return helpVerify(sequence.slice(0, pos)) && helpVerify(sequence.slice(pos, sequence.length - 1))
}

質問24:二分木は特定の値を持つパスです

難易度:♡♡♡♡

問題:ルートノードとバイナリツリーの整数を入力し、バイナリツリーのノード値の合計が入力整数であるすべてのパスを出力します。パスは、ツリーのルートノードから始まり、リーフノードに至るパスとして定義されます。(注:戻り値のリストでは、配列の長さが最大の配列が一番上にあります)

思考のトレイン:刻々と変化する宗派横断

function FindPath(root, expectNumber) {
    // write code here
    let result = []   //存放所有满足条件的路径
    if (root) {
        let path = []    //记录当前路径,当当前路劲满足条件的时候,push进result,
        let currentSum = 0   //记录当前路径的和
        isPath(root, expectNumber, path, result, currentSum)
    }
    return result
}

function isPath(root, expectNumber, path, result, currentSum) {
    currentSum += root.val
    path.push(root.val)

    if (currentSum == expectNumber && !root.left && !root.right) { //根结点开始往下一直到叶结点,当前sum等于目标数
        result.push(path.slice(0))  //注意:这里不能直接push(path),数组是引用类型。也可ES6用法:push([...path])
    }

    if (root.left) { //当前root有左节点
        isPath(root.left, expectNumber, path, result, currentSum)
    }

    if (root.right) { //当前root有右节点
        isPath(root.right, expectNumber, path, result, currentSum)
    }

    // 走到底(叶子)了,无论当前路径满不满足条件,都要回退到父节点继续搜索
    path.pop()
}

推論

ツリーのルートノードからリーフノードまでではなく、任意のパスがある場合はどうなりますか?

参照サブツリーとサブ構造

質問26:二分探索木と二重にリンクされたリスト

難易度:♡♡♡

アイデア:ポイントは、ポインタpを使用して前のノードを記録することです。絵を描くことは理解しやすいです。まだトラバースの順番で

function Convert(pRootOfTree) {
    if (!pRootOfTree) return null
    let p = null //指针,记录前一个结点
    p = ConvertSub(pRootOfTree, p)
    let re = p
    while (re.left) {
        re = re.left
    }
    return re
}

function ConvertSub(pNode, p) {
    if (pNode.left) p = ConvertSub(pNode.left, p);

    if (p == null) {
        p = pNode //找到最左端
    } else {
        p.right = pNode
        pNode.left = p
        p = pNode
    }

    if (pNode.right) p = ConvertSub(pNode.right, p);
    return p
}

質問38:二分木の深さ

難易度:♡♡

ツリーの深さは、ルートノード(深さは1)から始まり、上から下にレイヤーごとに累積されます。高さはリーフノード(高さは1)から始まり、下から上にレイヤーごとに累積されます。ツリーの深さと高さは同じですが、ツリーの特定のノードの深さと高さが異なります。

方法1:

function TreeDepth(pRoot) {
    if (!pRoot) return 0;
    var left = 1 + TreeDepth(pRoot.left);
    var right = 1 + TreeDepth(pRoot.right);
    return Math.max(left, right)
}

方法2:

この方法はルートパスから始まり、質問24の調査で使用されます。パスを記録する配列を見つけることです。リーフノードに到達するたびに、現在のパスの長さが計算され、以前のパスの長さと比較されます。次に、親ノードに戻り、他のパスの長さを計算します。

function TreeDepth(pRoot) {
    // write code here
    let longest = 0
    if (pRoot) {
        let path = []
        longest = getTreeDepth(pRoot, path, longest)
    }
    return longest
}

function getTreeDepth(pRoot, path, longest) {
    path.push(pRoot.val)
    if (!pRoot.left && !pRoot.right && path.length > longest) {
        longest = path.length
    }
    if (pRoot.left) {
        longest = getTreeDepth(pRoot.left, path, longest)
    }
    if (pRoot.right) {
        longest = getTreeDepth(pRoot.right, path, longest)
    }
    path.pop()
    return longest
}

質問39:平衡二分木

難易度:♡♡♡

空のツリー、または2つの左と右のサブツリーの高さの差(バランス係数と呼ばれる)が1以下のバイナリソートツリーです。そして、左と右の2つのサブツリーは、バランスのとれた二分木です。

アイデア:バランスのとれた二分木の定義の焦点をしっかりと把握し、右のサブツリーはバランスのとれた二分木です

function IsBalanced_Solution(pRoot) {
    if (pRoot == null) {
        return true
    }
    if (Math.abs(TreeDepth(pRoot.left) - TreeDepth(pRoot.right)) > 1) { 
        return false;
    } else { //当前节点的左右高度差不大于1
        return IsBalanced_Solution(pRoot.left) && IsBalanced_Solution(pRoot.right);//判断左右两个子树都是一棵平衡二叉树吗
    }
}

function TreeDepth(pRoot) {
    if (!pRoot) return 0;
    var left = 1 + TreeDepth(pRoot.left);
    var right = 1 + TreeDepth(pRoot.right);
    return Math.max(left, right)
}

質問57:二分木の次のノード

難易度:♡♡♡

function GetNext(pNode) {
    // write code here
    if (!pNode) {
        return null
    }
    //有右子树的
    if (pNode.right) {
        pNode = pNode.right;
        while (pNode.left) { //下个结点就是其右子树最左边的点
            pNode = pNode.left
        }
        return pNode
    }
    // 没有右子树
    while (pNode.next) { //有父节点
        let p = pNode.next //p指向当前节点的父节点
        if (p.left == pNode) { //直到当前结点是其父节点的左孩子为止
            return p
        }
        pNode = pNode.next
    }
    return null //尾节点
}

質問58:対称二分木

難易度:♡♡♡♡♡

アイデア:以前に行われた再帰はツリーの再帰であり、このツリーの左と右のサブツリーは再帰的になりました

function isSymmetrical(pRoot) {
    // write code here
    if (pRoot == null) {
        return true
    }
    return judge(pRoot.left, pRoot.right)
}

function judge(left, right) {
    // 以下判断是否都走到底
    if (left == null) {
        return right == null
    }
    if (right == null) {
        return false
    }
    // 都未走到底
    if (left.val != right.val)
        return false
    return judge(left.left, right.right) && judge(left.right, right.left)
}

質問59:ジグザグの順序でバイナリツリーを印刷します

難易度:♡♡♡♡

この問題の問題解決方法は、ノードの値がレイヤーの数に応じて左から右に保存されることです。一部の人々(そうです、私です)は、レイヤートラバーサルのコードに取り組んでいます。このステップでは、プッシュが分類され、議論されます。ここでは、左または右を押すと、最終的にハロになりました。

レベルトラバーサルは、1つシフトアウトして、その左と右のノード値にプッシュすることです。ここでは、forループがwhileに追加されています。すばらしいのは、同じレイヤーのノードを処理することです。偶数レイヤーが逆方向出力を必要とする場合でも、レイヤーの順次配列がある限り、配列を逆にするだけで済みます。偶数層のノードを逆方向にトラバースしたいのは誰ですか?

function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
}

function Print(pRoot) {
    if (!pRoot) return []

    let queue = []
    let result = []
    let flag = true //true奇数

    queue.push(pRoot)
    while (queue.length) {
        let tempArr = [] //用来存放当前层所有节点的值
        const len = queue.length //存放当前队列的长度
        for (let i = 0; i < len; i++) {
            let temp = queue.shift();
            tempArr.push(temp.val);
            if (temp.left) {
                queue.push(temp.left);
            }
            if (temp.right) {
                queue.push(temp.right);
            }
        }
        if (!flag) {
            tempArr.reverse();
        }
        flag = !flag;
        result.push(tempArr);
    }
    return result
}

質問60:バイナリツリーを複数行として印刷します

難易度:♡♡♡

タイトル:バイナリツリーを上から下にレイヤー別に印刷します。同じレイヤーのノードが左から右に出力されます。各レイヤーは1行を出力します。

上記の質問から、特定の順序のすべての値に関するコードを逆の順序で削除するだけです。

質問61:バイナリツリーのシリアル化

難易度:♡♡♡♡

この質問は、質問4のバイナリツリーの再構築に焦点を当てて吐き出したいと考えています。

function Serialize(pRoot) { 
    if (!pRoot) {
        res.push('#');
    } else {
        res.push(pRoot.val);
        Serialize(pRoot.left);
        Serialize(pRoot.right);
    }
}

function Deserialize(s) {
    if (res.length < 1) return null;
    let node = null;

    let cur = res.shift();
    if (typeof cur == 'number') {
        node = new TreeNode(cur);
        node.left = Deserialize(s);
        node.right = Deserialize(s);
    }
    return node;
}

質問62:二分探索木のk番目に小さいノード

難易度:♡♡♡♡

アイデア:k番目のノードは、中位走査のK番目のノードです。

代码需要注意的地方,一开始我将KthNodeCore(pRoot,<u>k</u>)放在KthNode外,明明和书本里C的代码一样却通不过。
后来发现还是因为JavaScript基本数据类型的传参问题,每次的p值改变必须得return回上一轮递归才能在上一轮递归中取得最新p值,但是该函数中我们还需要返回目标节点,因此最好的解决办法就是将k放于递归函数的上一级作用域中。

占个坑:用非递归写一下
占个坑:第K大呢?

function KthNode(pRoot, k) {
    // write code here
    if (!pRoot || k <= 0)
        return null

    // 为了能追踪k,应该把KthNodeCore函数定义在这里面,k应该在KthNodeCore函数外面
    function KthNodeCore(pRoot) {
        let target = null
        if (pRoot.left) target = KthNodeCore(pRoot.left)
        if (!target) {
            if (k == 1) target = pRoot
            k--
        }
        if (!target && pRoot.right) target = KthNodeCore(pRoot.right)
        return target
    }
    return KthNodeCore(pRoot)
}

おすすめ

転載: www.cnblogs.com/L-xmin/p/12680699.html