【データ構造】バイナリツリーの定番トピック

1.二分木作成文字列

タイトルの説明を読んだ後、ほとんどの人が私と同じように混乱すると思います。説明を見るまで気づきませんでしたが、

次の 3 つの状況があります。

  1. 左も右も空 -- 省略
  2. 右は空、左は空ではない – 省略
  3. 左は空、右は空ではない – 省略されていません

ここでは、バイナリ ツリーの前順序トラバーサル、順序内トラバーサル、および後順序トラバーサルの概要を示します。前順序の結果は次のとおりです:

ABDEGCF
順序の結果は次のとおりです:
DBGEACFポストオーダーは: DGEBFCA

class Solution {
public:
	string tree2str(TreeNode* root) {
		if (root == nullptr)
		{
			return "";
		}
		string str = to_string(root->val);
		if (root->left || root->right) // 特别注意这个条件
		{
			str += "(";
			str += tree2str(root->left);
			str += ")";
		}
		if (root->right)
		{
			str += "(";
			str += tree2str(root->right);
			str += ")";
		}
		return str;
	}
};

2.二分木のレベル順走査

考え方は大まかに次のようなものです:
キューを作成し、levelSize各層にデータがいくつあるかを記録します。この数値が 0 であれば、この層のデータは完全に送信されたことを意味します。


Out 3 は、9 と 20 を levelSize 2 のキューに入れます。等々。
キューが空でない場合、ループはキューが空になるまで継続します。
コード:

class Solution {
public:
	vector<vector<int>> levelOrder(TreeNode* root) {
		queue<TreeNode*> q;
		int levelSize = 0;
		if (root)
		{
			q.push(root);
			levelSize = 1;
		}
		vector<vector<int>> vv;
		while (!q.empty()) // 如果队列不为空,就继续
		{
			// 通过levelSize控制一层一层的出
			vector<int> v;
			while (levelSize--)
			{
				TreeNode* front = q.front();
				q.pop();
				v.push_back(front->val);
				if (front->left)
				{
					q.push(front->left);
				}
				if (front->right)
				{
					q.push(front->right);
				}
			}
			vv.push_back(v);
			// 更新下一层的个数
			levelSize = q.size();
		}
		return vv;
	}
};

3.二分木の階層順序走査 II


この質問は前の質問と似ていますが、最終的な回答を逆にするだけです。

4.二分木の最も近い共通の祖先



アイデア 1: 共通の祖先の特徴。一方が左側のサブツリーにある場合、もう一方は右側のサブツリーにあります。したがって、このノードは共通の祖先です。

class Solution {
public:
	bool isInTree(TreeNode* root, TreeNode* x) {
		if (root == nullptr) {
			return false;
		}
		return x == root
			|| isInTree(root->left, x)
			|| isInTree(root->right, x);
	}
	TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		if (root == nullptr) {
			return nullptr;
		}
		if (p == root || q == root) {
			return root;
		}
		// 判断p节点是在root的左边还是右边
		bool pInLeft = isInTree(root->left, p);
		bool pInRight = !pInLeft;
		// 判断q节点是在root的左边还是右边
		bool qInLeft = isInTree(root->left, q);
		bool qInRight = !qInLeft;

		if ((pInLeft && qInRight) || (pInRight && qInLeft)) {
			return root;
		}
		// 如果都在左边,则转换为在左树寻找公共祖先
		else if (pInLeft && qInLeft) {
			return lowestCommonAncestor(root->left, p, q);
		}
		else {
			return lowestCommonAncestor(root->right, p, q);
		}
	}
};

アイデア 2:共通の祖先の特徴。一方が左側のサブツリーにあり、もう一方が右側のサブツリーにある場合、私は共通の祖先です。検索
二分木であれば、O(N) に最適化できます。

  1. 1 つはルートより小さく、もう 1 つはルートより大きく、ルートは共通の祖先です
  2. ルートより小さい、再帰的な左ツリー検索
  3. どちらもルートより大きい、再帰的右ツリー検索
    ただし、このトピックのバイナリ ツリーを検索しているわけではないので、それを O(N) に最適化する必要があります。
    ここでは、別の考え方を使用して、パスを見つけることしかできません。 p と q をコンテナに入れてパス交差問題に変換します
class Solution {
public:
	bool getPath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path) {
		if (root == nullptr) {
			return false;
		}
		path.push(root);
		if (root == x) {
			return true;
		}
		if (getPath(root->left, x, path)) {
			return true;
		}
		if (getPath(root->right, x, path)) {
			return true;
		}
		path.pop();
		return false;
	}
	TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		stack<TreeNode*> pPath, qPath;
		getPath(root, p, pPath);
		getPath(root, q, qPath);
		while (pPath.size() != qPath.size()) {
			if (pPath.size() > qPath.size()) {
				pPath.pop();
			}
			else {
				qPath.pop();
			}
		}
		while (pPath.top() != qPath.top()) {
			pPath.pop();
			qPath.pop();
		}
		return pPath.top();
	}
};

上記のコードの鍵は、各ノードのパスを見つけることです。

5.二分探索木と二重リンクリスト


このトピックを見たとき、私たちが最初に考えたのは、すべてのノードを取り出して二重リンク リストに挿入することかもしれません。実際には、それほど単純ではありません。もちろん、私たちが考えることができる質問を作成した人は、次のことを行うことができます。それも考えてください。
このトピックには次の要件があります:

元のツリーを操作する必要があります。

class Solution {
public:
	void inorderTraversal(TreeNode* cur, TreeNode*& prev) {
		if (cur == nullptr) {
			return;
		}
		inorderTraversal(cur->left, prev);
		cur->left = prev;
		if (prev) {
			prev->right = cur;
		}
		prev = cur;
		inorderTraversal(cur->right, prev);
	}
	TreeNode* Convert(TreeNode* pRootOfTree) {
		TreeNode* prev = nullptr;
		inorderTraversal(pRootOfTree, prev);
		TreeNode* head = pRootOfTree;
		while (head && head->left) {
			head = head->left;
		}
		return head;
	}
};


上の写真がその本質です

6.プレオーダーおよびインオーダーのトラバーサル シーケンスからバイナリ ツリーを構築する


class Solution {
public:
	TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int& previ, int inbegin, int inend) {
		if (inbegin > inend) {
			return nullptr;
		}
		TreeNode* root = new TreeNode(preorder[previ]);
		// 分割出左右区间
		int rooti = inbegin;
		while (rooti <= inend) {
			if (inorder[rooti] == preorder[previ]) {
				break;
			}
			else {
				rooti++;
			}
		}
		++previ;
		// [inbegin, rooti - 1], rooti, [rooti + 1, inend]
		root->left = _buildTree(preorder, inorder, previ, inbegin, rooti - 1);
		root->right = _buildTree(preorder, inorder, previ, rooti + 1, inend);
		return root;
	}
	TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
		int i = 0;
		return _buildTree(preorder, inorder, i, 0, inorder.size() - 1);
	}
};

7.バイナリツリーの事前順序走査 (非再帰的)

class Solution {
public:
	vector<int> preorderTraversal(TreeNode* root) {
		stack<TreeNode*> st;
		TreeNode* cur = root;
		vector<int> v;
		while (cur || !st.empty()) {
			// 1. 开始访问一棵树
			// 2. 左路节点
			// 3. 左路节点的右子树
			while (cur) {
				v.push_back(cur->val);
				st.push(cur);
				cur = cur->left;
			}
			// 访问右子树
			TreeNode* top = st.top();
			st.pop();
			// 子问题访问右子树
			cur = top->right;// 这个地方非常重要
		}
		return v;
	}
};

8.バイナリツリーの順序トラバーサル (非再帰的)

class Solution {
public:
	vector<int> inorderTraversal(TreeNode* root) {
		stack<TreeNode*> st;
		TreeNode* cur = root;
		vector<int> v;
		while (cur || !st.empty()) {
			while (cur) {
				st.push(cur);
				cur = cur->left;
			}
			// 栈里面取到左路节点,左路节点的左子树访问完了
			TreeNode* top = st.top();
			st.pop();
			v.push_back(top->val);

			cur = top->right;
		}
		return v;
	}
};

8.二分木の事後走査(非再帰)

class Solution {
public:
	vector<int> postorderTraversal(TreeNode* root) {
		stack<TreeNode*> st;
		TreeNode* cur = root;
		vector<int> v;
		TreeNode* prve = nullptr;
		while (cur || !st.empty()) {
			while (cur) {
				st.push(cur);
				cur = cur->left;
			}
			// 栈里面取到左路节点,左路节点的左子树访问完了
			TreeNode* top = st.top();
			// 右为空或者右已经访问过了,可以访问根节点
			if (top->right == nullptr || top->right == prve) {
				v.push_back(top->val);
				st.pop();
				prve = top;
			}
			else {
				cur = top->right;
			}
		}
		return v;
	}
};

以下は 3 つの非再帰コードの比較です。

おすすめ

転載: blog.csdn.net/qq_63474430/article/details/131216389