二叉树的性质
(二叉树的每个节点最多只有 2 个子树 (不存在度 >2 的节点) 有左右之分,不可以颠倒)
(二叉树的第 i 层最多有 2^{i-1}个结点)
(设二叉树的深度为 k 则: 二叉树最多有 2^{k}-1 个结点)
(对任何一棵二叉树 T ,设其叶节点数为 n_{0} 其度为 2 的结点个数为 n_{2} 则:n_{0} = n{2} + 1)
某些特殊的二叉树
满二叉树:
满二叉树示意图
$深度为 k 且有 2^{k}-1 个结点,就是一棵满二叉树,满二叉树的每一层都是满的$
完全二叉树:
完全二叉树示意图
$除最后一层外,其他层都是满的,或在右边连续缺少若干结点,就是一棵完全二叉树,设有 n 个结点的完全二叉树的深度为log_{2}(n+1)$
$深度为 k 的完全二叉树,至少 2^{k-1} 个结点,最多 2^{k}-1 个结点$
二叉搜索树:
二叉搜索树示意图
排序二叉树有如下性质
– $若左子树不空,则左子树上所有节点均小于根节点的值$
– $若右子树不空,则右子树上所有节点均大于根节点的值$
– $左右子树也都是二叉搜索树$
– $二叉搜索树可以为空树$
遍历二叉树
- 先序遍历
$先序遍历是指先访问根节点,再依次访问左子树和右子树$
- 中序遍历
$中序遍历是指先访问左子树,再依次访问根节点和右子树$
- 后序遍历
$先序遍历是指先访问左子树和右子树,最后再访问根节点$
- 层次遍历
$跟“扩展式广度优先搜索(俗称“广搜”)相同”$
- 四种遍历参考代码:
int son[N][2], root;
vector<int> v1, v2, v3, v4;
//先序遍历
void preorder(int u) {
if (u == 0) return;
v1.push_back(u);
preorder(son[u][0]);
preorder(son[u][1]);
}
//中序遍历
void inorder(int u) {
if (u == 0) return;
inorder(son[u][0]);
v2.push_back(u);
inorder(son[u][1]);
}
//后序遍历
void postorder(int u) {
if (u == 0) return;
postorder(son[u][0]);
postorder(son[u][1]);
v3.push_back(u);
}
//层次遍历
void levelorder() {
queue<int> q;
if (root != 0) q.push(root);
while (!q.empty()) {
int u = q.front();
q.pop();
v4.push_back(u);
if (son[u][0] != 0) q.push(son[u][0]);
if (son[u][1] != 0) q.push(son[u][1]);
}
}
```
## 根据遍历结果复原二叉树:
注意!先序遍历和后序遍历不能确定唯一的二叉树!
对于先序遍历和中序遍历,我们只需在先序遍历中确定根节点,并且靠中序遍历找出其位置,再依次还原左子树和右子树,即可确定二叉树。
对于后序遍历和中序遍历,也可按照同样的方法进行还原,确定唯一的二叉树。
参考代码:
```cpp
// a数组为中序遍历,b数组为先序遍历
// x1、y1为当前子树在a数组中下标的范围,x2为前子树在b数组中下标的起点
int a[N], b[N], son[N][2];
void dfs(int x1, int y1, int x2) {
int pos = x1;
while (a[pos] != b[x2]) { // 在中序遍历里找到当前子树的根
pos++;
}
int len1 = pos - x1, len2 = y1 - pos; // 在中序遍历里确定当前子树的左子树和右子树大小
if (len1 > 0) { // 如果有左子树,那么根据先序遍历确定这个节点的左孩子
son[b[x2]][0] = b[x2 + 1];
dfs(x1, pos - 1, x2 + 1); // 递归进入左子树
}
if (len2 > 0) { // 如果有右子树,那么根据先序遍历确定这个节点的右孩子
son[b[x2]][1] = b[x2 + 1 + len1];
dfs(pos + 1, y1, x2 + 1 + len1); // 递归进入右子树
}
}