#数据结构 #C
树是
因此在任意一棵非空树中应满足:
- 有且仅有一个特定的称为根的结点。
- 当
$n > 1$ 时,其余结点可分为$m(m > 0)$ 个互不相交的有限集合$T_1, T_2,…, T_m$ ,其中每个集合本身又是一棵树,并且称为根结点的子树。
每个结点中保存指向双亲的“指针”。
#define MAX_TREE_SIZE 100 // 树中最多的结点
typedef struct { // 树的结点定义
ElemType data; // 数据元素
int parent; // 双亲位置域
} PTNode;
typedef struct { // 树的类型定义
PTNode node[MAX_TREE_SIZE]; // 双亲表示
int n; // 结点树
} PTree;
数组下标 | data | parent |
---|---|---|
0 | A | -1 |
1 | B | 0 |
2 | C | 0 |
3 | D | 0 |
4 | E | 1 |
5 | F | 1 |
6 | G | 2 |
7 | H | 3 |
8 | I | 3 |
9 | J | 3 |
10 | K | 4 |
11 | ... | ... |
其中,根结点固定存储在0,-1表示没有双亲。
问题来了,如何让用双亲表示法实现增删查操作? 新增数据元素时候,无需按逻辑上的次序存储,直接在尾部新增即可。例如新增M、L结点,只需要在尾部新增即可。
数组下标 | data | parent |
---|---|---|
.......... | ...... | ........ |
11 | M | 7 |
12 | L | 4 |
删除数据元素,如果是叶子结点的话,可以直接删除元素,也可以只让data值删除。 如果不是叶子结点,删除的时候,就需要找到它的所有孩子结点并且全部删除。但是,查找指定结点的孩子只能从头遍历。
- 优点:查指定 结点的双亲很方便
- 缺点:查指定 结点的孩子只 能从头遍历
采用顺序+链式存储,顺序存储各个结点,每个结点中保存孩子链表头指针。
struct CTNode {
int child; // 孩子结点在数组中的位置
struct CTNode *next; // 下一个孩子
};
typedef struct {
ElemType data;
struct CTNode *firstChild; // 第一个孩子
} CTBox;
typedef struct {
CTBox nodes[MAX_TREE_SIZE];
int n, r; // 结点数和根的位置
}
纯粹使用链表的方式存储:
// 树的存储 -- 孩子兄弟表示法
typeddef struct CSNode {
ElemType data; // 数据域
struct CSNode *firstchild, *nextSibling; // 第一个孩子和右兄弟指针
} CSNode, *CSTree;
这样的表示方法和二叉树形式同构,可以看作是左指针和右指针表示法。
如图,树转化的二叉树中,某个结点的左右两叉完全属于不同层次。 左叉(蓝色线)为孩子,右叉(黄色线)为兄弟,沿着黄色线一直追溯即可把所有的同层次的结点全找到。相对应的,二叉树也可以复原成原来树的逻辑结构。
把森林中所有的根结点全都视为兄弟。
相对应的,二叉树也可以复原成原来森林的逻辑结构。
树的遍历有以下几种方式:先根遍历、后根遍历、层次遍历。
若树非空,先访问根结点,再依次对每棵子树进行先根遍历。
// 树的先根遍历
void PreOrder (TreeNode *R)
{
if (R != NULL) {
visit(R); // 访问根结点
while( R 还有下一个子树T ){
PreOrder(T); // 先根遍历下一个子树
}
}
}
树的先根遍历序列与这棵树转化形成的相应二叉树的先序序列相同。
如下这个树的先根遍历序列是:ABEKFCGDHIJ
。
若树非空,先依次访问每棵子树进行后根遍历,再访问根结点。
// 树的先根遍历
void PostOrder (TreeNode *R)
{
if (R != NULL) {
while( R 还有下一个子树T ){
PostOrder(T); // 后根遍历下一个子树
}
visit(R); // 访问根结点
}
}
树的后根遍历序列与这棵树转化形成的相应二叉树的中序序列相同。
如下这个数的先根遍历序列是:KEFBCGDHIJA
。
使用队列实现:
(1) 若树非空,则根结点入队;
(2) 若队列非空,队头元素出队并访问,同时将该元素的孩子依次入队;
(3) 重复(2)直到队列为空。
对树的层次遍历可以称作是对树的广度优先遍历(Breadth_First_Search),对树的先根遍历、后跟遍历是对树的深度优先遍历(Depth_First_Search)。
若森林为非空,则按如下规则进行遍历:
(1)访问森林中第一棵树的根结点。
(2)先序遍历第一棵树中根结点的子树森林。
(3)先序遍历除去第一棵树之后剩余的树构成的森林。
对森林的先序遍历,效果等同于依次对各个树进行先根遍历,实质上,等同于依次对二叉树的先序遍历。
上图中森林的先序遍历是 BEKFCGDHMIJ
。
若森林为非空,则按如下规则进行遍历:
(1)中序遍历森林中第一棵树的根结点的子树森林。
(2)访问第一棵树的根结点。
(3)中序遍历除去第一棵树之后剩余的树构成的森林。
对森林的先序遍历,效果等同于依次对各个树进行后根遍历,实质上,等同于依次对二叉树的中序遍历。上图中森林的中序遍历是 KEFBGCMHIJD
。
总结,树、森林的遍历与其转化成二叉树遍历的对应关系:
树 | 森林 | 二叉树 |
---|---|---|
先根遍历 | 先序遍历 | 先序遍历 |
后根遍历 | 中序遍历 | 中序遍历 |