二分木(&BST)のプレオーダー、ミドルオーダー、およびポストオーダートラバーサル(再帰と反復)、JavaBSTの構築

プレオーダーミドルオーダーポストオーダートラバーサルについて

ここに画像の説明を挿入
たとえば、上の図の通常のフルノード、A:ルートノード、B:左ノード、C:右ノード、予約注文の順序はABCです(ルートノードが最初で、次に同じレベルが最初に左、次に右)。 ;中央の順序はBAC(左から左、次にルート、最後の右)です。次の順序はBCA(最初に左、次に右、最後のルート)です。
これらは、ルートノード(現在のノード)がアクセスした場所によって名前が付けられ、すべてdfsで実装されます。
フロントオーダー:
左右ルートミドルオーダー:レフトルートライト
アフターオーダー:レフトルートミドル

実用的な再帰的なアイデアで、プレオーダー、ミドルオーダー、ポストオーダーを理解することをお勧めします。
たとえば、真ん中のシーケンス:
新しいノードに到達するたびに、考慮しなければならないことの1つは、左ルート右です。現在のノードがルートです。
最初に左を見つける必要があります。(または、現在のトラバースにアクセスするために左右をトラバースしている場合これは一部をよりよく理解する可能性があります)
したがって、そのようなバイナリツリーの面:
ここに画像の説明を挿入
プレオーダートラバーサル:ABCDEFGHK

中次走査:BDCAEHGKF

注文後のトラバーサル:DCBHKGFEA

Javaコードの実装-再帰

プロローグ:

void dfs(TreeNode root) {
    
    
    visit(root);
    dfs(root.left);
    dfs(root.right);
}

ミドルオーダー:

void dfs(TreeNode root) {
    
    
    dfs(root.left);
    visit(root);
    dfs(root.right);
}

シーケンス後:

void dfs(TreeNode root) {
    
    
    dfs(root.left);
    dfs(root.right);
    visit(root);
}

具体例:LeetCode144バイナリツリープレオーダートラバーサル
トピックの詳細:バイナリツリーのルートノードルートを指定し、そのノード値のプレオーダートラバーサルを返します。

class Solution {
    
    
    public List<Integer> preorderTraversal(TreeNode root) {
    
    
        List<Integer> visited = new LinkedList<>();
        dfs(root,visited);
        return visited;

    }

    public void dfs(TreeNode s, List<Integer> visited){
    
    
        if(s==null){
    
    
            return;
        }
        visited.add(s.val);
        dfs(s.left,visited);
        dfs(s.right,visited);
        return;
    }
}

後続の順序と中間の順序で同様

Javaコードの実装-反復

ここでの反復では、実際にスタックを使用して再帰のアイデアをシミュレートします。
実際、再帰はスタックへの呼び出しでもあるためです。

プロローグ

public List<Integer> preorderTraversal(TreeNode root) {
    
    
    List<Integer> visited = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()) {
    
    
        TreeNode s = stack.pop();
        if (s == null) continue;
        visited.add(s.val);
        stack.push(s.right);  // 先右后左,保证左子树先遍历
        stack.push(s.left);
    }
    return visited;
}

ここではスタックが使用され、スタックは先入れ先出しです。
したがって、ここでは非再帰が使用されます。事前注文を確実にするために、現在のノードが最初に追加されます。
左リアを積み重ね、最初の左と右の後なので、ここでは右の最初のスタックであることを確実にするために、その後。左右

ポストオーダーの場合:
プレオーダートラバーサルはルート->左->右であり、ポストオーダートラバーサルは左->右->ルートです。事前注文トラバーサルをルート->右->左に変更できます。この順序は、事後注文トラバーサルとは正反対です。

public List<Integer> postorderTraversal(TreeNode root) {
    
    
    List<Integer> ret = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()) {
    
    
        TreeNode node = stack.pop();
        if (node == null) continue;
        ret.add(node.val);
        stack.push(node.left);
        stack.push(node.right);
    }
    Collections.reverse(ret);
    return ret;
}

中間シーケンス:
これはもう少し面倒です。各ステップで、最初に左側の子ノードが空になるまで左側の子ノードをスタックに配置し、次にアクセスしてから右側の子ノードをスタックに配置します。
ここでのロジックは次のとおりです。
最初にすべての左側をスタックにプッシュし、次に
現在のノードにアクセスしてから
、右側のノードをスタックにプッシュします(プッシュがある場合、以下のコードの右側のノードはスタックにプッシュされません。ここで、whileの上部はまだスタックから外れている必要があります。これにより、時間の複雑さが大幅に増加します。必要ありません。whileの下部と上部で使用されるノードは同じノードであるため、オーバーヘッドを増やすには、スタックしてポップする必要があります。ただし、スタックとして理解できます)

class Solution {
    
    
    public List<Integer> inorderTraversal(TreeNode root) {
    
    
        List<Integer> ret = new ArrayList<>();
        if (root == null) return ret;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur != null || !stack.isEmpty()) {
    
    
            while (cur != null) {
    
    
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode node = stack.pop();
            ret.add(node.val);
            cur = node.right;
        }
        return ret;
    }
}

二分木のモリストラバーサル

上記の再帰的トラバーサルを実装するには、実際にはスタックに依存する必要があります。Javaでは、仮想マシンスタックのjvmの制限により、データ量が多い場合にスタックがオーバーフローします。比較的大きいので、時々、複雑なスペースが必要になります。トラバースする方法は小さくなります。
ここでのモリストラバーサルは、O(1)空間の複雑さと引き換えに、特定の時間の複雑さが犠牲になることを意味します。
再帰では、スタックに依存して、下部の要素が上部の要素として返されるため、ここにはスタックがありません。
代わりに、巧妙な方法が採用されています。つまり、この時点で、左側のサブツリーの右端のポイントがこのポイントを指しているため、当然、スタックから上位の要素をポップする操作をシミュレートして、スタックに戻ることができます。上部要素。(これはモリスでは手がかりの設定と呼ばれます)
次に、訪問後に手がかりを削除する必要があります。これは、スタックからポップされ、訪問後に破棄された要素と同じであり、バイナリツリーの変更を回避できます。 。
次に、前のトラバーサルでは、プレオーダーはポイントごとに1回トラバースされて印刷され、ミドルオーダーは2回トラバースされて印刷され、ポストオーダーは3回トラバースされて印刷されます。モリスはこれを真似しました。
pre-intermediate post-sequenceのmorris実装では、最初の2つの実装は比較的単純です。問題は見られず、使用することもできます。post-sequenceは少し扱いに​​くいため、リンクリストを反転する必要があります。 。最初にここにマークを付けます。アプリケーションのシナリオは比較的多くなります。必要なときに戻って理解してください。
まず、時間を節約するためによく書かれていると思われる2つのブログを配置し
ます
。https //blog.csdn.net/wdq347/article/details/8853371 https://blog.csdn.net/pcwl1206/article/details/96977808

二分探索木BSTの場合、順序付けられたシーケンスを取得するための順序どおりのトラバーサル

タイトルのように、
ここでのBSTの特徴は、左側のサブツリーのすべての値<=ルートノードの値<=右側のサブツリーの値です。

BST(バランス)の作成

バランスは毎回中間点を見つけることにあるので、バランスが取れていることが保証されます(左右のサブツリーの深さの差は1以下です)。
実際、私はそうではないBSTを構築したいと思います。バランスが取れていることが保証されます。間隔全体の中央の値をポイントとして選択するだけです。値で十分です。左側のサブツリーは間隔内のポイントの左側、右側のサブツリーはポイント内のポイントの右側です。間隔。このようなロジックは、ルートノードが左側のサブツリーのすべての値以上であり、右側のサブツリーのすべての値以下であることを保証し、それによってBSTを構築します。
また、値を選択するたびに中央の位置のみを考慮すると、各ラウンドで、左右のサブツリーの数の差が1を超えず、最終的にバランスが取れることを意味します。
(同時に、ここでmedium = l +(rl)/ 2を使用する目的は、オーバーフローを防ぐことです。(r + l)/ 2を使用すると、r + lがオーバーフローする可能性があり、rlがオーバーフローすることはありません)

class Solution {
    
    
    public TreeNode sortedArrayToBST(int[] nums) {
    
    
        return dfs(nums,0,nums.length-1);

    }

    public TreeNode dfs(int[] nums,int l, int r){
    
    
        if(l>r){
    
    
            return null;
        }
        int medium = l+(r-l)/2;
        TreeNode node = new TreeNode(nums[medium]);
        node.left = dfs(nums,l,medium-1);
        node.right = dfs(nums,medium+1,r);
        return node;
    }
}

参照

https://blog.csdn.net/qq_33243189/article/details/80222629
LeetCode

おすすめ

転載: blog.csdn.net/qq_34687559/article/details/109369006