1. 相关概念
采用某种方法遍历二叉树的结果是一个节点的线性序列。
修改空链域改为存放指向节点的前趋和后继节点的地址。这样的指向该线性序列中的前趋和后继节点的指针,称为线索。
创建线索的过程称为线索化。线索化的二叉树称为线索二叉树。
线索二叉树与才用的遍历方法相关,有先序线索二叉树、中序线索二叉树和后序线索二叉树。
节点的存储结构:
ltag | lchild | data | rchild | rtag |
---|
其中,ltag=0表示lchild指向左孩子;ltag=1表示lchild指向前趋节点,即左线索。同理,rtag=0表示rchild指向右孩子;rtag=1表示rchild指向后继节点,即右线索。
线索化二叉树中节点的类型定义如下:
typedef char ElemType;
struct TBTNode{//线索树节点类型定义
ElemType data;//数据域
int ltag,rtag;//线索标记
TBTNode* lchild;//左孩子或者线索指针
TBTNode* rchild;//右孩子或者线索指针
};
2. 线索化二叉树
建立某种次序的线索二叉树的过程:
(1)以该遍历方式遍历一个二叉树;
(2)在遍历的过程中,检查当前访问节点的左右指针域是否为空:若左空,将它改为左线索;若右空,将它改为右线索。
建立中序线索二叉树的算法:
CreatThread(b):对b进行中序线索化,返回头节点指针root;
Thread( p ):对以p为根节点的二叉树子树进行中序线索化。
在线索二叉树中,再增加一个头节点。
在中序遍历中:
p总是指向当前线索化的节点;pre作为全局变量,指向刚刚访问过的节点;pre是p的中序前趋节点,p是pre的中序后继节点。
#include<iostream>
using namespace std;
#include<string>
#define MaxSize 100
typedef char ElemType;
struct TBTNode{//线索树节点类型定义
ElemType data;//数据域
int ltag,rtag;//线索标记
TBTNode* lchild;//左孩子或者线索指针
TBTNode* rchild;//右孩子或者线索指针
};
void Create(TBTNode* &root, string str) { //创建二叉树
TBTNode *St[MaxSize], *p;
root = nullptr;
int k = 0, j = 0, top = -1;
char ch = str[j];
while (ch != '\0') {
switch (ch){
case '(': top++; St[top] = p; k = 1; break;
case ')': top--; break;
case ',': k = 2; break;
default:
p = (TBTNode *)malloc(sizeof(TBTNode));
p->data = ch;
p->lchild = p->rchild = nullptr;
if (root == nullptr) root = p;
else {
switch(k){
case 1: St[top]->lchild = p;break;
case 2: St[top]->rchild = p;break;
}
}
}
ch = str[++j];
}
}
TBTNode* pre;//全局变量
void Thread(TBTNode* &p){//对二叉树p进行中序线索化
if(p!=nullptr){
Thread(p->lchild);//左子树线索化
if(p->lchild==nullptr){
p->lchild = pre;p->ltag=1;//建立当前节点的前趋线索
}
else p->ltag = 0;
if(pre->rchild==nullptr){
pre->rchild=p;pre->rtag=1;//建立前趋节点的后继线索
}
else pre->rtag=0;
pre = p;
Thread(p->rchild);//递归调用右子树线索化
}
}
TBTNode* CreatThread(TBTNode* b){//中序线索化二叉树
TBTNode* root;//创建头节点
root = (TBTNode*)malloc(sizeof(TBTNode));
root->ltag=0;root->rtag=1;root->rchild=b;
if(b==nullptr) root->lchild=root;//空二叉树
else{
root->lchild = b;
pre = root;//pre是p的前趋节点,供加线索用
Thread(b);//中序遍历线索化二叉树
pre->rchild = root;//最后处理,加入指向头节点的线索
pre->rtag = 1;
root->rchild = pre;//头节点右线索化
}
}
3. 遍历线索化二叉树
遍历某种次序的线索化二叉树,就是从该次序下的开始节点出发,反复找到该节点在该次序下的后继节点,直至头节点。
以中序线索二叉树为例,开始节点是根节点的最左下节点。
void ThInOrder(TBTNode* tb){//遍历中序线索二叉树
TBTNode* p = tb->lchild;//p指向根节点
while(p != tb){
while(p->ltag == 0) p = p->lchild;//找开始节点
printf("%c", p->data);
while(p->rtag==1&&p->rchild!=tb){//p有右线索,一直以右线索访问下去
p = p->rchild;
printf("%c", p->data);
}
p = p->rchild;//p没有右线索,转向右孩子节点,重新寻找开始节点
}
}
int main()
{
TBTNode *root = nullptr;
string str = "A(B(D(,G)),C(E,F))";
Create(root, str);
TBTNode* root1;
root1 = CreatThread(root);
ThInOrder(root1);
return 0;
}
4. 哈夫曼树
定义:设二叉树具有n个带权值的叶节点,那么从根节点到各个叶节点的路径长度与相应节点权值的乘积的和叫做二叉树的带权路径长度。
具有最小带权路径长度的二叉树称为哈夫曼树。
构建哈夫曼树
原则:权值越大的叶节点越靠近根节点,权值越小的叶节点越远离根节点。
哈夫曼树的特点:没有度为1的节点,即
n
1
=
0
n_1=0
n1=0,
n
=
n
0
+
n
1
+
n
2
=
n
0
+
n
2
=
2
n
0
−
1
n=n_0+n_1+n_2=n_0+n_2=2n_0-1
n=n0+n1+n2=n0+n2=2n0−1.
哈夫曼编码
规定哈夫曼树中的左分支为0,右分支为1,则从根节点到每个叶节点所经过的分支对应的0和1组成的序列便为该节点对应字符的编码,称为哈夫曼编码。
特点:权值越大的字符编码越短,反之越长。
在一组字符的哈夫曼编码中,不可能出现一个字符的哈夫曼编码是另一个字符哈夫曼编码的前缀。(若是前缀,则第一个字符在第二个字符的路径上,便不可能是叶子节点)