经过前人的总结,二叉树具有以下几个性质:
满二叉树以及完全二叉树的定义以及区别。
满二叉树一定是完全二叉树,反之则不一定。
满二叉树示意图:
完全二叉树示意图:
二叉树的顺序存储,指的是使用顺序表(数组)存储二叉树。需要注意的是顺序存储只适用于完全二叉树。换句话说,只有完全二叉树才可以使用顺序表存储。因此,如果我们想顺序存储普通二叉树,需要提前将普通二叉树转化为完全二叉树。堆就是典型的使用顺序表存储的一种完全二叉树。
完全二叉树的顺序存储,仅需从根节点开始,按照层次依次将树中节点存储到数组即可。
如上面所示的完全二叉树,存储状态如图所示:
不仅如此,从顺序表中还原完全叉树也很简单。我们知道,完全二叉树具有这样的性质,将树中节点按照层次并从左到右依次标号(1,2,3…)若节点i有左右孩子,则其左孩子节点为2*i,右孩子节点为2*i+1。比性质可用于还原数组中存储的完全二叉树。(注意,这里的i是从1开始的,如果是数组的下标,左孩子结点为2*i+1,右孩子结点为2*i+2)。
由于使用顺序表存储二叉树只能存储完全二叉树,存储其他的树需要先转换成完全二叉树,比较麻烦,所以,常用的存储二叉树的方式为链表的方式进行存储。如图所示:
表示一个结点的代码为:
struct TreeNode{
int val; // 结点的数值
TreeNode* left; // 左孩子结点
TreeNode* right; // 右孩子结点
TreeNode() {};
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}; // 初始化列表的构造函数
};
二叉树的遍历方式主要有两种:
深度优先遍历:
广度优先遍历
这个前中后,其实就是指的是中间结点的遍历顺序。
实现递归需要注意以下几个步骤:
这也是递归最重要的几步,此外,根据一些题目,我对递归做了一点点自己的理解,主要是方便自己理解,在之后进行补充。
题目分析:
对于二叉树的遍历既可以使用递归的方式,也可以采用非递归,使用栈或者队列的方式。
递归法: 注意递归的三要素
class Solution {
public:
void traversal(TreeNode* node, vector<int>& vec) {
if (!node) return; // 递归的中止条件
// 每一层递归需要执行的逻辑
vec.emplace_back(node->val); // 先保存当前结点的值
traversal(node->left, vec); // 再遍历左树
traversal(node->right, vec); // 最后遍历右树
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ans;
traversal(root, ans);
return ans;
}
};
非递归法:
class Solution {
public:
// 使用非递归的(迭代)的方法进行前序遍历,注意栈的操作是先弹出再存放新的
vector<int> preorderTraversal(TreeNode* root) {
if (root == nullptr) return {};
vector<int> ans;
stack<TreeNode*> stk;
stk.push(root); // 先将根结点压进去
while (!stk.empty()) { // 直至栈为空,表示遍历结束
TreeNode* node = stk.top();
ans.emplace_back(stk.top()->val);
stk.pop();
// 由于栈是先进后出,所以先将右子树压进栈
if (node->right != nullptr) stk.push(node->right);
if (node->left != nullptr) stk.push(node->left);
}
return ans;
}
};
注意由于栈是先进后出的,所以要先压右结点,再压左结点。
递归法:
class Solution {
public:
void traversal(TreeNode* node, vector<int>& vec) {
if (!node) return; // 递归的中止条件
// 每一层递归需要执行的逻辑
traversal(node->left, vec); // 先遍历左树
vec.emplace_back(node->val); // 再保存当前结点的值
traversal(node->right, vec); // 最后遍历右树
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ans;
traversal(root, ans);
return ans;
}
};
非递归法:
class Solution {
public:
// 使用非递归的(迭代)的方法进行前序遍历,注意栈的操作是先弹出再存放新的
vector<int> postorderTraversal(TreeNode* root) {
if (root == nullptr) return {};
vector<int> ans;
stack<TreeNode*> stk;
stk.push(root); // 先将根结点压进去
while (!stk.empty()) { // 直至栈为空,表示遍历结束
TreeNode* node = stk.top();
ans.emplace_back(stk.top()->val);
stk.pop();
// 注意这里是左子树先进去
if (node->left != nullptr) stk.push(node->left);
if (node->right != nullptr) stk.push(node->right);
}
// 需要将ans反转
reverse(ans.begin(), ans.end());
return ans;
}
};
递归法:
class Solution {
public:
void traverse(TreeNode* node, vector<int>& ans) {
if (node == nullptr) return;
traverse(node->left, ans); // 左
ans.emplace_back(node->val); // 中
traverse(node->right, ans); // 右
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ans;
traverse(root, ans);
return ans;
}
};
迭代法:注意中序遍历的迭代法和前序遍历以及后序遍历的迭代法的不同之处
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
if (root == nullptr) return {};
// 使用迭代法来实现
vector<int> ans;
stack<TreeNode*> stk;
TreeNode* node = root;
// 注意循环中的判断条件,当stk为空的时候,此时的node不一定是空,所以得考虑两个条件
while (node || !stk.empty()) {
while (node) {
stk.push(node);
node = node->left; // 首先将所有的左节点全部入栈
}
// 此时已经到了最左边的结点了,所以肯定是最左边的结点就是第一个遍历到的
node = stk.top();
ans.emplace_back(node->val);
stk.pop();
node = node->right;
}
return ans;
}
};
注意这里的循环的判断条件是node || stk.empty()
,是这样理解的,二叉树如图所示:
最开始node=root,然后进入循环,进入大循环之后,再进入内部的循环,最终出来后node为空,然后获得了相应的值后,将栈顶元素弹出,此时node = node->right
。这个时候栈已经为空了,但是node不为空,所以是可以继续进行循环的,因为树还没有遍历完。直到node为空同时栈为空才保证了树遍历完毕。