記事ディレクトリ
再帰的走査
ここでは、再帰アルゴリズムの 3 つの要素を決定するのに役立ちます。再帰を記述するたびに、これら 3 つの要素に従って記述してください。これにより、誰もが正しい再帰アルゴリズムを確実に作成できるようになります。
- 再帰関数のパラメータと戻り値を決定する: 再帰プロセス中にどのパラメータを処理する必要があるかを決定し、このパラメータを再帰関数に追加し、各再帰の戻り値を指定して戻り値の型を決定します。再帰関数の。
- 終了条件を決定する: 再帰的アルゴリズムを作成した後、その実行中にスタック オーバーフロー エラーが発生することがよくあります。つまり、終了条件が書き込まれていないか、終了条件が正しく書かれていません。オペレーティング システムもスタック構造を使用して、再帰の各層の情報を保存します。再帰が終了しない場合、オペレーティング システムのメモリ スタックは必然的にオーバーフローします。
- 単一レベルの再帰のロジックを決定する: 再帰の各レベルで処理する必要がある情報を決定します。ここでは、再帰を実現するために自分自身を繰り返し呼び出すプロセスも繰り返されます。
プレオーダートラバーサル
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
インオーダートラバーサル
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
vec.push_back(cur->val); // 中
traversal(cur->right, vec); // 右
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
ポストオーダートラバーサル
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
vec.push_back(cur->val); // 中
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
シーケンストラバーサル
class Solution {
public:
void order(TreeNode* cur, vector<vector<int>>& result, int depth)
{
if (cur == nullptr) return;
if (result.size() == depth) result.push_back(vector<int>());
result[depth].push_back(cur->val);
order(cur->left, result, depth + 1);
order(cur->right, result, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;
order(root, result, depth);
return result;
}
};
反復処理する
再帰の実現は次のとおりです。各再帰呼び出しはローカル変数、パラメータ値、関数の戻りアドレスをコール スタックにプッシュし、再帰が戻ると、前の再帰のパラメータが先頭からポップされます。スタックなので、これが再帰です。前の位置に戻ることができる理由です。
現時点では、スタックを使用してバイナリ ツリーの前から後ろへの順序どおりの走査を実現できることを誰もが知っておく必要があります。
事前注文トラバーサル (反復)
事前順序トラバーサルは中央と左であり、毎回中央のノードが最初に処理され、次にルート ノードが最初にスタックに置かれ、次に右側の子がスタックに追加され、次に左側の子が追加されます。
lass Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); // 中
st.pop();
result.push_back(node->val);
if (node->right) st.push(node->right); // 右(空节点不入栈)
if (node->left) st.push(node->left); // 左(空节点不入栈)
}
return result;
}
インオーダートラバーサル
順序トラバーサルは左、中、右で、最初にバイナリ ツリーの一番上のノードにアクセスし、次にツリーの左側の一番下に到達するまで層ごとにアクセスしてから開始します。ノードを処理する (つまり、ノードの値を結果配列に入れる) ため、処理順序とアクセス順序が矛盾します。
次に、反復法を使用して順序トラバーサルを記述する場合、ノードへのアクセスを支援するためにポインター トラバーサルを借用する必要があり、ノード上の要素を処理するためにスタックが使用されます。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur != NULL || !st.empty()) {
if (cur != NULL) {
// 指针来访问节点,访问到最底层
st.push(cur); // 将访问的节点放进栈
cur = cur->left; // 左
} else {
cur = st.top();
// 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
st.pop();
result.push_back(cur->val); // 中
cur = cur->right; // 右
}
}
return result;
}
};
ポストオーダートラバーサル
もう一度事後トラバーサルを見てみましょう。前順序トラバーサルは左と右にあり、後続トラバーサルは左と右にあります。その後、次のようになるように前順序トラバーサルのコード順序を調整するだけで済みます。中央、右、左の走査順序を決定し、結果の配列を反転して出力します。結果のシーケンスは、次の図に示すように右と左になります。
したがって、事後トラバーサルでは、事前順序トラバーサルのコードをわずかに変更するだけで済みます。コードは次のとおりです。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
if (node->left) st.push(node->left);
// 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
if (node->right) st.push(node->right); // 空节点不入栈
}
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
シーケンストラバーサル
補助的なデータ構造、つまりキューを借用する必要があります。キューは先入れ先出しであり、レイヤーごとのトラバーサルのロジックに準拠しています。代わりに、スタック先入れ先出しを使用します。 -out は、再帰である深さ優先トラバーサルのロジックをシミュレートするのに適しています。
そして、この層順序走査法は、グラフ理論における幅優先走査法ですが、これを二分木に適用します。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
vector<vector<int>> result;
while (!que.empty()) {
int size = que.size();
vector<int> vec;
// 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
result.push_back(vec);
}
return result;
}
};