七、 二叉树学习1(代码随想录学习)

1. 理论基础

代码随想录:二叉树理论

C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn。而unordered_map、unordered_set,unordered_map、unordered_set底层实现是哈希表。

2. 二叉数的递归遍历

先序遍历

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

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

后序遍历

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

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

中序遍历

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

	vector<int> inorderTraversal(TreeNode* root) {
		vector<int> res;
		inorder(root, res);
		return res;
	}
};

3. 二叉树的迭代遍历

先序遍历

思路: 利用栈,将根结点存入栈中。对栈进行遍历,先将栈顶元素的值加入res数组中,在将右左指针依次放入栈中(**先加右孩子的原因是,先入栈的后出栈,遍历时就会后打印)。遍历完成,即可得到先序遍历的数组。

class Solution {
public:
	vector<int> preorderTraversal(TreeNode* root) {
		vector<int> res;
		stack<TreeNode*> st;
		if (root == nullptr)
			return res;
		st.push(root);
		while (!st.empty()) {
			res.push_back(st.top()->val);
			TreeNode* cur = st.top();
			st.pop(); // 将栈顶元素弹出
			// 判断栈顶元素是否存在左右孩子
			// 先存右孩子,因为栈为先进后出
			if (cur->right != nullptr) st.push(cur->right);
			if (cur->left != nullptr) st.push(cur->left);
			
		}
		return res;
	}
};

中序遍历

思路: 中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。

class Solution {
public:
	vector<int> inorderTraversal(TreeNode* root) {
		vector<int> res;
		stack <TreeNode*> st;
		TreeNode* cur = root;
		while (!st.empty() || cur != nullptr) {
			// 先向深遍历左孩子,直到遍历到底层
			if (cur != nullptr) {
				st.push(cur);  // 将当前指针入栈
				cur = cur->left; // 左
			}
			else {  // 若当前指针为空
				cur = st.top();
				st.pop();
				res.push_back(cur->val); // 中
				cur = cur->right; // 右
			}
		}
		return res;
	}
};

后序遍历

思路: 序遍历是中左右,后序遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了。

class Solution {
public:
	vector<int> postorderTraversal(TreeNode* root) {
		vector<int> res;
		stack<TreeNode*> st;
		if (root == nullptr) return res;
		st.push(root);
		// =》中右左
		while (!st.empty()) {
			TreeNode* cur = st.top();
			st.pop();
			res.push_back(cur->val);
			// 先加左孩子,再加有孩子,遍历时先遍历右孩子再左孩子
			if (cur->left != nullptr) st.push(cur->left);
			if (cur->right != nullptr) st.push(cur->right);
		}
		// 中右左=》左右中
		reverse(res.begin(), res.end());
		return res;
	}
};

4. 二叉树的统一迭代法

思路: 使用空指针标识已被操作过的结点,当遇到空指针时,添加进结果数组。就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。

关键代码:先/中/后序遍历,只需更改语句的顺序即可

if (cur->right != nullptr) st.push(cur->right);
st.push(cur);
st.push(nullptr);
if (cur->left != nullptr) st.push(cur->left);

中序遍历

class Solution {
public:
	vector<int> inorderTraversal(TreeNode* root) {
		vector<int> res;
		stack<TreeNode*> st;
		if (root != nullptr) st.push(root);
		while (!st.empty()) {
			TreeNode* cur = st.top();  // 记录栈顶指针
			if (cur != nullptr) { // 当栈顶指针非空时,添加其左右孩子结点
				st.pop(); // 弹出栈顶结点,防止重复操作
				// 当右孩子非空时,添加进栈
				if (cur->right != nullptr) st.push(cur->right);
				st.push(cur); // 再将中间结点入栈
				st.push(nullptr); // 添加空指针,表示该结点已添加左右孩子
				// 当左孩子非空时,添加进栈
				if (cur->left != nullptr) st.push(cur->left);
			}
			else { // 当遇到空结点时,即碰到表示中间结点已被标识
				st.pop();
				cur = st.top();
				st.pop();
				res.push_back(cur->val); // 将中间结点的值添加
			}
		}
		return res;
	}
};

先序遍历

class Solution {
public:
	vector<int> preorderTraversal(TreeNode* root) {
		vector<int> res;
		stack<TreeNode*> st;
		if (root != nullptr) st.push(root);
		while (!st.empty()) {
			TreeNode* cur = st.top();  // 记录栈顶指针
			if (cur != nullptr) { // 当栈顶指针非空时,添加其左右孩子结点
				st.pop(); // 弹出栈顶结点,防止重复操作
				// 当右孩子非空时,添加进栈
				if (cur->right != nullptr) st.push(cur->right);
				// 当左孩子非空时,添加进栈
				if (cur->left != nullptr) st.push(cur->left);
				st.push(cur); // 再将中间结点入栈
				st.push(nullptr); // 添加空指针,表示该结点已添加左右孩子
			}
			else { // 当遇到空结点时,即碰到表示中间结点已被标识
				st.pop();
				cur = st.top();
				st.pop();
				res.push_back(cur->val); // 将中间结点的值添加
			}
		}
		return res;
	}
};

后序遍历

class Solution {
public:
	vector<int> postorderTraversal(TreeNode* root) {
		vector<int> res;
		stack<TreeNode*> st;
		if (root != nullptr) st.push(root);
		while (!st.empty()) {
			TreeNode* cur = st.top();  // 记录栈顶指针
			if (cur != nullptr) { // 当栈顶指针非空时,添加其左右孩子结点
				st.pop(); // 弹出栈顶结点,防止重复操作
				st.push(cur); // 再将中间结点入栈
				st.push(nullptr); // 添加空指针,表示该结点已添加左右孩子
				// 当右孩子非空时,添加进栈
				if (cur->right != nullptr) st.push(cur->right);
				// 当左孩子非空时,添加进栈
				if (cur->left != nullptr) st.push(cur->left);
			}
			else { // 当遇到空结点时,即碰到表示中间结点已被标识
				st.pop();
				cur = st.top();
				st.pop();
				res.push_back(cur->val); // 将中间结点的值添加
			}
		}
		return res;
	}
};

5. 二叉树的层序遍历

  1. 二叉树的层序遍历
    思路: 利用队列将每个结点的值加入结果中,并将该结点的左右孩子加入队列。对整个队列进行遍历,直至遍历完所有结点。
class Solution {
public:
	vector<vector<int>> levelOrder(TreeNode* root) {
		vector<vector<int>> res;
		queue<TreeNode*> que;
		if (root == NULL) return res;
		que.push(root);
		while (!que.empty()) {
			int size = que.size(); // 使用size确定当前层的结点个数
			vector<int> vec; // 添加当前层的结果
			for (int i = 0; i < size; i++) {
				TreeNode* cur = que.front();
				que.pop();
				vec.push_back(cur->val);// 添加结点的val
				// 将左右孩子入队
				if (cur->left != NULL) que.push(cur->left);
				if (cur->right != NULL) que.push(cur->right);
			}
			res.push_back(vec);
		}
		return res;
	}
};

时间复杂度: O(n)
空间复杂度: O(n)

  1. 二叉树的层序遍历Ⅱ
    思路: 将层序遍历的结果直接反转即可
class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        vector<vector<int>> res;
		queue<TreeNode*> que;
		if (root == NULL) return res;
		que.push(root);
		while (!que.empty()) {
			int size = que.size(); // 使用size确定当前层的结点个数
			vector<int> vec; // 添加当前层的结果
			for (int i = 0; i < size; i++) {
				TreeNode* cur = que.front();
				que.pop();
				vec.push_back(cur->val);// 添加结点的val
				// 将左右孩子入队
				if (cur->left != NULL) que.push(cur->left);
				if (cur->right != NULL) que.push(cur->right);
			}
			res.push_back(vec);
		}
		reverse(res.begin(), res.end());
		return res;
    }
};
  1. 二叉树的右视图
    思路: 再二叉树的层序遍历的基础上,判断当前元素是否为本层的末尾元素即可。
class Solution {
public:
	vector<int> rightSideView(TreeNode* root) {
		vector<int> res;
		queue<TreeNode*> que;
		if (root == NULL)  return res;
		que.push(root);
		while (!que.empty()) {
			int size = que.size();
			int value = 0;
			for (int i = 0; i < size; i++) {
				TreeNode* cur = que.front();
				que.pop();
				value = cur->val;
				if (cur->left != NULL) que.push(cur->left);
				if (cur->right != NULL) que.push(cur->right);
			}
			res.push_back(value); // 添加末尾元素
		}
		return res;
	}
};
  1. 二叉树的平均值
    思路: 将层序遍历中的每层元素加入一个数组改为加和为一个值,添加进一个总数组中即可。
class Solution {
public:
	vector<double> averageOfLevels(TreeNode* root) {
		vector<double> res;
		queue<TreeNode*> que;
		if (root == NULL)  return res;
		que.push(root);
		while (!que.empty()) {
			int size = que.size();
			double value = 0;
			for (int i = 0; i < size; i++) {
				TreeNode* cur = que.front();
				que.pop();
				value += cur->val; // 计算本层的元素和
				if (cur->left != NULL) que.push(cur->left);
				if (cur->right != NULL) que.push(cur->right);
			}
			res.push_back(value / size); // 本层的均值
		}
		return res;
	}
};
  1. N叉树的层序遍历
    思路: 与层序遍历的解法相似,只不过将添加左右孩子改为添加全部孩子。
class Solution {
public:
	vector<vector<int>> levelOrder(Node* root) {
		vector<vector<int>> res;
		queue<Node*> que;
		if (root == NULL) return res;
		que.push(root);
		while (!que.empty()) {
			int size = que.size();
			vector<int> vec;
			for (int i = 0; i < size; i++) {
				Node* cur = que.front();
				que.pop();
				vec.push_back(cur->val); // 加入当前结点的值
				// 将孩子添加进队列
				for (int j = 0; j < cur->children.size(); j++)
					if (cur->children[j] != NULL)
						que.push(cur->children[j]);
			}
			res.push_back(vec);
		}
		return res;
	}
};
  1. 在每个树行中找最大值
    思路: 与求二叉树的右视图类似,只不过将求每行最右元素改为求每行最大值。在for循环中比较即可。
class Solution {
public:
	vector<int> largestValues(TreeNode* root) {
		vector<int> res;
		queue<TreeNode*> que;
		if (root == NULL)  return res;
		que.push(root);
		while (!que.empty()) {
			int size = que.size();
			int max = INT_MIN;
			for (int i = 0; i < size; i++) {
				TreeNode* cur = que.front();
				que.pop();
				if (max < cur->val)
					max = cur->val;
				if (cur->left != NULL) que.push(cur->left);
				if (cur->right != NULL) que.push(cur->right);
			}
			res.push_back(max); // 添加末尾元素
		}
		return res;
	}
};
  1. 填充每个节点的下一个右侧节点
    思路: 在层序遍历的基础,判断当前节点是否是 本层的末尾节点,若是,则不进行操作;若不是,则添加next。
class Solution {
public:
	Node* connect(Node* root) {
		queue<Node*> que;
		if (root == NULL) return root;
		que.push(root);
		while (!que.empty()) {
			int size = que.size();
			for (int i = 0; i < size; i++) {
				Node* cur = que.front();
				que.pop();
				// 当该结点非当层的末尾结点,且还有剩余元素时,添加next
				if (!que.empty() && i + 1 < size)
					cur->next = que.front();
				if (cur->left != NULL) que.push(cur->left);
				if (cur->right != NULL) que.push(cur->right);
			}
		}
		return root;
	}
};
  1. 填充每个节点的下一个右侧节点Ⅱ
    思路: 与上一题的思路相同
class Solution {
public:
    Node* connect(Node* root) {
        queue<Node*> que;
		if (root == NULL) return root;
		que.push(root);
		while (!que.empty()) {
			int size = que.size();
			for (int i = 0; i < size; i++) {
				Node* cur = que.front();
				que.pop();
				// 当该结点非当层的末尾结点,且还有剩余元素时,添加next
				if (!que.empty() && i + 1 < size)
					cur->next = que.front();
				if (cur->left != NULL) que.push(cur->left);
				if (cur->right != NULL) que.push(cur->right);
			}
		}
		return root;
    }
};
  1. 二叉树的最大深度
    思路: 只需要一个遍历在层序遍历的过程中记录深度即可
class Solution {
public:
	int maxDepth(TreeNode* root) {
		int depth = 0;
		queue<TreeNode*> que;
		if (root == NULL) return depth;
		que.push(root);
		while (!que.empty()) {
			int size = que.size();
			depth++;
			for (int i = 0; i < size; i++) {
				TreeNode* cur = que.front();
				que.pop();
				if (cur->left != NULL) que.push(cur->left);
				if (cur->right != NULL) que.push(cur->right);
			}
		}
		return depth;
	}
};
  1. 二叉树的最小深度
    思路: 与最大深度相似,但需要判断当前节点是否有左右孩子,若左右孩子均无,则直接返回当前深度,当前深度即为最小深度。
class Solution {
public:
	int minDepth(TreeNode* root) {
		int depth = 0;
		queue<TreeNode*> que;
		if (root == NULL) return depth;
		que.push(root);
		while (!que.empty()) {
			int size = que.size();
			depth++;
			for (int i = 0; i < size; i++) {
				TreeNode* cur = que.front();
				que.pop();
				if (cur->left != NULL) que.push(cur->left);
				if (cur->right != NULL) que.push(cur->right);
				// 当左右孩子均为空时,返回当前深度
				if (cur->left == NULL && cur->right == NULL) return depth;
			}
		}
		return depth;
	}
};
  1. 总结
    二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现

6. 翻转二叉树

leetcode链接

思路: 翻转二叉树,即将每个二叉树的左右孩子翻转,即可得到整个二叉树翻转的结果

层序遍历:

class Solution {
public:
	TreeNode* invertTree(TreeNode* root) {
		if (root == NULL) return root;
		queue<TreeNode*> que;
		que.push(root);
		while (!que.empty()) {
			int size = que.size();
			for (int i = 0; i < size; i++) {
				TreeNode* cur = que.front();
				que.pop();
				// 交换左右孩子
				swap(cur->left, cur->right);
				if (cur->left != NULL) que.push(cur->left);
				if (cur->right != NULL) que.push(cur->right);
			}
		}
		return root;
	}
};

先序遍历(递归法):

class Solution {
public:
	void preorder(TreeNode* T) {
		if (T == NULL) return;
		// 翻转左右孩子
		swap(T->left, T->right);
		preorder(T->left);
		preorder(T->right);
	}

	TreeNode* invertTree(TreeNode* root) {
		if (root == NULL) return root;
		preorder(root);
		return root;
	}
};

先序遍历(迭代法):

class Solution {
public:
	TreeNode* invertTree(TreeNode* root) {
		if (root == NULL) return root;
		stack<TreeNode*> st;
		st.push(root);
		while (!st.empty()) {
			TreeNode* cur = st.top();
			swap(cur->right, cur->left);
			st.pop();
			// 先加右孩子,后加左孩子
			if(cur->right!= NULL) st.push(cur->right);
			if (cur->left != NULL) st.push(cur->left);
		}
		return root;
	}
};

时间复杂度: O(n)

7. 二叉树周末总结

代码随想录:二叉树周末总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值