代码随想录算法训练营Day14 | 94.二叉树的中序遍历、144.二叉树的前序遍历、145.二叉树的后序遍历

本文详细介绍了二叉树的基本概念,如满二叉树、完全二叉树和二叉搜索树,重点讲解了C++中map、set等容器的底层实现。此外,对深度优先搜索和广度优先搜索以及前序、中序和后序遍历的递归和迭代方法进行了深入剖析。
摘要由CSDN通过智能技术生成

二叉树理论基础

二叉树的种类

  • 满二叉树

节点数量为2k-1

  • 完全二叉树

在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置

  • 二叉搜索数

二叉搜索数对节点布局没有要求,但必须保证元素的排列是有序的

· 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

· 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

· 它的左、右子树也分别为二叉排序树

  • 平衡二叉搜索树(AVL)

是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

C++中map、multimap、set、multiset的底层实现都是平衡二叉搜索树

(unordered_map的底层实现是哈希表)

二叉树的存储方式

  • 链式存储:

(传入二叉树可以理解为传入一个链表,每个节点有两个next指针分别指向左右孩子)

  • 顺序存储:

即使用数组来存储二叉树

获取下标i的左孩子:2 * i + 1

获取下标i的右孩子:2 * i + 2

二叉树的遍历

 

  • 深度优先搜索

前中后序遍历都是深度优先搜索

        前 / 中 / 后序中的“序”指的是中间节点搜索的次序

        前序:中-左-右

        中序:左-中-右

        后序:左-右-中

一般采用递归来实现,或使用栈来模拟递归进行实现

  • 广度优先搜索

        即层序遍历

 94.二叉树的中序遍历

递归遍历

递归遍历的要点:

1、确定递归函数的参数和返回值

2、确定终止条件

3、确定单层递归的逻辑

由于二叉树结构本身就是递归的(每一个节点都是一个子树),所以递归写法好理解,写法也简洁。 

// 递归遍历
void traversal(TreeNode* root, vector<int>& ans) {
	if (root == nullptr)
		return;
	traversal(root->left, ans);    // 左
	ans.push_back(root->val);      // 中
	traversal(root->right, ans);   // 右
}

vector<int> inorderTraversal(TreeNode* root) {
	vector<int> ans;
	traversal(root, ans);
	return ans;
}

迭代遍历

由于中序遍历是,对节点的访问与操作不是在同一步中进行的,所以迭代遍历的写法与前序后序有所不同。

不断访问左孩子,直到没有左孩子时取出栈顶节点进行操作

// 迭代遍历
vector<int> inorderTraversal(TreeNode* root) {
	vector<int> ans;
	stack<TreeNode*> st;
	TreeNode* cur = root;

	while (cur || !st.empty()) {
		// 节点非空时访问节点,但不对节点进行操作,
		// 而是不断将其压入栈并继续访问其左孩子
		while (cur) {
			st.push(cur);
			cur = cur->left;		// 左
		}
		// 直到左孩子为空节点时,才从栈中弹出节点进行操作
		cur = st.top();			// 中
		st.pop();
		ans.push_back(cur->val);
		cur = cur->right;		// 右
	}
	return ans;
}

迭代遍历——统一写法 

思路:

1、对节点进行访问时,先不进行操作,而是将节点展开为一个子树,将树中的节点以右-中-左的顺序压入栈。(前序与后序在这步改变一下入栈的顺序即可)

2、使用一个空指针来标记已经被访问但还未被操作的节点(即上一步中的中节点)。之后遇到空指针就表示需要对栈中下一个元素进行操作了。

// 迭代——统一写法
vector<int> inorderTraversal(TreeNode* root) {
	vector<int> ans;
	stack<TreeNode*> st;
	TreeNode* cur;
	if (root)
		st.push(root);
	while (!st.empty()) {
		cur = st.top();
		if (cur) {
			// 先将栈顶弹出
			st.pop();

			// 将栈顶节点扩展成一个子树,按右-中-左的顺序压入栈
			// 右
			if (cur->right)
				st.push(cur->right);
			// 中,中节点访问但还没操作,后面加入一个空指针作为标识
			st.push(cur);
			st.push(nullptr);
			// 左
			if (cur->left)
				st.push(cur->left);
		}
		// 碰到了标示符(空指针),说明此时该对节点进行操作了
		else {
			// 先将空指针弹出,再对栈顶元素进行操作
			st.pop();
			cur = st.top();
			ans.push_back(cur->val);
			// 最后将操作完成的栈顶元素弹出
			st.pop();
		}
	}
	return ans;
}

144.二叉树的前序遍历

递归遍历与迭代遍历的统一写法都只需要改变一下节点压入栈的顺序即可:

递归写法

void traversal(TreeNode* root, vector<int>& ans) {
	if (root == nullptr)
		return;
	ans.push_back(root->val);
	traversal(root->left, ans);
	traversal(root->right, ans);
}

vector<int> preorderTraversal(TreeNode* root) {
	vector<int> ans;
	traversal(root, ans);
	return ans;
}

迭代遍历——统一写法

vector<int> preorderTraversal(TreeNode* root) {
	vector<int> ans;
	stack<TreeNode*> st;
	TreeNode* cur;
	if (root)
		st.push(root);

	while (!st.empty()) {
		cur = st.top();
		if (cur) {
			st.pop();
			// 右-左-中顺序入栈
			if (cur->right)
				st.push(cur->right);
			if (cur->left)
				st.push(cur->left);
			st.push(cur);
			st.push(nullptr);		// 中节点后添加标识符
		}
		else {
			st.pop();
			cur = st.top();
			ans.push_back(cur->val);
			st.pop();
		}
	}
	return ans;
}

迭代遍历

前序遍历时,对节点的访问与操作在同一步进行,所以每次迭代直接取出栈顶节点进行操作即可(对比中序遍历是直到访问到空节点时才取出栈顶节点进行操作)

操作完再将节点的左右节点顺序入栈

vector<int> preorderTraversal(TreeNode* root) {
	vector<int> ans;
	if (!root)
		return ans;
	stack<TreeNode*> st;
	TreeNode* cur;

	st.push(root);
	while (!st.empty()) {
		// 中
		cur = st.top();
		st.pop();
		ans.push_back(cur->val);
		// 左、右(入栈顺序需要与遍历顺序相反,空节点不入栈)
		if(cur->right)
			st.push(cur->right);
		if(cur->left)
			st.push(cur->left);
	}
	return ans;
}

145.二叉树的后序遍历

同前序遍历,递归遍历与迭代遍历的统一写法都只需要改变一下节点压入栈的顺序即可:

递归遍历:

void traversal(TreeNode* root, vector<int>& ans) {
	if (root == nullptr)
		return;
	traversal(root->left, ans);
	traversal(root->right, ans);
	ans.push_back(root->val);
}

vector<int> postorderTraversal(TreeNode* root) {
	vector<int> ans;
	traversal(root, ans);
	return ans;
}

迭代遍历——统一写法:

 

vector<int> postorderTraversal(TreeNode* root) {
	vector<int> ans;
	stack<TreeNode*> st;
	TreeNode* cur;
	if (root)
		st.push(root);

	while (!st.empty()) {
		cur = st.top();
		if (cur) {
			st.pop();
			// 中-右-左顺序入栈
			st.push(cur);
			st.push(nullptr);		// 中节点后添加标识符
			if (cur->right)
				st.push(cur->right);
			if (cur->left)
				st.push(cur->left);
		}
		else {
			st.pop();
			cur = st.top();
			ans.push_back(cur->val);
			st.pop();
		}
	}
	return ans;
}

迭代遍历

后序遍历只需将前序遍历时左右入栈的顺序交换一下(变为中-右-左),最后将结果数组反转即可(中-右-左反转变为左-右-中,即后序遍历的顺序)

vector<int> postorderTraversal(TreeNode* root) {
	vector<int> ans;
	if (!root)
		return ans;
	stack<TreeNode*> st;
	TreeNode* cur;

	st.push(root);
	while (!st.empty()) {
		// 先以中右左的顺序存入数组
		// 中
		cur = st.top();
		st.pop();
		ans.push_back(cur->val);
		// 右、左
		if (cur->left)
			st.push(cur->left);
		if (cur->right)
			st.push(cur->right);
	}
	// 再将数组反转,中右左 变为 左右中,即后序遍历顺序
	std::reverse(ans.begin(), ans.end());
	return ans;
}
  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值