【アルゴリズム】研究ノート(3)----Morrisの前順トラバーサル、インオーダートラバーサル、ポストオーダートラバーサル(C++コード)

モリストラバーサル

モリス トラバーサルとも呼ばれるモリス トラバーサルは、スレッド化されたバイナリ ツリーを使用して実装されるバイナリ ツリー トラバーサル メソッドであり、スタックや再帰を使用せずにバイナリ ツリーのトラバーサルを完了することができます。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)

まとめ:モリス逆順トラバーサル、モリス順トラバーサルの右をすべて左に置き換え、左をすべて右に置き換えて、トピックに応じて出力条件を変更します。

おすすめ

転載: blog.csdn.net/weixin_50202509/article/details/130033813