树还可以表示为嵌套集合(类似韦恩图)、广义表、凹入表示(类似书的目录)。
树的深度:树中结点的最大层次。
有序树:树中结点的各子树从左至右有次序。
二叉树的规律性强,且所有的树都可以转化为唯一对应的二叉树,实现较为简易的运算。
虽然二叉树和树的概念不同,但是有关树的术语对于二叉树都适用。
第i层上至少有1个结点。
深度为k时至少有k个结点。
实现:按满二叉树的结点层次编号,在数组中依次存放二叉树中的数据元素;
#define MAXSIZE 100
Typedef TElemType SqBiTree[MAXSIZE]
SqBiTree bt;
缺点:若二叉树为右单支树,则存储密度过低,空间浪费大;
适用于存储满二叉树和完全二叉树。
图示:
typedef struct BiNode{//二叉链表
TElemType data;
struct BiNode *lchild,*rchild;//左右孩子指针
}BiNode,*BiTree;
typedef struct TriTNode{//三叉链表,用于在某个结点寻找双亲
TelemType data;
struct TriNode *lchild,*parent,*rchild;//增加了指向双亲的指针
}TriTNode,*TriTree;
定义:顺着某一条搜索路径巡访问二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次(又称周游)。
目的:得到树中所有结点的一个线性序列。
用途:是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。
方法:共三种。设访问根结点为D,遍历左子树为L,遍历右子树为R,且规定先左后右,则又三种情况:DLR(先序遍历)、LDR(中序遍历)、LRD(后序遍历)。
Status PreOrderTraverse(BiTree T){//使用二叉链表
if(T==NULL) return OK;//空二叉树
else{
visit(T);//访问根结点
PreOrderTraverse(T->lchild);//递归遍历左子树
PreOrderTraverse(T->rchild);//递归遍历右子树
}
}
Status InOrderTraverse(BiTree T){
if(T==NULL) return OK;
else{
PreOrderTraverse(T->lchild);
visit(T);
PreOrderTraverse(T->rchild);
}
}
Status PostOrderTraverse(BiTree T){
if(T==NULL) return OK;
else{
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
visit(T);
}
}
Status InOrderTraverse(BiTree T){
BiTree p;
InitStack(S);
P=T;
while(p||!StackEmpty(S)){
if(p){
Push(S,p);
p=p->lchild;
}
else{
Pop(S,q);
printf("%c",q->data);
p=q->rchild;
}
}
return OK;
}
层次遍历:对于一棵二叉树,从根结点开始,按从上到下、从左到右的顺序访问每一个结点。使用队列来实现。
typedef struct{
BTNode data[MAXSIZE];//存放队中元素
int front,rear;//队头和队尾指针
}SqQueue;//顺序循环队列类型
void LevelOrder(BTNode *b){
BTNode *p;
SqQueue *qu;
InitQueue(qu);//初始化队列
enQueue(qu,b);//根结点指针进入队列
while(!QueueEmpty(qu)){//队不为空,则循环
deQueue(qu,p);//出队结点p
printf("%c",p->data);//访问结点p
if(p->lchild!=NULL) enQueue(qu,p->lchild);//有左孩子时将其进队
if(p->rchild!=NULL) enQueue(qu,p->rchild);//有右孩子时将其进队
}
}
按先序遍历序列建立二叉树的二叉链表,按顺序读入字符ch:ABC##DE#G##F###
Status CreateBiTree(BiTree &T){
cin>>ch;
if(ch=="#") T=NULL;
else{
if(!(T=new BiTree)) exit(OVERFLOW);//开辟出链表空间
T->data=ch;//生成根节点
CreateBiTree(T->lchild);//构造左子树
CreateBiTree(T->rchild);//构造右子树
}
return OK;
}
int Copy(BiTree T,BiTree &NewT){
if(T=NULL){
NewT=NULL;return 0;
}
else{
NewT=New BiTNode;
NewT->data=T->data;
Copy(T->lchild,NewT->lchild);
Copy(T->rchild,NewT->rchild);
}
}
int Depth(BiTree T){
if(T==NULL) return 0;
else{
m=Depth(T->lchild);
n=Depth(T->rchild);
if(m>n) return(m+1);
else return(n+1);
}
}
int NodeCount(BiTree T){
if(T=NULL)
return 0;
else
return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
}
int LeafCount(BiTree T){
if(T=NULL)
return 0;
if(T->lchild==NULL&&T->rchild==NULL)
return 1;
else
return LeafCount(T->lchild)+LeafCount(T->rchild);
}
用于寻找特定遍历序列中二叉树结点的前驱和后继;
利用二叉链表中的空指针域,左空则将其指向前驱,右空则将其指向后继;
改变指向的指针称为线索,加上线索的二叉树称为线索二叉树,对二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化。
为区分lchild指针和rchild指针到底是指向孩子还是指向前驱后继,对二叉链表中每个结点增设两个标志域ltag和rtag:
typedef struct BiThrNode{
int data;
int ltag,rtag;
struct BiThrNode *lchild,*rchild;
}BiThrNode,*BiThrTree;
在中序遍历的情况下,序列头尾结点会各剩下一个空指针域,为避免这种悬空态,增设一个头结点,对于这个头结点来说:
实现:定义结构数组,存放树的结点,每个结点含两个域;
数据域:存放结点本身信息;
双亲域:指示本结点的双亲结点在数组中的位置;
特点:找双亲容易,找孩子难。
typedef struct PTNode{//结点结构定义
TElemType data;
int parent;//双亲位置域
}PTNode;
#define MAX_TREE_SIZE 100
typedef struct{//树结构定义
PTNode nodes[MAX_TREE_SIZE];
int r,n;//根结点的位置和结点个数
}PTree;
实现:用单链表存储指向每一个结点的指针,每个非叶子结点的指针又作为一个链表的头指针,后接它的孩子结点的指针,如下:
又称二叉树表示法、二叉链表表示法
实现:用二叉链表作树的存储结构,链表中每个结点的两个指针域分别指向其第一个孩子结点和下一个兄弟结点。
typedef struct CSNode{
ElemType data;
struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;
树变二叉树:兄弟相连留长子
加线:在兄弟之间加一条线
抹线:对每一个结点,除了其左孩子外,去除与其余孩子之间的关系
旋转:以树的根结点为轴心,将横线顺时针转45°
左孩右右连双亲,去掉原来右孩线。
加线:若某结点是双亲结点的左孩子,则将其右孩子、右孩子的右孩子、......沿分支找到的所有右孩子,都与该结点的双亲用线连起来
抹线:抹掉原二叉树中双亲与右孩子之间的连线
调整:将结点按层次排列,形成树结构
树变二叉根相连
将各棵树分别转换成二叉树
将每棵树的根结点用线相连
以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型结构
去掉全部右孩线,孤立二叉再还原
抹线:将二叉树中根结点与其右孩子连线、及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树
还原:将孤立的二叉树还原成树
先序、后序、按层次
先序遍历:依次从左至右对森林中的每一课树进行先序遍历
后序遍历:依次从左至右对森林中的每一课树进行后序遍历
引例:
路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。
结点的路径长度:两结点路径上的分支数。
树的路径长度:从树根到每一个结点的路径长度之和,记作:TL
权(Weight):将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。
结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积。
树的带权路径长度:树中所有叶子结点的带权路径长度之和,
满二叉树不一定是哈夫曼树;
哈夫曼树中权越大的叶子离根越近;
具有相同带权结点的哈夫曼树不唯一。
在哈夫曼算法中,初始时有n棵二叉树,要经过n-1次合并最终形成哈夫曼树。
经过n-1次合并产生n-1个新结点,且这n-1个新结点都是具有两个孩子的分支结点。
可见,哈夫曼树中共有2n-1个结点,且其所有的分支结点的度均不为1.
typedef struct{
int weight;
int parent,lch,rch;
}HTNode,*HuffmanTree;
void CreatHuffmanTree(HuffmanTree HT,int n){//构造哈夫曼树——哈夫曼算法
if(n<=1) return;
m=2*n-1;//数组共2n-1个元素
HT=new HTNode[m+1];//0号单元未用,HT[m]表示根结点
for(i=1;i<=m;i++){
HT[i].lch=0;
HT[i].rch=0;
HT[i].parent=0;
}
for(i=1;i<=n;i++)
cin>>HT[i].weight;
//初始化结束,下面开始建立哈夫曼树
for(i=n+1;i<=m;i++){//合并产生n-1个结点————构造哈夫曼树,从第n+1个空间开始暂存合并的结点权值
Select(HT,i-1,s1,s2);//在HT[k](1<=k<=i-1)中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2
HT[s1].parent=i;
HT[s2].parent=i;//表示从F中删除s1和s2
HT[i].lch=s1;
HT[i].rch=s2;//s1和s2分别作为i的左右孩子
HT[i].weight=HT[s1].weight+HT[s2].weight;//i的权值为左右孩子权值之和
}
}
void CreateHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n){//从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
HC=new char*[n+1];//分配n个字符编码的头指针矢量
cd=new char[n];//分配临时存放编码的动态数组空间
cd[n-1]='\0';//编码结束符
for(i=1;i<=n;i++){//逐个字符求哈夫曼编码
start=n-1;
c=i;
f=HT[i].parent;
while(f!=0){//从叶子结点开始向上回溯,直到根结点
start--;//回溯一次start向前指一个位置
if(HT[f].lchild==c) cd[start]='0';//结点c是f的左孩子,则生成代码0
else cd[start]='1';//结点c是f的右孩子,则生成代码1
c=f;
f=HT[f].parent;//继续向上回溯
}//求出第i个字符的编码
HC[i]=new char[n-start];//为第i个字符串编码分配空间
strcpy(HC[i],&cd[start]);//将求得的编码从临时空间cd复制到HC的当前行中
}
delete cd;//释放临时空间
}
编码:
输入各字符及其权值
构造哈夫曼树HT[i]
进行哈夫曼编码
查HC[i],得到各字符的哈夫曼编码
解码:
构造哈夫曼树
依次读入二进制码
读入0,则走向左孩子;读入1,则走向右孩子
一旦到达某叶子时,即可译出字符
然后再从根出发继续译码,直到结束