データ構造の復習: 10 日目

目次

 1. バイナリツリーの事前順序走査

1. 再帰

アイデアとアルゴリズム

複雑さの分析

2. 反復

アイデアとアルゴリズム

複雑さの分析

3.モリストラバーサル

アイデアとアルゴリズム

複雑さの分析

2. 二分木の順序通りの走査

 1. 再帰

アイデアとアルゴリズム

複雑さの分析

3. モリスの順序トラバーサル

アイデアとアルゴリズム

複雑さの分析

3. 二分木の事後ソート

1. 再帰

アイデアとアルゴリズム

複雑さの分析

2. 反復

複雑さの分析

3.モリストラバーサル

アイデアとアルゴリズム

複雑さの分析

 1. バイナリツリーの事前順序走査

144. バイナリ ツリーの事前順序トラバーサル - LeetCode https://leetcode.cn/problems/binary-tree-preorder-traversal/?plan=data-structions&plan_progress=ggfacv7

1. 再帰

アイデアとアルゴリズム

まず第一に、バイナリ ツリーの事前順序トラバーサルとは何かを理解する必要があります: ルート ノード - 左サブツリー - 右サブツリーと同じ方法でツリーをトラバースします。左サブツリーまたは右サブツリーにアクセスするときは、同じ方法に従います。ツリー全体を横断するまで横断します。したがって、走査プロセス全体は当然再帰的であり、再帰関数を直接使用してこのプロセスをシミュレートできます。

preorder(root) を定義して、現在ルート ノードに到達している回答を表します。定義によれば、最初にルート ノードの値を答えに追加し、次に preorder(root.left) を再帰的に呼び出してルート ノードの左側のサブツリーを走査し、最後に preorder(root.right) を再帰的に呼び出すだけです。ルート ノードの右側のサブツリーをトラバースする場合、つまり [はい] の場合、再帰終了の条件は空のノードに遭遇することです。

class Solution {
public:
    void preorder(TreeNode *root, vector<int> &res) {
        if (root == nullptr) {
            return;
        }
        res.push_back(root->val);
        preorder(root->left, res);
        preorder(root->right, res);
    }

    vector<int> preorderTraversal(TreeNode *root) {
        vector<int> res;
        preorder(root, res);
        return res;
    }
};

複雑さの分析

時間計算量: O(n)、n はバイナリ ツリーのノードの数です。各ノードは 1 回だけ通過されます。

空間計算量: O(n)、再帰的プロセス中のスタックのオーバーヘッドです。平均的な場合、O(logn) です。最悪の場合、ツリーはチェーン状になり、O(n) になります。 。

2. 反復

アイデアとアルゴリズム

反復を使用して、メソッド 1 の再帰関数を実装することもできます。この 2 つのメソッドは同等です。違いは、再帰中にスタックが暗黙的に維持され、反復中にこのスタックを明示的にシミュレートする必要があることです。実装の残りの部分と詳細は同じです。詳細については、以下のコードを参照してください。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        if (root == nullptr) {
            return res;
        }

        stack<TreeNode*> stk;
        TreeNode* node = root;
        while (!stk.empty() || node != nullptr) {
            while (node != nullptr) {
                res.emplace_back(node->val);
                stk.emplace(node);
                node = node->left;
            }
            node = stk.top();
            stk.pop();
            node = node->right;
        }
        return res;
    }
};

複雑さの分析

時間計算量: O(n)、nn はバイナリ ツリーのノードの数です。各ノードは 1 回だけ通過されます。

空間計算量: O(n)。反復プロセス中の明示的なスタックのオーバーヘッドです。平均的な場合、O(logn) です。最悪の場合、ツリーはチェーン状になり、O(n )。

3.モリストラバーサル

アイデアとアルゴリズム

線形時間で一定のスペースのみを占有するプリオーダー トラバーサルを実装する賢い方法があります。この方法は、JH Morris によって 1979 年の論文「Traversing Binary Trees Simply and Couldaly」で初めて提案されたため、Morris トラバーサルと呼ばれています。

Morris トラバーサルの中心となるアイデアは、ツリー内の多数の空きポインタを利用して、スペースのオーバーヘッドを究極的に削減することです。その事前注文トラバーサル ルールは次のように要約されます。

新しい一時ノードを作成し、そのノードをルートにします。

現在のノードの左側の子ノードが空の場合は、現在のノードを回答に追加し、現在のノードの右側の子ノードをトラバースします。

現在のノードの左側の子ノードが空でない場合は、現在のノードの左側のサブツリーで順序トラバーサルの下で現在のノードの先行ノードを検索します。

先行ノードの右の子ノードが空の場合は、先行ノードの右の子ノードを現在のノードに設定します。次に、現在のノードを回答に追加し、先行ノードの右側の子ノードを現在のノードに更新します。現在のノードは、現在のノードの左側の子ノードに更新されます。

先行ノードの右の子ノードが現在のノードである場合、その右の子ノードを空にリセットします。現在のノードは、現在のノードの右側の子ノードに更新されます。

トラバースが完了するまで、ステップ 2 と 3 を繰り返します。

このようにして、モリス走査法を使用してバイナリ ツリーを事前順序で走査し、線形時間および一定空間の走査を実現できます。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode *root) {
        vector<int> res;
        if (root == nullptr) {
            return res;
        }

        TreeNode *p1 = root, *p2 = nullptr;

        while (p1 != nullptr) {
            p2 = p1->left;
            if (p2 != nullptr) {
                while (p2->right != nullptr && p2->right != p1) {
                    p2 = p2->right;
                }
                if (p2->right == nullptr) {
                    res.emplace_back(p1->val);
                    p2->right = p1;
                    p1 = p1->left;
                    continue;
                } else {
                    p2->right = nullptr;
                }
            } else {
                res.emplace_back(p1->val);
            }
            p1 = p1->right;
        }
        return res;
    }
};

複雑さの分析

時間計算量: O(n)、nn はバイナリ ツリーのノードの数です。左サブツリーのないノードは 1 回だけ訪問され、左サブツリーのあるノードは 2 回訪問されます。

空間複雑度: O(1)。すでに存在するポインター (ツリーの空きポインター) のみを操作するため、一定量の追加スペースのみが必要です。

2. 二分木の順序通りの走査

94. バイナリ ツリーの順序トラバーサル - LeetCode https://leetcode.cn/problems/binary-tree-inorder-traversal/?plan=data-structions&plan_progress=ggfacv7

 1. 再帰

アイデアとアルゴリズム

まず第一に、バイナリ ツリーの順序トラバーサルとは何かを理解する必要があります。左のサブツリー - ルート ノード - 右のサブツリーと同じ方法でツリーをトラバースし、いつ左のサブツリーまたは右のサブツリーにアクセスするかを理解する必要があります。も同様の方法で、ツリー全体が走査されるまで走査します。したがって、走査プロセス全体は当然再帰的であり、再帰関数を直接使用してこのプロセスをシミュレートできます。

現在ルート ノードに到達している答えを表すように inorder(root) を定義します。定義によれば、必要なのは inorder(root.left) を再帰的に呼び出してルート ノードの左側のサブツリーを走査し、その値を追加することだけです。ルート ノードから答えを取得し、inorder を再帰的に呼び出します (root.right)。ルート ノードの右のサブツリーをトラバースします。再帰的終了の条件は、空のノードに遭遇することです。

class Solution {
public:
    void inorder(TreeNode* root, vector<int>& res) {
        if (!root) {
            return;
        }
        inorder(root->left, res);
        res.push_back(root->val);
        inorder(root->right, res);
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        inorder(root, res);
        return res;
    }
};

複雑さの分析

時間計算量: O(n)、nn はバイナリ ツリー ノードの数です。バイナリ ツリー トラバーサルでは、各ノードは 1 回だけ訪問されます。

空間の複雑さ: O(n)。空間の複雑さは再帰的なスタックの深さに依存し、バイナリ ツリーがチェーンの場合、スタックの深さは O(n) レベルに達します。

2. 反復

反復を使用してメソッド 1 の再帰関数を実装することもできます。この 2 つのメソッドは同等です。違いは、再帰中にスタックが暗黙的に維持されることですが、反復中にこのスタックを明示的にシミュレートする必要があることです。その他はすべて同じです。具体的な実装は以下のコードで確認できます。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> stk;
        while (root != nullptr || !stk.empty()) {
            while (root != nullptr) {
                stk.push(root);
                root = root->left;
            }
            root = stk.top();
            stk.pop();
            res.push_back(root->val);
            root = root->right;
        }
        return res;
    }
};

複雑さの分析

時間計算量: O(n)、nn はバイナリ ツリー ノードの数です。バイナリ ツリー トラバーサルでは、各ノードは 1 回だけ訪問されます。

空間の複雑さ: O(n)。空間の複雑さはスタックの深さに依存し、バイナリ ツリーがチェーンの場合、スタックの深さは O(n) レベルに達します。

3. モリスの順序トラバーサル

アイデアとアルゴリズム

Morris トラバーサル アルゴリズムは、バイナリ ツリーをトラバースするためのもう 1 つの方法であり、非再帰的な順序トラバーサルの空間複雑さを O(1) に削減できます。

モリス走査アルゴリズムの全体的な手順は次のとおりです (現在走査されているノードが x であると仮定します)。

1. x に左の子がない場合、まず x の値を応答配列に追加し、次に x の右の子、つまり x =x.right にアクセスします。 2. x に左の子がある場合、右端のノードを見つけます
。 x の左側のサブツリー (つまり、順序トラバーサルにおける左側のサブツリーの最後のノード、順序トラバーサルにおける x の先行ノード) 上で、それを先行ノードとして記録します。\textit{predecessor}predecessor の右側の子が空かどうかに応じて、次の操作を実行します。

                先行者の右の子が空の場合は、その右の子を x にポイントし、次に x の左の子、つまり x =x.left にアクセスします。
                先行者の右の子が空でない場合、その右の子はこの時点で x を指します。これは、x の左側のサブツリーを走査したことを示します。先行者の右の子を空のままにし、x の値を配列を回答し、x にアクセスします。右側の子は x=x.right です。

完全なツリーにアクセスするまで、上記の操作を繰り返します。

実際、プロセス全体で必要な作業はあと 1 つだけです。現在通過しているノードが x であると仮定し、x の左側のサブツリーにある右端のノードの右側の子をポイントし、このポインタを通じて通過したことを知ることができます。左側のサブツリーをスタックを通じて維持する必要がなく、スタックのスペースの複雑さが解消されます。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        TreeNode *predecessor = nullptr;

        while (root != nullptr) {
            if (root->left != nullptr) {
                // predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
                predecessor = root->left;
                while (predecessor->right != nullptr && predecessor->right != root) {
                    predecessor = predecessor->right;
                }
                
                // 让 predecessor 的右指针指向 root,继续遍历左子树
                if (predecessor->right == nullptr) {
                    predecessor->right = root;
                    root = root->left;
                }
                // 说明左子树已经访问完了,我们需要断开链接
                else {
                    res.push_back(root->val);
                    predecessor->right = nullptr;
                    root = root->right;
                }
            }
            // 如果没有左孩子,则直接访问右孩子
            else {
                res.push_back(root->val);
                root = root->right;
            }
        }
        return res;
    }
};

複雑さの分析

時間計算量: O(n)、nn は二分探索ツリー内のノードの数です。モリス トラバーサルの各ノードは 2 回訪問されるため、合計の時間計算量は O(2n)=O(n) になります。

空間複雑度: O(1)。

3. 二分木の事後ソート

145. バイナリ ツリーのポストオーダー トラバーサル - LeetCode https://leetcode.cn/problems/binary-tree-postorder-traversal/?plan=data-structions&plan_progress=ggfacv7

1. 再帰

アイデアとアルゴリズム

まず第一に、バイナリ ツリーのポストオーダー トラバーサルとは何かを理解する必要があります: 左のサブツリー - 右のサブツリー - ルート ノードと同じ方法でツリーをトラバースします。左のサブツリーまたは右のサブツリーにアクセスするときは、同じ方法に従い、ツリー全体を走査するまで走査します。したがって、走査プロセス全体は当然再帰的であり、再帰関数を直接使用してこのプロセスをシミュレートできます。

現在ルート ノードに到達している回答を表す postorder(root) を定義します。定義によれば、postorder(root->left) を再帰的に呼び出してルート ノードの左側のサブツリーを走査し、次に postorder(root->right) を再帰的に呼び出してルート ノードの右側のサブツリーを走査するだけで済み、最後にルート ノードの値を答えに追加します。 以上です。再帰終了の条件は、空のノードに遭遇することです。

class Solution {
public:
    void postorder(TreeNode *root, vector<int> &res) {
        if (root == nullptr) {
            return;
        }
        postorder(root->left, res);
        postorder(root->right, res);
        res.push_back(root->val);
    }

    vector<int> postorderTraversal(TreeNode *root) {
        vector<int> res;
        postorder(root, res);
        return res;
    }
};

複雑さの分析

時間計算量: O(n)、n は二分探索ツリー内のノードの数です。各ノードは 1 回だけ通過されます。

空間計算量: O(n)、再帰的プロセス中のスタックのオーバーヘッドです。平均的な場合、O(logn) です。最悪の場合、ツリーはチェーン状になり、O(n) になります。 。

2. 反復

反復を使用して、メソッド 1 の再帰関数を実装することもできます。この 2 つのメソッドは同等です。違いは、再帰中にスタックが暗黙的に維持され、反復中にこのスタックを明示的にシミュレートする必要があることです。実装の残りの部分と詳細は同じです。詳細については、以下のコードを参照してください。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode *root) {
        vector<int> res;
        if (root == nullptr) {
            return res;
        }

        stack<TreeNode *> stk;
        TreeNode *prev = nullptr;
        while (root != nullptr || !stk.empty()) {
            while (root != nullptr) {
                stk.emplace(root);
                root = root->left;
            }
            root = stk.top();
            stk.pop();
            if (root->right == nullptr || root->right == prev) {
                res.emplace_back(root->val);
                prev = root;
                root = nullptr;
            } else {
                stk.emplace(root);
                root = root->right;
            }
        }
        return res;
    }
};

複雑さの分析

時間計算量: O(n)、n は二分探索ツリー内のノードの数です。各ノードは 1 回だけ通過されます。

空間計算量: O(n)。反復プロセス中の明示的なスタックのオーバーヘッドです。平均的な場合、O(logn) です。最悪の場合、ツリーはチェーン状になり、O(n )。

3.モリストラバーサル

アイデアとアルゴリズム

線形時間で一定の空間のみを占有するポストオーダートラバーサルを実装する賢い方法があります。この方法は、JH Morris によって 1979 年の論文「Traversing Binary Trees Simply and Couldaly」で初めて提案されたため、Morris トラバーサルと呼ばれています。

Morris トラバーサルの中心となるアイデアは、ツリー内の多数の空きポインタを利用して、スペースのオーバーヘッドを究極的に削減することです。ポストオーダートラバーサルのルールは次のように要約されます。

新しい一時ノードを作成し、そのノードをルートにします。

現在のノードの左側の子ノードが空の場合は、現在のノードの右側の子ノードをトラバースします。

現在のノードの左側の子ノードが空でない場合は、現在のノードの左側のサブツリーで順序トラバーサルの下で現在のノードの先行ノードを検索します。

先行ノードの右の子ノードが空の場合、先行ノードの右の子ノードが現在のノードに設定され、現在のノードが現在のノードの左の子ノードに更新されます。

先行ノードの右の子ノードが現在のノードである場合、その右の子ノードを空にリセットします。現在のノードの左の子ノードから先行ノードまでのパス上のすべてのノードを逆順に出力します。現在のノードは、現在のノードの右側の子ノードに更新されます。

トラバースが完了するまで、ステップ 2 と 3 を繰り返します。

このようにして、モリス トラバーサル法を使用して二分探索ツリーを事後順序でトラバースし、線形時間および一定空間のトラバースを実現できます。

class Solution {
public:
    void addPath(vector<int> &vec, TreeNode *node) {
        int count = 0;
        while (node != nullptr) {
            ++count;
            vec.emplace_back(node->val);
            node = node->right;
        }
        reverse(vec.end() - count, vec.end());
    }

    vector<int> postorderTraversal(TreeNode *root) {
        vector<int> res;
        if (root == nullptr) {
            return res;
        }

        TreeNode *p1 = root, *p2 = nullptr;

        while (p1 != nullptr) {
            p2 = p1->left;
            if (p2 != nullptr) {
                while (p2->right != nullptr && p2->right != p1) {
                    p2 = p2->right;
                }
                if (p2->right == nullptr) {
                    p2->right = p1;
                    p1 = p1->left;
                    continue;
                } else {
                    p2->right = nullptr;
                    addPath(res, p1->left);
                }
            }
            p1 = p1->right;
        }
        addPath(res, root);
        return res;
    }
};

複雑さの分析

時間計算量: O(n)、n はバイナリ ツリーのノードの数です。左サブツリーのないノードは 1 回だけ訪問され、左サブツリーのあるノードは 2 回訪問されます。

空間複雑度: O(1)。すでに存在するポインター (ツリーの空きポインター) のみを操作するため、一定量の追加スペースのみが必要です。

おすすめ

転載: blog.csdn.net/m0_63309778/article/details/126831933