树和二叉树
线索二叉树
定义
我们遍历二叉树的得到一个线性序列,指向该线性序列的“前驱”和“后继”的指针,称为“线索”。
线索链表是在二叉链表的基础上增加两个标志域,如果该结点的左子树不空,那么Lchild域的指针指向其左子树,并且将左标志域的值设为“指针Link”,说明Lchild域中是指向左子树;否则,Lchild域的指针指向遍历得到的线性序列的“前驱”,将左标志域的值定为“线索Thread”。Rchild同样,但是在没有右子树时,指向“后继”
这种记录方式可以使得我们很快找到一个结点的“前驱”和“后继”
typedef enum{Link,Thread} PointerThr;
//Link == 0:指针,Thread == 1:线索
typedef struct BiThrNod{
TElemType data;
struct BiThrNode *lchild,*rchild; //左右指针
PointerThr Ltag,Rtag;//左右标志
}BiThrNode,*BiThrTree;
遍历
先序遍历:
如果结点因为有右孩子导致无法找到其后继结点,如果结点有左孩子,则后继结点是其左孩子;否则,就一定是右孩子。
中序遍历:
如果一个结点有左子树,那么其前驱就是左子树的最右结点。 如果没有左子树,通过线索指明前驱。
如果一个结点有右子树,那么其后继就是右子树的最左结点。 如果没有右子树,通过线索指明后继。
void InOrderTraverse_Thr(BiThrTree T){
p = T-> lchild; //p指向根结点
while(p!=T){
while(p->Ltag==Link) p = p->lchild;//如果左指针域指向左子树,则中序遍历的第一个结点为整个树最左下结点
printf(p->data);
while(p->Rtag==Thread && p->rchild!=T){
p = p->rchild;//当没有右子树
printf(p->data);
}
p = p->rchild;//按照中序遍历的规律,找其右子树中最左下的结点,也就是继续循环遍历
}
}
线索化
通过在中序遍历的过程中,保存“前驱”和“后继”信息,进行中序线索化
就是在遍历二叉树的过程中,将二叉链表中的空指针改为指向直接前趋或者直接后继的线索。
在遍历过程中,如果当前结点没有左孩子,需要将该结点的 lchild 指针指向遍历过程中的前一个结点,所以在遍历过程中,设置一个指针(名为 pre ),时刻指向当前访问结点的前一个结点。
BiThrTree pre=NULL;
void InThreading(BiThrTree p){
//如果当前结点存在
if (p) {
InThreading(p->lchild);//递归当前结点的左子树,进行线索化
//如果当前结点没有左孩子,左标志位设为1,左指针域指向上一结点 pre
if (!p->lchild) {
p->Ltag=Thread;
p->lchild=pre;
}
//如果 pre 没有右孩子,右标志位设为 1,右指针域指向当前结点。
if (pre&&!pre->rchild) {
pre->Rtag=Thread;
pre->rchild=p;
}
pre=p;//pre指向当前结点
InThreading(p->rchild);//递归右子树进行线索化
}
}
树和森林的表示方法
树的三种存储结构
- 双亲表示法
#define MAX_TREE_SIZE 100
typedef struct PTNode{
Elem data;//结点的值
int parent;//双亲所在的位置
}PTNode;
typedef struct{
PTNode nodes[MAX_TREE_SIZE];
int r,n;//根结点的位置和结点总数
}PTree;
- 孩子链表表示法
在每一个结点的后面使用链表结构,添加孩子结点的位置
#define MAX_SIZE 20
#define TElemType char
//孩子表示法
typedef struct CTNode {
int child;//链表中每个结点存储的不是数据本身,而是数据在数组中存储的位置下标
struct CTNode * next;
}ChildPtr;
typedef struct {
TElemType data;//结点的数据类型
ChildPtr* firstchild;//孩子链表的头指针
}CTBox;
typedef struct {
CTBox nodes[MAX_SIZE];//存储结点的数组
int n, r;//结点数量和树根的位置
}CTree;
- 孩子-兄弟表示法
每个结点包含三个值:
节点的值;指向孩子节点的指针;指向兄弟节点的指针;
#define ElemType char
typedef struct CSNode{
ElemType data;
struct CSNode * firstchild,*nextsibling;
}CSNode,*CSTree;
通过孩子兄弟表示法,任意一棵普通树都可以相应转化为一棵二叉树,换句话说,任意一棵普通树都有唯一的一棵二叉树于其对应。
森林与二叉树&&树与二叉树
转化
在这一部分主要考察将森林转化为二叉树和将树转为二叉树,在这个转化过程中,我们重新定义了左右子树,将左子树定为孩子,右子树定为兄弟,这个孩子兄弟表示法相同
- 树转换为二叉树
将所有处于同一层的相邻兄弟结点之间加一条连线;对于每个结点只保留从左数第一个孩子与该结点之间的连线,其他孩子结点与该结点之间的连线全部去除;将转换后的树进行一定旋转,让它变好看一些 - 森林转换为二叉树
由于森林中有很多棵树,我们先将每棵树都转换为二叉树;而后第一棵树不动,依次将后一棵树的根结点作为前一棵树根结点的右子树连在一起,直到连接完森林中所有的树
遍历
- 树的遍历只有先根和后根,没有中序遍历
- 森林的遍历只有先序和中序遍历,没有后序遍历
先序遍历时,先访问森林中的第一棵树的根结点,然后先序访问第一棵树的子树,最后依次从左到右对森林中每一棵树进行先根遍历;
中序遍历时,先后根遍历第一棵树的子树,然后访问第一棵树的根结点,最后依次从左到右对森林中每一棵树进行后根遍历
哈夫曼树与哈夫曼编码
带权路径长度:树中所有叶子结点的带权路径长度之和,也就是所有叶子结点上的权值乘以它所在层次(根为0)的和
哈夫曼树是一棵带权路径长度取最小值的m叉树,也就是“最优树”
构造哈夫曼树
从给定的权值中,挑选m个最小值,构成一棵m叉树,计算m个最小值的总和,然后从权值序列中,删除选定的最小值,将计算后的总和加入(及构造出来的m叉树)。重复此过程,直到只剩一个权值,即为构造的哈夫曼树
Huffman树只有度为0和度为2的结点
哈夫曼编码
哈夫曼编码就是在哈夫曼树的基础中,将左子树变为0,右子树编为0,(也可以颠倒)对叶子结点进行编码