文章目录
- 5.6.1 树的存储结构(不是二叉树)
- 双亲表示法
- 孩子表示法
- 结构定义
- 双亲孩子法
- 孩子兄弟法
- 5.6.2 二叉树的转换
- 树与二叉树的转换
- 将树转换成二叉树
- 将二叉树转换成树
- 森林与二叉树的转换
- 森林转换成二叉树
- 二叉树转换成森林
- 5.6.3 树和森林的遍历
- 树的遍历
- 森林的遍历
- 如果将一棵树去掉根节点,那么这棵树就变成一片森林。
- 反之,将森林中的所有的树加上一个统一的根结点,就变成了树。
5.6.1 树的存储结构(不是二叉树)
双亲表示法
- 以一组连续的存储单元存储树的结点,每个结点除了数据域 data 之外,还附加一个 parent 域用来指向其双亲结点的位置。
- 找双亲容易,找孩子难。
实现
定义结构数组存放树的结点,每个结点包含两个域:
- 数据域:存放结点本身的信息。
- 双亲域:指示本结点的双亲结点在数组中的位置。
例如
- 根节点 R 没有双亲,所以双亲域存放-1。
- ABC 的双亲域里存着的都是0,表示他们共同的双亲都是在数组下标0出的数据 R,以此类推。
类型定义
typedef struct PTNode
{
TElemType data;//存放的结点当中的数据,元素类型取决于要存储的结点的元素类型。
int parent;//存放结点双亲的所在位置的下标
}PTNode;
定义树结构数组
#define MAX_TREE_SIZE 100
typedef struct
{
PTNode nodes[MAX_TREE_SIZE];//有100个PTNode类型的数组
int r;//表示根结点的下标的位置
int n;//记录总共存储了多少结点
}PTree;
孩子表示法
- 由于树中每个结点都可能有多棵子树,则可以使用多重链表,即每个结点由多个指针域,其中每个指针域指向一棵子树的根节点。
- 把每个结点的孩子结点排列起来,看成是一个线性表,用单链表存储,则 n 个结点有 n 个孩子链表(叶子的孩子链表为空表)。而 n 个头指针又组成一个线性表,用顺序表(含 n 个元素的结构数组)存储。
- 找孩子容易,找双亲难
举个栗子
如上图所示:
- 根节点 R 的孩子是 ABC,那么就把它的孩子看成是一个线性表。
- 将 ABC 用一个单链表穿起来。数据域用来存放数据元素,指针域则存放后继结点的地址。
- 其他结点的孩子则以此类推依次用单链表串起来。
- 用数组来存储每个单链表的头指针。
根节点 R 在下标为 r = 4 的位置,总共有 n = 10 个结点
结构定义
孩子结点结构
typedef struct CTNode
{
int child;
struct CTNode *next;
}*ChildPtr;
双亲结点结构
typedef struct
{
TElemType data;
ChildPtr firstchild;//孩子链表的头指针,这个指针指向的类型是有两个成员所构成的孩子结点结构。
}CTBox;
树结构
typedef struct
{
CTBox nodes[MAX_TREE_SIZE];//用来存放所有指向孩子链表的头指针
int r;//表示根节点所在数组位置的下标
int n;//表示该树有多少个结点
}CTree;
双亲孩子法
- 由前面两种表示法知道,要么不好找孩子,要么不好找双亲,这个时候就需要一种既能找爸爸又能找孩子的表示法。
- 可以将双亲法与孩子表结合起来,变成孩子双亲法,在结构数组中再增加一个成员用来存储每个结点的双亲结点的下标。
- 这种结构就称为带双亲的孩子链表。
孩子兄弟法
- 又称为二叉树表示法,或二叉链表表示法,即以二叉链表做树的存储结构。
- 链表中结点的两个链域分别指向该结点的第一个孩子以及下一个兄弟结点(左孩右兄)。
- 找孩子兄弟容易,找双亲困难
结构定义
//树的二叉链表(孩子-兄弟)存储表示
typedef struct CSNode
{
ElemType data;//data存储的元素是什么类型它就是什么类型
struct CSNode* firstchild ;//指向结点的第一个孩子
struct CSNode* nextsibling;//指向节点的下一个兄弟
}CSNode,*CSTree;
举个栗子
- 根节点 R 的左指针域存储它的第一个孩子 A 的地址,因为 R 没有兄弟,所以右指针域为NULL。
- A 左指针指向它的第一个孩子 D,右指针指向它从左往右数的下一个兄弟 B。
- 其余结点以此类推。
- 从左往右斜着看在一条线上的都是兄弟,反之从右往左斜着看在一条线上的则是孩子双亲。
5.6.2 二叉树的转换
树与二叉树的转换
- 将树转化为二叉树进行处理,利用二叉树的算法来实现对树的操作。
- 由于树和二叉树都可以用二叉链表作存储结构,则以二叉链表作媒介可以导出树与二叉树之间的一个对应关系。
将树以二叉链表的形式存储
- 左指针域指向结点的第一个孩子,右指针域则指向结点的下一个兄弟。
- A 结点的第一个孩子是 B,A 是根节点没有兄弟。
- B 没有孩子,它的下个兄弟是 C。
- C 的第一个孩子是 D,下个兄弟是 E。
- D 既没有孩子也没用兄弟。
让一棵二叉树存储成二叉链表是同样的结构
- 每个结点的左右指针域分别存储该结点的左右孩子。
总结
上图的 树与二叉树,具有相同的二叉链表存储结构,那么就可以:
- 把一棵 树 进行存储得到二叉链表,然后将二叉链表解释成二叉树对应的存储形式,就可以得到二叉树了。
- 同理,将这棵 二叉树 存储得到二叉链表,然后将二叉链表解释成树对应的存储形式,就可以得到二叉树了。
- 以中间的这个存储结构作为媒介,就可以找到树与二叉树之间的对应关系。
- 给定一棵树,可以找到唯一的一棵二叉树与之对应
规律
:在树中的兄弟结点到了二叉树中变成了右孩子,左变右兄弟变儿子,右变左儿子变兄弟。
将树转换成二叉树
- 加线:在兄弟之间加一条线
- 抹线:对每个结点,只保留双亲与第一个孩子之间的连线,其余连线全部消除。
- 旋转:以树的根节点为轴心,将整棵树顺时针转 45°。
树变二叉树:兄弟相连留长子
举个栗子
将这棵树转换成二叉树,兄弟相连留长子
- 连线:兄弟之间要连线
- 抹线:只保留双亲结点与其第一个孩子之间的连线。
- 旋转:以树的根结点为轴,顺时针转 45°。
将二叉树转换成树
既然可以将树通过兄弟相连留长子这个方法转换成二叉树,那么同样可以将这个方法逆转得到二叉树。
- 加线:若 p 结点是双亲结点的左孩子,则将 p 的右孩子,右孩子的右孩子…沿着分支找到所有的右孩子,都与 p 的双亲用线连接起来。
- 抹线:抹掉原来二叉树中双亲结点与右孩子之间的连线
- 调整:将结点按照层次排列,形成树结构。
二叉树变树:左孩右右连双亲,去掉原来右孩线。
举个例子
将这棵二叉树转换成树,左孩右右连双亲,去掉原来右孩线。
- 加线:B 为 A 的左孩子,将 B 的右右右孩子每个都与 A 连起来,其余结点同理。
- 抹线:去掉原来的二叉树中双亲结点与右孩子之间的连线,(加了几根线就去掉几根线)。
- 如:去掉B与C之间的线、去掉C与D之间的线、H与I之间的线…
- 调整:同一层的结点的孩子为同一层。
- BCD 为第一层的结点 A 的孩子,放在第二层。
- EFGHI 为第二层的结点的孩子,放在第三层。
森林与二叉树的转换
森林转换成二叉树
二叉树与多棵树之间的关系
- 将各棵树分别转换成二叉树
- 将每棵树的根结点用线相连。
- 以第一棵树的根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构。
森林变二叉树:树变二叉树根相连。
举个栗子
- 通过兄弟相连留长子的方法将所有的树转换成二叉树。
- 所有的树变二叉树之后,将所有的二叉树的根 AEG 连在一条线上。
- 以第一棵树的根节点 A 作为整个二叉树的根,然后以这个根结点旋转,其余的根结点 EG 就变成了 A 的右孩子,以及右孩子的右孩子。
二叉树转换成森林
- 抹线: 将二叉树中根节点与其右孩子的连线,及沿右分支搜索到的所有右孩子间的连线全部抹掉(森林变二叉树时,根结点值留了个A剩下两个成了它的右孩子),使其变成独立的二叉树。
- 还原:将独立的二叉树还原成树。
二叉树变森林:去掉全部右孩线,孤立二叉再还原。
举个例子
去掉全部右孩线,孤立二叉再还原。
- 抹线:
- 将根结点 A 与它的右孩子 E,以及右孩子 E 的右孩子 G之间的线抹除。
- 如果还有右右右孩子,则继续顺着右分支抹线。
- 去掉所有的右孩子线之后整棵二叉树就变成了几棵独立的二叉树。
- 将独立的二叉树通过左孩右右连双亲,去掉原来右孩线,变成树
- 将同一个结点的孩子放在同一层。
5.6.3 树和森林的遍历
树的遍历
- 由于树的每个结点可以有多个子树,所以将根放在哪个位置上就无法确定,导致树没有中序遍历。
- 只有先根遍历和后根遍历以及层次遍历这三种遍历方法。
先根遍历
- 若树不为空,则先访问根节点,然后依次先根遍历根的每棵子树。
后根遍历
- 若树不为空,则先依次后根遍历每棵子树,然后访问根结点。
层次遍历
- 若树不为空,则按照自上而下,自左而右的顺序访问树中每个节点。
举个栗子
森林的遍历
将森林看作由三部分构成,分别遍历这三个部分。
- 森林中 第一棵树的根结点,
- 森林中 第一棵树的子树森林。
- 森林中 其他树构成的森林。
- 先序遍历(123):如果首先遍历森林的第一部分,则称这种遍历为先序遍历。
- 中序遍历(213):如果首先遍历森林的第二部分,则称这种遍历为中序遍历。
先序遍历
若森林不为空,则:
- 首先访问森林中第一棵树的根节点。
- 先序遍历森林中第一棵树的子树森林。
- 先序遍历森林中(除第一棵树之外)其余树所构成的森林。
森林的先序:依次从左至右对森林中的每一棵树进行先根遍历。
中序遍历
若森林不为空,则:
- 中序遍历森林中第一棵树的子树森林。
- 然后访问森林中第一棵树的根节点。
- 中序遍历森林中(除第一棵树之外)其余树所构成的森林。
森林的中序:依次从左至右对森林中的每一棵树进行后根遍历(注意是后根遍历)。
举个栗子