モリストラバーサル
モリス トラバーサルとも呼ばれるモリス トラバーサルは、スレッド化されたバイナリ ツリーを使用して実装されるバイナリ ツリー トラバーサル メソッドであり、スタックや再帰を使用せずにバイナリ ツリーのトラバーサルを完了することができます。Morris traversal メソッドの中心的なアイデアは、各ノードに格納されている親ノードへのポインターを使用して、左サブツリーの右端のノードの親ノードへのポインターを現在のノードに向けることです。現在のノードのサブツリー 、親ノードへのこのポインターを介して現在のノードに戻ることができます。このように、追加のスペースは必要なく、スペース複雑度 O(1) のトラバーサル アルゴリズムが実現されます。
モリス予約注文トラバーサル
モリス トラバーサル プレオーダー traversal_哔哩哔哩_bilibili
最初のステップ: 現在のノードの左の子が空であるかどうか、空である場合は現在のノードを出力し、現在のノードを現在のノードの右の子に更新し、3 番目のステップに入ります。そうでない場合は 2 番目のステップに入ります.
ステップ 2: 現在のノードの左サブツリーの先行ノードとして、左サブツリーの右端のノードを見つける
a. 先行ノードの右の子が空の場合、先行ノードの右の子を現在のノードにポイントし、現在のノードを出力し、現在のノードの最後で現在のノードの左の子を更新します。 3番目のステップ
b. 先行ノードの右の子が空でない場合 (現在のノード)、先行ノードの右の子を NULL に設定し、現在のノードを現在のノードの右の子に更新して、3 番目のステップに進みます。
ステップ 3: 現在のノードが空でない場合は、ステップ 1 に進みます。そうでない場合、プログラムは終了します。
テスト トピック: 144. 二分木のプリオーダー トラバーサル - LeetCode
再帰コードの使用:
class Solution {
vector<int> res;
public:
vector<int> preorderTraversal(TreeNode* root)
{
dfs(root);
return res;
}
void dfs(TreeNode* root)
{
if(root == nullptr) return;
res.push_back(root->val);
dfs(root->left);
dfs(root->right);
}
};
時間の複雑さ: O(n)
スペースの複雑さ: O(n)
Morris の事前注文トラバーサル コードの例:
class Solution {
vector<int> res;
public:
vector<int> preorderTraversal(TreeNode* root)
{
vector<int> res;
TreeNode* curr = root, * pre = nullptr;
while (curr != nullptr) //第三步:若当前结点不为空,进入第一步;否则程序结束
{
if (curr->left == nullptr) //第一步:判断当前结点的左孩子是否为空
{
res.push_back(curr->val); //输出当前结点
curr = curr->right; //更新当前结点为当前结点的右孩子
}
else //第二步
{
pre = GetPreNode(curr); //获取当前结点的左子树中最右结点
if (pre->right == nullptr) //若前驱结点的右孩子为空
{
pre->right = curr; //将前驱结点的右孩子指向当前结点 建立链接
res.push_back(curr->val); //输出当前结点
curr = curr->left; //当前结点更新尾当前结点的左孩子
}
else //b.若前驱结点的右孩子不为空(当前结点)
{
pre->right = nullptr; //还原 去除链接
curr = curr->right;
}
}
}
return res;
}
TreeNode* GetPreNode(TreeNode* curr)
{
TreeNode* node = curr->left;
while (node->right != nullptr && node->right != curr)
{
node = node->right;
}
return node;
}
};
時間計算量: O(n) 左部分木を持たないノードは 1 回だけ訪れ、左部分木を持つノードは 2 回訪れます。
スペースの複雑さ: O(1)
モリス順トラバーサル
Morris Inorder Traversal_哔哩哔哩_bilibili
最初のステップ: 現在のノードの左の子が空であるかどうか、空である場合は現在のノードを出力し、現在のノードを現在のノードの右の子として更新して 3 番目のステップに進み、そうでない場合は 2 番目のステップに進みます。
ステップ 2: 現在のノードの左サブツリーの先行ノードとして、左サブツリーの右端のノードを見つける
a. 先行ノードの右の子が空の場合、先行ノードの右の子を現在のノードに向け、現在のノードを現在のノードの左の子に更新し、3 番目のステップに進みます。
b. 先行ノードの右の子が空でない場合 (現在のノードである場合)、先行ノードの右の子を NULL に設定し、現在のノードを出力し、現在のノードを現在のノードの右の子に更新します。そして三段目に入る
ステップ 3: 現在のノードが空でない場合は、ステップ 1 に進みます。そうでない場合、プログラムは終了します。
テスト トピック: 94. 二分木の順序通りのトラバーサル - LeetCode
再帰コードの使用:
class Solution {
vector<int> res;
public:
vector<int> inorderTraversal(TreeNode* root)
{
dfs(root);
return res;
}
void dfs(TreeNode* root)
{
if(root == nullptr) return;
dfs(root->left);
res.push_back(root->val);
dfs(root->right);
}
};
時間の複雑さ: O(n)
スペースの複雑さ: O(n)
Morris 順トラバーサル コードの例:
class Solution {
vector<int> res;
public:
vector<int> inorderTraversal(TreeNode* root)
{
vector<int> res;
TreeNode* curr = root, * pre = nullptr;
while (curr != nullptr) //第三步:若当前结点不为空,进入第一步;否则程序结束
{
if (curr->left == nullptr) //第一步:判断当前结点的左孩子是否为空
{
res.push_back(curr->val); //输出当前结点
curr = curr->right; //更新当前结点为当前结点的右孩子
}
else //第二步
{
pre = GetPreNode(curr); //获取当前结点的左子树中最右结点
if (pre->right == nullptr) //a.若前驱结点的右孩子为空
{
pre->right = curr; //将前驱结点的右孩子指向当前结点 建立链接
curr = curr->left; //当前结点更新尾当前结点的左孩子
}
else //b.若前驱结点的右孩子不为空(当前结点)
{
pre->right = nullptr; //还原 去除链接
res.push_back(curr->val); //输出当前结点
curr = curr->right;
}
}
}
return res;
}
TreeNode* GetPreNode(TreeNode* curr)
{
TreeNode* node = curr->left;
while (node->right != nullptr && node->right != curr)
{
node = node->right;
}
return node;
}
};
時間計算量: O(n) 左部分木を持たないノードは 1 回だけ訪れ、左部分木を持つノードは 2 回訪れます。
スペースの複雑さ: O(1)
モリスのポストオーダートラバーサル
モリスポストオーダー traversal_哔哩哔哩_bilibili
新しいダミー ノードを作成します。ノードの左側の子はツリーのルートを指し、現在のノードとしてダミーを使用します。
最初のステップ: 現在のノードの左の子が空であるかどうか、現在のノードを現在のノードの右の子に更新し、3 番目のステップに入り、そうでない場合は 2 番目のステップに入ります。
ステップ 2: 現在のノードの左サブツリーの先行ノードとして、左サブツリーの右端のノードを検索します。
a. 先行ノードの右の子が空の場合、先行ノードの右の子を現在のノードに向け、現在のノードは最後に現在のノードの左の子を更新し、3 番目のステップに入ります。
b. 先行ノードの右の子が空でない (現在のノードである) 場合、現在のノードの左の子と先行ノードの間のパスを逆にし、パス上のすべてのノードを出力します。元の状態。先行ノードの右の子を NULL に設定し、現在のノードの最後で現在のノードの右の子を更新し、3 番目のステップに入ります。
3 番目のステップ: 現在のノードが空でない場合は最初のステップに進み、そうでない場合はプログラムが終了します。
テスト トピック: 145. 二分木のポスト オーダー トラバーサル - LeetCode
再帰コードの使用:
class Solution {
vector<int> res;
public:
vector<int> postorderTraversal(TreeNode* root)
{
dfs(root);
return res;
}
void dfs(TreeNode* root)
{
if(root == nullptr) return;
dfs(root->left);
dfs(root->right);
res.push_back(root->val);
}
};
時間の複雑さ: O(n)
スペースの複雑さ: O(n)
Morris ポストオーダー トラバーサル コードの例:
class Solution {
vector<int> res;
public:
vector<int> postorderTraversal(TreeNode* root)
{
vector<int> res;
TreeNode* Dummy = new TreeNode();
Dummy->left = root;
TreeNode* curr = Dummy, * pre = nullptr;
while (curr != nullptr) //第三步:若当前结点不为空,进入第一步;否则程序结束
{
if (curr->left == nullptr) //第一步:判断当前结点的左孩子是否为空
{
curr = curr->right; //更新当前结点为当前结点的右孩子
}
else //第二步
{
pre = GetPreNode(curr); //获取当前结点的左子树中最右结点
if (pre->right == nullptr) //a.若前驱结点的右孩子为空
{
pre->right = curr; //将前驱结点的右孩子指向当前结点 建立链接
curr = curr->left; //当前结点更新尾当前结点的左孩子
}
else //b.若前驱结点的右孩子不为空(当前结点)
{
ReverseAdd(res, curr->left, pre); //反转当前结点左孩子到前驱结点之间的路径,输出该路径所有结点,再反转恢复原状
pre->right = nullptr; //还原 去除链接
curr = curr->right; //当前结点更新尾当前结点的右孩子
}
}
}
delete Dummy;
return res;
}
TreeNode* GetPreNode(TreeNode* curr)
{
TreeNode* node = curr->left;
while (node->right != nullptr && node->right != curr)
{
node = node->right;
}
return node;
}
void ReverseAdd(vector<int>& nums, TreeNode* begin, TreeNode* end)
{
if (begin == nullptr || end == nullptr) return;
int pos = nums.size();
while (begin != end) //这里注意不使用do while的原因是最后一次调用ReverseAdd的end->right是Dummy,使用do while会越界
{
nums.push_back(begin->val);
begin = begin->right;
}
nums.push_back(begin->val);
begin = begin->right;
reverse(nums.begin() + pos, nums.end());
}
};
時間計算量: O(n) 左部分木を持たないノードは 1 回だけ訪れ、左部分木を持つノードは 2 回訪れます。
スペースの複雑さ: O(1)
モリス逆順トラバーサルの練習
トピック リンク: 538. 二分探索木を累積木に変換する - LeetCode
再帰を使って解く:
class Solution {
public:
int sum = 0;
TreeNode* convertBST(TreeNode* root)
{
if (root != nullptr)
{
convertBST(root->right);
sum += root->val;
root->val = sum;
convertBST(root->left);
}
return root;
}
};
時間の複雑さ: O(n)
スペースの複雑さ: O(n)
Morris 逆インオーダー トラバーサルを使用します。
class Solution {
public:
TreeNode* convertBST(TreeNode* root)
{
int sum = 0;
TreeNode* curr = root, * pre = nullptr;
while (curr != nullptr)
{
if (curr->right == nullptr)
{
curr->val += sum;
sum = curr->val;
curr = curr->left;
}
else
{
pre = GetPreNode(curr);
if (pre->left == nullptr)
{
pre->left = curr;
curr = curr->right;
}
else
{
pre->left = nullptr;
curr->val += sum;
sum = curr->val;
curr = curr->left;
}
}
}
return root;
}
TreeNode* GetPreNode(TreeNode* curr)
{
TreeNode* node = curr->right;
while (node->left != nullptr && node->left != curr)
{
node = node->left;
}
return node;
}
};
時間計算量: O(n) 左部分木を持たないノードは 1 回だけ訪れ、左部分木を持つノードは 2 回訪れます。
スペースの複雑さ: O(1)
まとめ:モリス逆順トラバーサル、モリス順トラバーサルの右をすべて左に置き換え、左をすべて右に置き換えて、トピックに応じて出力条件を変更します。