树的存储与遍历
一、概念
1. 数据结构
一般情况下,我们会将数据结构分为逻辑结构和物理结构,其中逻辑结构是我们的逻辑下存储的结构,而物理结构是计算机的逻辑下存储的结构。数据结构的分类可以参见下表:
2. 树的基础知识
树是一种非线性的数据结构,每个数据都是一对多的关系,由结点和边组成。树是有 n n n( n ≥ 0 n \ge 0 n≥0)个结点组成的具有分支和层次特性的数据集合。
树当中的数据就是 结点。
结点的深度:设根结点的深度为
1
1
1(根据题目),其他根结点深度为其父节点深度加
1
1
1。
结点的高度:设叶结点的高度为
1
1
1(根据题目),其他结点高度为其他子结点高度加
1
1
1。
层次:从根开始,根为第
1
1
1 层,根的子结点为第
2
2
2 层,以此类推。
路径:对于树中两点不同的结点,如果从一个结点出发,自上而下沿着树中连着结点的线段能到达另一个结点,称为它们之间存在着一条路径。
一棵树中,结点最大的度就是 树的度。
一个结点拥有子树的个数称为 结点的度。
具有相同父结点的结点被称为 兄弟结点。
某个结点的直接后继结点被称为该结点的 子结点。
某个结点的直接前驱结点被称为该结点的 父结点。
没有直接前驱,只有直接后继的结点为 根结点。
一棵树中,度为
0
0
0 的结点称为 叶结点。
当
n
>
1
n>1
n>1 的时候,除根结点以外其余结点可以分为
m
m
m 个互不相交的有限集合,其中每个集合本身又是一棵树,这些集合称为这棵树的 子树。
当有
m
m
m(
m
≥
1
m\ge1
m≥1)个树的集合时,称呼其为 森林。
当
n
=
0
n=0
n=0 的时候,树为 空树。
3. 树的相关概念
性质1: 树中的结点数等于所有结点的度之和加
1
1
1,即:
n
=
1
+
n
1
×
1
+
n
2
×
2
+
⋯
n=1+n_1\times1+n_2\times2+\cdots
n=1+n1×1+n2×2+⋯
性质2: 树中的结点树等于所有结点个数总和,即:
n
=
n
0
+
n
1
+
n
2
+
⋯
n=n_0+n_1+n_2+\cdots
n=n0+n1+n2+⋯
性质3: n n n 个结点的树中有 n − 1 n-1 n−1 条边。
4. 树的遍历
- 先序(根)遍历
先遍历根结点,然后遍历左子树和右子树 - 中序(根)遍历
先遍历左子树,再遍历根节点和右子树 - 后序(根)遍历
先遍历左子树,在遍历右子树和根结点 - 层序(根)遍历
一层一层地遍历
二、绘制树
1. 知先中得后
有一棵树:
先序遍历序列为 a b d g c e f h
中序遍历序列为 d g b a e c h f
则它的后序遍历序列为_______________。
首先通过先序找到根 a,然后通过中序找到左子树(d g b)和右子树(e c h f)。
对于左子树(d g b),先序是(b d g),中序是(d g b)。通过先序找到根 b,然后通过中序找到左子树(d g)并且它没有右子树。
然后再看(d g),先序和中序都是(d g)。通过先序找到根 d,然后通过中序找到 d 的右孩子 g。
对于右子树(e c h f),先序确定根是 c,中序确定 c 的左孩子是 e,右子树是(h f),推出 h 是 f 的左孩子。
因此,后序遍历是 g d b e h f c a。
2. 知中后得先
中序遍历序列为 d g b a e c h f
后序遍历序列为 g d b e h f c a
先序遍历序列为_______________。
a ⇒ (dgb, echf)
(dbg): b ⇒ (db, NULL)
(db): d ⇒ (g, NULL)
(echf): c ⇒ (e, hf)
(hf): f ⇒ (h, NULL)
因此,先序遍历是 a b d g c e f h。
3. 知先后猜结点数
【NOIP 2010 普及组 T17】 一颗二叉树的前序遍历是 ABCDEFG,后序遍历是 CBFEGDA,否则结点的左子树的结点个数可能是( A )。
A. 2
B. 3
C. 4
D. 5
我们可以直接用试的方法:
三、树的操作
1. 建树
首先,每个结点都可以当作是一个数组中的元素,它有三个信息:自己本身、父结点、左孩子、右孩子。所以我们可以考虑用 struct[]
来实现。
2. 转换
- 找根结点(先序的头 / 后序的尾)
- 存储根结点
- 在中序找到左子树和右子树
- 在左子树重复步骤 1~4,直到碰到叶结点
- 在右子树重复步骤 1~4,直到碰到叶结点
根据上面的步骤,我们直到可以用递归的方法来实现。
3. 专有变量
PreOd
:前序遍历
InOd
:中序遍历
PostOd
:后序遍历
root
:根结点
leaf
:叶结点
buildTree
:递归建树函数
注意:后续讲到的"平衡二叉树"指任意一个结点的左子树的高度和右子树的高度相差不超过
1
1
1;二叉查找树,即一个二叉树上左子树所有结点的关键字均小于根结点的关键字,右子树所有结点的关键字均大于根结点的关键字,且左子树和右子树都是一颗二叉查找树,那么整棵树就是一颗二叉查找树。
四、例题
1. 知中后得先
【题目描述】
给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,长度
≤
26
≤26
≤26)。
【输入格式】
2
2
2 行,均为大写字母组成的字符串,表示一棵二叉树的中序与后序排列。
【输出格式】
1
1
1 行,表示一棵二叉树的先序。
【输入样例#1】
BADC
BDCA
【输出样例#1】
ABCD
【参考答案】
#include <iostream>
#include <string>
using namespace std;
int pos;
string InOd, PostOd;
struct Node
{
char val;
int l, r, f;
}tree[30];
// 建树
int buildTree(string InOd, string PostOd)
{
int len = InOd.length(); // 序列长度
if (len == 0) return 0; // 序列为空,表无子树
char c = PostOd[len-1]; // 找根结点
int root = ++pos; // 记录根结点的位置
tree[root].val = c; // 存根结点
int k = InOd.find(c); // 在中序找根结点的位置
tree[root].l = buildTree(InOd.substr(0, k), PostOd.substr(0, k)); // 存左子树
tree[root].r = buildTree(InOd.substr(k+1), PostOd.substr(k, len-k-1)); // 存右子树
return root; // 当前根结点的位置
}
void printPreOd(int root)
{
cout << tree[root].val;
if (tree[root].l) printPreOd(tree[root].l);
if (tree[root].r) printPreOd(tree[root].r);
}
int main()
{
cin >> InOd >> PostOd;
int root = buildTree(InOd, PostOd);
printPreOd(root);
return 0;
}
2. 二叉查找树
假如我们在最后的时候要输出插入后二叉查找树的中序遍历和倒序遍历。
#include <iostream>
using namespace std;
struct Node
{
int val;
int r, l, f;
}tree[100005];
int n;
void insert(int idx, int root)
{
if (tree[idx].val < tree[root].val)
{
if (tree[root].l == 0)
{
tree[root].l = idx;
return;
}
else
{
insert(idx, tree[root].l);
}
}
else
{
if (tree[root].r == 0)
{
tree[root].r = idx;
return;
}
else
{
insert(idx, tree[root].r);
}
}
}
void printInOd(int root)
{
if (tree[root].l) printInOd(tree[root].l);
cout << tree[root].val << " ";
if (tree[root].r) printInOd(tree[root].r);
}
void printPostOd(int root)
{
if (tree[root].l) printPostOd(tree[root].l);
if (tree[root].r) printPostOd(tree[root].r);
cout << tree[root].val << " ";
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> tree[i].val;
if (i > 1) insert(i, 1);
}
printInOd(1);
cout << endl;
printPostOd(1);
return 0;
}