树与二叉树基础知识

5.1树的逻辑结构

5.1.1树的定义和基本术语

1.树的定义

在树中通常将数据元素称为结点

是n个结点的有限集合;当n=0时,称为空树

任意一颗非空树满足以下条件:

  1. 有且仅有一个特定的根节点
  2. 当n>1时,除根结点之外的其余结点被分成m个互不相交的有限集合,其中每个集合又是一棵树,并成为这个根节点的子树

2.树的基本术语

  • 某结点所拥有的子树的个数成为该结点的度;数中各结点的最大值称为该树的度
  • 度为0的结点称为叶子结点,也成为终端结点;度不为0的结点称为分支结点,也成为分终端结点
  • 某结点的子树的根结点称为该结点的孩子结点(例如b是a的孩子结点);反之,该节点称为其孩子结点的双亲结点(a是b的双亲结点);具有同一个双亲的孩子结点互称为兄弟结点(例如b与c)。
  •  路径上经过的边数称为路径长度
  • 如果从结点x到结点y有一条路径,那么x就称为y的祖先,y称为x的子孙
  • 规定根节点的层数为1;对其余结点,若某结点在k ,则其孩子结点在k+1 层;树中所有结点的最大层数称为树的深度,也称为树的高度;树中每一层结点个数的最大值称为树的宽度

 5.1.2树的抽象数据类型定义

ADT  Tree

DataModel

    由一个根结点和若干棵子树构成,树中结点具有层次关系

Operation

    InitTree初始化一棵树

    DestroyTree销毁一棵树

    PreOrder序遍历树

    PostOrder后序遍历树

    LeverOrder层序遍历树

endADT

5.1.3树的遍历操作

 树的遍历是指从根结点出发,按照某种次序风闻树中所有结点,使得每个结点被访问一次且仅访问一次。

树的遍历是一种递归操作,如下为递归,可参考

1.树的前序遍历操作:

A B D E H I F C G

从根节点开始,递归以下操作

若树为空,则空返回;

否则执行:(1)访问根节点(2)按照从左到右的顺序前序遍历根结点的每一棵子树


2.树的后序遍历操作:

D H I E F B G C A

从根节点开始,递归以下操作

若树为空,则空操作返回

否则:(1)按照从左到右的顺序后序遍历根结点的每一棵子树(2)访问根节点

3.层序遍历:A B C D E F G H I

从根结点开始,自上而下依次遍历

5.2树的存储结构

输入:9(节点的个数)

9个结点:ABCDEFG (数组)

8个边数:AB AC BD BE BF CG CH EI

5.2.1双亲表示法

树的双亲表示法用一维数组存储树中各个结点(一般按层序存储)的数据信息以及该结点的双亲在数组中的下标

数组中一个元素对应树中的一个结点,数组元素包括树中结点的数据信息以及该结点的双亲在数组中的下标。 

parent域的值为-1表示该结点无双亲,即该结点是根节点

template <typename DataType>
struct PNode               
{
    DataType data;       
    int parent;         
};
for (i = 0; i < n; i++) {
	pnode[i].data = a[i];
	pnode[i].parent = -1;
}//初始化parent
find(x)//寻找x的角标
for (i = 0; i < n; i++) {
	cin >> par >> child;//输入表示边数
	k = find(child);//child的角标
	pnode[k].parent = find(par);//child的双亲为par的数组坐标
}

 5.2.2孩子表示法

 树的孩子表示法是一种基于链表的存储方法,把每个结点的的孩子排列起来,看成一个线性表,以单链表存储,成为该结点的孩子链表,则n个结点共有n个孩子链表。

struct CTNode                  //孩子结点
{
    int child;
    CTNode *next;
};
template <typename DataType>
struct CBNode                  //表头结点
{
    DataType data;
    CTNode *firstChild;  //指向孩子链表的头指针
};

child:本身所在的数组下标

data:双亲结点的的数值

firstchild:孩子节点的数组下标

 找孩子结点:

i = find(B);//B在list中的位置
p = list[i].firstchild;//p指向B的孩子节点
while (p != nullptr) {
    cout << list[p.child].data;//依次输出B的孩子节点的数据
    p = p->next;
}

找双亲结点:

k = find(D);//D的双亲结点,返回D的数组坐标
for (i = 0; i < n; i++) {
	p = list[i].firstchild;//p指向i的孩子结点
	flag = 0;
	while (p != nullptr) {
		if (p->data == k) {//判断孩子结点是否与D一样
			flag = 1;
			break;
		}
		p = p->next;
	}
	if (flag == 1) {
	cout << list[i].data;
	break;
	}
}

5.2.3孩子兄弟表示法

 的孩子兄弟表示法(二叉链表)链表中的每个结点包括数据域分别指向该结点的第一个孩子和右兄弟的指针

data:存储该节点的数据信息

firstchild:存储该结点第一个孩子结点的存储地址

rightsib:存储该结点的右兄弟结点的存储地址

5.3二叉树的逻辑结构

5.3.1二叉树的定义

二叉树 nn≥0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树右子树的二叉树组成

  1. 每个结点最多有两颗子树,度仅为0、1、2
  2. 二叉树的左右子树不能任意颠倒,若只有一个结点,一定要指明左右子树。
  3. 左右不同,二叉树不同

左斜所有结点都只有左子树的二叉树

右斜树所有结点都只有右子树的二叉树

斜树左斜树和右斜树的统称

斜树特点:

  1.   每一层仅有一个节点
  2. 节点个数与层数相同

满二叉树所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上二叉树

 特点:

  1. 叶子只能出现在最下一层
  2. 只有度为0和度为2的结点
  3. 同样深度的二叉树中结点个数最多
  4. 同样深度的二叉树中叶子结点个数最多

 完全二叉树在满二叉树中,从最后一个结点开始连续去掉任意结点得到的二叉树

特点:

  1. 叶子结点只能出现在最下两层且最下层的叶子结点都集中在二叉树的左面
  2. 完全二叉树中如果有度为 1 的结点,只可能有一个,且该结点只有孩子
  3. 深度为 k 完全二叉树 k-1 上一定是二叉树
  4. 同样结点个数的二叉树中,完全二叉树的深度最小

5.3.2二叉树的基本性质

性质一:

对于多叉树:

n0=1+n2+2n3+...+(m-1)nm

对于完全二叉树,度为1的结点的个数只能为1、0

当度为1的结点个数为1时,由性质一可得n2=(N-2)/2

当度为1的结点个数为0时,由性质一可得n2=(N-1)/2

性质二:

性质三:

性质四: 

性质五: 

性质六: 

给定n个结点,能构成种不同的二叉树

5.3.3二叉树的抽象数据类型定义

ADT  BiTree

DataModel

   二叉树由一个根结点和两棵互不相交的左右子树构成,结点具有层次关系

Operation

    InitBiTree初始化一空的二叉树

    CreatBiTree建立一棵二叉树

    DestroyBiTree销毁一棵二叉树

    PreOrder前序遍历二叉树

    InOrder中序遍历二叉树

    PostOrder后序遍历二叉树

    LeverOrder层序遍历二叉树

endADT

5.3.4二叉树的遍历操作

二叉树的遍历递归操作,一条路访问到底,在返回

 前序遍历:从根节点开始,递归以下操作

若二叉树为空,则空操作返回;否则:

1)访问结点

2遍历结点的左子树

3遍历结点的右子

中序遍历: 从根节点开始,递归以下操作

二叉树为空,则空操作返回;否则:

1中序遍历结点的左子树

2访问结点

3中序遍历结点的右子

后序遍历:从根节点开始,递归以下操作

二叉树为空,则空操作返回;否则:

1后序遍历结点的左子树

2遍历结点的右子

3访问结点

层序遍历:

二叉树的根结点开始,从上至下逐层遍历,在同一层中,则按从左到右的顺序对结点逐个访问

前序:ABDGCEF

中序:DGBAECF

后序:GDBEFCA

前序:ABDECFG

中序:BEDAFGC

后序:EDBGFCA

前序遍历中序遍历能唯一确定这颗二叉树

  • 前序遍历从左往右确定根节点出现的顺序
  • 中序遍历结点左右两侧是左右子树

后序遍历中序遍历能唯一确定这颗二叉树

  • 后序遍历从右往左确定根节点出现的顺序
  • 中序遍历结点左右两侧是左右子树

前序:A B C D E F G H I

中序:B C A E D G H F I

前序: D E F G H I

中序: E D G H F I

 

前序:F G H I

中序:G H F I

5.4二叉树的存储结构

5.4.1顺序存储结构

二叉树的顺序存储结构是用一维数组存储二叉树的结点,用结点的存储位置(下标)表示结点的之间的逻辑关系。

1.将二叉树按完全二叉树进行编号,根节点的编号为1,若某结点i有左孩子,则其左孩子的编号为2i;若某结点i有右孩子,则其右孩子的编号为2i+1

2.将二叉树的结点按照编号顺序存储到一维数组中

顺序存储结构结点的定义:

const MaxSize = 100;
template <typename DataType>
struct SeqBiTree
{
    DataType data[MaxSize];
    int biTreeNum;    
};

5.4.2二叉链表

1.二叉链表的存储结构

二叉树一般采用二叉链表存储:每个结点对应一个链表结点,链表结点主要存放数据信息和指示左右孩子的指针。

data存放该结点的数据信息;lchild存放左孩子的指针;rchilc存放右孩子的指针。

构造链表结点:

template <typename DataType> struct BiNode
{
    DataType data;
    BiNode< DataType > *lchild, *rchild;
};

 n 个结点的二叉链表有2n-(n-1) = n+1 个空指针个空指针

2.二叉链表的实现

InitBiTree初始化一空的二叉树

CreatBiTree建立一棵二叉树

DestroyBiTree销毁一棵二叉树

PreOrder前序遍历二叉树

InOrder中序遍历二叉树

PostOrder后序遍历二叉树

LeverOrder层序遍历二叉树

template <typename DataType>
class BiTree
{
public:
    BiTree( ){root = Creat(root);}
    ~BiTree( ){Release(root);}
    void PreOrder( ){PreOrder(root);}
    void InOrder( ){InOrder(root);}
    void PostOrder( ){PostOrder(root);}
    void LeverOrder( );                   
private:
    BiNode<DataType> *Creat(BiNode<DataType> *bt); 
    void Release(BiNode<DataType> *bt);         
    void PreOrder(BiNode<DataType> *bt);      
    void InOrder(BiNode<DataType> *bt);         
    void PostOrder(BiNode<DataType> *bt);     
    BiNode<DataType> *root;                           
};

(1)前序遍历

递归操作,根节点-左子树-右子树

template <typename DataType>
void BiTree<DataType> :: PreOrder(BiNode<DataType> *bt) 
{
      if (bt == nullptr) return;                         //递归调用的结束条件
      else {
            cout << bt->data;                            //访问根结点bt的数据域
            PreOrder(bt->lchild);                     //前序递归遍历bt的左子树
            PreOrder(bt->rchild);                     //前序递归遍历bt的右子树  
     }
}

(2)中序遍历

递归操作,左子树-根节点-右子树

template <typename DataType>
void BiTree<DataType> :: PreOrder(BiNode<DataType> *bt) 
{
      if (bt == nullptr) return;                         //递归调用的结束条件
      else {
            PreOrder(bt->lchild);                       //前序递归遍历bt的左子树
            cout << bt->data;                            //访问根结点bt的数据域                    
            PreOrder(bt->rchild);                     //前序递归遍历bt的右子树  
     }
}

(3)后序遍历

递归操作,左子树-右子树-根节点

template <typename DataType>
void BiTree<DataType> :: PreOrder(BiNode<DataType> *bt) 
{
      if (bt == nullptr) return;                         //递归调用的结束条件
      else {
            PreOrder(bt->lchild);                       //前序递归遍历bt的左子树
            PreOrder(bt->rchild);                     //前序递归遍历bt的右子树  
            cout << bt->data;                            //访问根结点bt的数据域        
    }
}

(4)层序遍历

在进行层序遍历时,访问每一层的结点后,在对各个结点的左孩子和右孩子顺序访问,这样一层一层进行,先访问的结点其左右孩子也要先访问

1.队列 Q 初始化

2. 如果二叉树非空,将根指针入队

3. 循环直到队列 Q

    3.1 q = 队列 Q 队头元素出队;

    3.2 访问结点 q 数据域;

    3.3 若结点 q 存在左孩子,则将左孩子指针入队;

    3.4 若结点 q 存在右孩子,则将右孩子指针入队;

template <typename DataType>
void BiTree<DataType> :: LeverOrder( )//root为二叉树的根指针
{
      BiNode<DataType> *Q[100], *q = nullptr; //Q用来存储队列
        //q用来存储根指针的位置,根指针不能移动 
      int front = -1, rear = -1;    //队列初始化           
      if (root == nullptr) return; //二叉树判空操作,若为空,则结束
      Q[++rear] = root;  //根指针入队操作                      
      while (front != rear)//当队列非空时
      {
           q = Q[++front];//q接收出队指针,目前根指针
           cout << q->data;   
           if (q->lchild != nullptr)  Q[++rear] = q->lchild;//左子树入队
           if (q->rchild != nullptr)  Q[++rear] = q->rchild;//右子树入队
      }
}

(5)求二叉树的宽度

 构造新的数组结构体

struct QNode {
	Benode* ptr;
	int lno;
};

 代码实现部分:

int front = rear = -1;//初始化队列
QNode Q[maxsize];
if (root) {//判空操作
	Q[++rear].ptr = root;//根指针入队,并且赋值
	Q[rear].lno = 1;//赋值层数
}
Benode* p = nullptr;//接收ptr指针,以防ptr发生移动
int LNO;//用来接收lno
while (front != rear) {//队列非空时操作
	q = Q[++front].ptr;//A出队操作,出队指针传给q
	LNO = Q[front].lno;//A所在层数传给LNO
	if (q->lchild != nullptr) { Q[++rear].ptr = q->lchild; Q[rear].lno = LNO + 1 };
	if (q->rchild != nullptr) { Q[++rear].ptr = q->rchild; Q[rear].lno = LNO + 1 };
	//while循环结束后,所有二叉树均已遍历完毕,此时LNO为二叉树的深度
}
int cnt = 0, width = 0;
for (int i = 1; i <= LNO; i++) {//遍历每一层
	for (int k = 0; k < n; k++) {//遍历每一个节点所在的层数	
		if (Q[k].lno == i) {//计算每一层有几个结点
			cnt++;
		}
	}
	if (width < cnt) {
		width = cnt;//宽度为最大结点数,每一层遍历完成后,都进行比较,然后更新width
	}
}

深度:

核心:左右子树递归找层数,找最大

int BiTree::Depth(BiNode* bt) {
	int i = 0, j = 0;
	if (bt == NULL)return 0;
	else {
		i = Depth(bt->Lchild);
		j = Depth(bt->Rchild);
	}
	if (i >= j) {
		return i + 1;
	}
	else
	{
		return j + 1;
	}
}
#include <iostream>
using namespace std;
struct BiNode {
	char data;
	BiNode* Lchild;
	BiNode* Rchild;
};

class BiTree {
public:
	BiTree() {
		root = Creat();
	}

	~BiTree() {
		Release(root);
	}

	int CountDepth() {
		return Depth(root);
	}

private:
	BiNode* Creat();

	void Release(BiNode* bt);

	int Depth(BiNode* bt);

	BiNode* root;
};
BiNode* BiTree::Creat() {
	BiNode* bt;
	char a = getchar();
	if (a == '#')bt = NULL;
	else {
		bt = new BiNode;
		bt->data = a;
		bt->Lchild = Creat();
		bt->Rchild = Creat();
	}
	return bt;
}
void BiTree::Release(BiNode* bt) {
	if (bt == NULL)return;
	else {
		Release(bt->Lchild);
		Release(bt->Rchild);
		delete bt;
	}
}
int BiTree::Depth(BiNode* bt) {
	int i = 0, j = 0;
	if (bt == NULL)return 0;
	else {
		i = Depth(bt->Lchild);
		j = Depth(bt->Rchild);
	}
	if (i >= j) {
		return i + 1;
	}
	else
	{
		return j + 1;
	}
}
int main() {
	BiTree t;
	cout << t.CountDepth();
	return 0;
}

(6)构造函数——建立二叉树

将二叉树中每个结点的空指针引出一个虚结点,其值为一特定的值(#),以标识其为空,处理过后的二叉树称为拓展二叉树。

拓展二叉树的一个遍历序列就能唯一确定一颗二叉树。

设二叉树中的结点均为一个字符,拓展二叉树的前序遍历序列由键盘输入,root为指向根结点的指针。

首先输入根结点,若输入的是#字符,则表明该二叉树为空树,即root=null;否则输入的字符赋给bt->data,之后依次递归建立他的左子树和右子树。

template <typename DataType>
BiNode<DataType> *BiTree<DataType> :: Creat(BiNode<DataType> *bt)//bt为根节点(理想的)
{
     char ch;
     cin >> ch;          //输入结点的数据信息,假设为字符
     if (ch == ‘#’) bt = nullptr;     //建立一棵空树
     else {
          bt = new BiNode<DataType>;  bt->data = ch;        
          bt->lchild = Creat(bt->lchild);          //递归建立左子树
          bt->rchild = Creat(bt->rchild);          //递归建立右子树
     }
     return bt;
}

(7)析构函数——销毁二叉树

二叉链表是动态存储分配,二叉链表的结点是在程序运行过程中动态申请的,在二叉链表变量退出作用域前,要释放二叉链表的存储空间

template <typename DataType>
void BiTree<DataType> :: Release(BiNode<DataType> *bt)
{
      if (bt == nullptr) return;
      else{
           Release(bt->lchild);   //递归释放左子树
           Release(bt->rchild);  //递归释放右子树
           delete bt;         //释放根结点
     }
}

5.4.3三叉链表

 三叉链表:

5.5森林

5.5.1森林的逻辑结构

森林是m颗互不相交的树的集合

森林是由树构成的,任何一个树,删去根结点就变成了森林,反之,若增加一个根结点,将森林中的每一棵树作为这个根节点的子树,则森林就变成了一棵树

森林的遍历:依次遍历这m棵树就可以遍历整个森林,仅有前序遍历和后序遍历两种遍历方式

前序遍历:ABCDEFGHIJ

后序遍历:BADEFCHJIG

5.5.2树、森林与二叉树转换

1.树转化为二叉树

二叉树根节点的右子树必为空

树的兄弟结点变为二叉树的父子关系

方法:

  1. 加线——树中所有相邻兄弟结点之间加一条线
  2. 去线——对树中每个结点只保留他和第一个孩子结点的连线,删除他与其他孩子的连线
  3. 层次调整——按照二叉树结点的关系进行调整

  • 树的前序遍历等于对应二叉树的前序遍历序列
  • 树的后序遍历等于对应二叉树的中序遍历序列

树的前序遍历:ABEFCDG

树的后序遍历:EFBCGDA

二叉树的前序遍历:ABEFCDG

二叉树的后序遍历:EFBCGDA

2.森林转换为二叉树

设森林中有4颗树,树中结点的个数依次是n1、n2、n3、n4 ,则把森林转换成二叉树后,根结点的左子树上有n1-1个结点,右子树有n2+n3+n4个结点

即第一棵树的根结点作为二叉树根结点,除了根节点外,第一棵树作为左子树,其余各树作为右子树

森林的根结点转换为二叉树的根结点、右孩子、右孩子的右孩子、、、、

方法:

  1. 将森林中的每一颗树转换为二叉树
  2. 将每颗树的根结点视为兄弟,在所有根结点之间加上连线‘
  3. 按照二叉树结点关系进行调整

 

 

  •  森林的前序遍历等于对应二叉树的前序遍历
  • 森林的后序遍历等于对应二叉树的中序遍历

森林的前序遍历:ABCDEFGHIJ

森林的后序遍历:BADEFCHJIG

二叉树的前序遍历:ABCDEFGHIJ

二叉树的中序遍历:BADEFCHJIG

3.二叉树转换为森林 

转换成的二叉树,其根结点无右子树,而森林转换后得到二叉树,其根结点有右子树

二叉树的根结点、右孩子、右孩子的右孩子、右孩子的右孩子的右孩子

方法:

  1. 加线+若某结点x是其双亲y的左孩子,则把结点x的右孩子、有孩子的右孩子....,都与结点y连接起来
  2. 去线——删去源二叉树中所有的双亲结点与右孩子结点的连线
  3. 层次调整

5.6最优二叉树(哈夫曼算法)

5.6.1哈夫曼算法

  • 叶子结点的权值是对叶子节点赋予一个有意义的数值量
  • 从根结点到各个叶子结点的路径长度与相应叶子结点的权值乘积之和称为二叉树带权路径长度

5*1+4*3+2*3+3*3=32

3*1+2*2+4*3+5*3=34

5*1+4*2+3*3+2*3=28

最优二叉树:

  • 二叉树带权路径长度最小,使权值越大的叶子结点越靠近根节点,权值越小的叶子结点远离根节点
  • 不存在度为1的结点

哈夫曼算法:

1. 初始化 n 值构造 n 只有一个根结点的二叉树,得到一个二叉树集合 F{T1T2Tn}

2. 重复下述操作,直到集合 F 只剩下一棵二叉树

        2.1 选取与合并F 选取根结点的权值最小的两棵二叉树分别作为左右子树构造一棵新的二叉树,这棵新二叉树的根结点的权值为其左右子树根结点的权值之和

        2.2 删除与加入F 删除作为左右子树的两棵二叉树,并将新建立的二叉树加入到F

  给定权值集合{245 3}

 采用顺序存储结构存储哈夫曼树,具有n个叶子结点的哈夫曼树中有n-1个分支结点,他们是在n-1次合并过程中生成的,因此哈夫曼树有2*n-1个结点,设置一个数组hufftree[2n-1]来存储各节点信息。

struct ElemType
{
    int weight;//权值
    int parent, lchild, rchild;//双亲结点、左孩子、右孩子的下标
};

哈夫曼树的构造:

  1.  数组hafftree初始化,所有数组元素的双亲,左右孩子都置为-1;
  2. 数组的前n个元素的权值置给定权值;
  3. 循环变量k从n~n-2进行n-1次合并:
    1. 选取两个权值最小的根结点,其下标为i1,i2
    2. 将二叉树i1和i2合并为一颗新的二叉树
void HuffmanTree(ElemType huffTree[], int w[], int n)
{
    int i, k, i1, i2;
    for (i = 0; i < 2 * n - 1; i++)           /*初始化,所有结点均没有双亲和孩子*/
    {
        huffTree[i].parent = -1;   huffTree[i].lchild = -1;   huffTree[i].rchild = -1;
    }
    for (i = 0; i < n; i++)                //赋权值
        huffTree[i].weight = w[i];
    for (k = n; k < 2 * n - 1; k++)       //n-1次合并,最多有2n-1个结点,从最初结点满的时候开始
    {
        Select(huffTree, i1, i2);        /*权值最小的两个根结点下标为i1和i2*/
        huffTree[k].weight = huffTree[i1].weight + huffTree[i2].weight;//权值相加
        huffTree[i1].parent = k;  huffTree[i2].parent = k;//创造双亲结点
        huffTree[k].lchild = i1;  huffTree[k].rchild = i2;//创造左右孩子
    }
}

 ps:找最小与次小的函数

void  HuffmanTree::Select(HTNode* HT, int  len, int& i1, int& i2) {
    int min1, min2;
    min1 = 9999;
    min2 = 9999;
    i1 = -1;//节点中最小权值的下标
    i2 = -1;//节点中倒数第二权值小的下标
    for (int i = 0; i < len; i++) {
        if (HT[i].parent != -1) continue;//该节点已经使用过
        if (HT[i].weight < min1) {
            min1 = HT[i].weight;
            i1 = i;
        }
    }
    for (int i = 0; i < len; i++) {
        if (HT[i].parent != -1) continue;//该节点已经使用过
        if (HT[i].weight < min2 && HT[i].weight > min1) {//保证比min1大
            min2 = HT[i].weight;
            i2 = i;
        }
    }
}

5.6.2哈夫曼编码

  • 表示字符集的简单方法是列出所有字符,给每个字符赋一个二进制位串,称为编码
  • 所有编码都等长,则表示n个不同的字符需要log2n 的下限,称为等长编码
  • 如果每个编码的使用频率相同,则等长编码空间效率最高
  • 如果字符出现的频率不等,可以让频率高的字符采用短的编码,构造不等长编码
  • 如果一组编码中任一编码都不是其他任何一个编码的前缀,则称为无歧义编码,简称前缀编码。——哈夫曼编码
  • 规定哈夫曼编码树的左分支代表0,右分支代表1,则从根节点到叶子结点所经过的路径右0和1的序列便为叶子结点对应的编码,称为哈夫曼编码
  1. 一般来说,题目中出现的哈夫曼树狗仔结构都是左子树权值小于右子树,两子树权值相同时,较矮的子树在左边
  2. 判断哈夫曼编码:
    1. 前缀编码
    2. 二叉树权值路径最小(权大近根)
    3. 度为0或2
  3. 定理:若度为m的哈夫曼树中,叶子结点的个数为n,则非叶子结点的个数为(n-1)/(m-1)取下限。
  4. 类比m进制编码,则需要将m个小值合并成一个,子树依次编为0、1、....

        若为三进制,叶子结点有6个,根据以上定理,(6-1)/(3-1)取下限为1,因此补一个叶子结点且权值为0。三个子树依次编码0、1、2。

5.6.3线索链表

1.线索链表的存储结构

        一个具有n个结点的二叉链表,在2n个指针域中只有n-1个指针域用来存放孩子节点的地址,存在n+1个空指针域,可以利用这些空指针指向该结点的在某种遍历序列的前驱和后继结点

         这些指向前驱和后继结点的指针称为线索,加上线索的二叉链表称为线索链表,二叉树称为线索二叉树

为了区分某结点的指针域存放的是指向孩子的指针还是指向前驱或后继的线索,每个结点再增设两个标志位Ltag和Rtag

template <typename DataType>
struct node
{
    DataType data;
    int ltag, rtag;
    node<DataType>* lchild, rchild;
};

中序遍历为例:DGBAECF

2.二叉链表的线索化

将二叉链表中的空指针改为指向前去或后继的线索,而前驱或后继的信息只有在遍历二叉树时才能使用,具体地对访问的结点bt执行以下操作:

  1. 检查bt的左右指针域,如果为空,则将相应标志置为1
  2. 由于结点bt的前驱刚被访问过,所以若左指针为空,则可令其指向它的前驱,但由于bt的后续尚未访问到,所以他的右指针不能建立线索,要等到访问结点bt的后继指针时才能进行。为实现这个过程,设指针pre始终指向刚刚访问过的结点,即若结点bt指向当前结点,则pre指向它的前驱。
  3. 令pre指向刚刚访问过的结点root
1.若二叉链表为空,则空操作返回
2.对bt的左子树建立线索
3.访问根结点bt执行下述操作
    3.1如果bt没有左孩子,则为bt加上前驱线索
    3.2如果bt没有右孩子,则将bt的右标志置为1
    3.3如果结点pre的右标志为1,则为其加上后继线索
    3.4令pre指向刚访问的结点bt
4.对bt的右子树建立线索
template <typename DataType>
void Bitree<DataType>::Bitree(node<DataType>* bt, node<DataType>* pre) {
    if (bt == null) { return; }
    Bitree(bt -> child, pre);
    if (bt->lchild == NULL) {
        bt->Ltag = 1;
        bt->lchild = pre;
    }
    if (bt->rchild == null) {
        bt->Rtag = 1;
    }
    if (pre->Rtag == 1) {
        pre->rchild = bt;
    }
    pre = bt;
    Bitree(bt->rchild, pre);

}

3.查找后续结点

对于中序线索链表的任意结点,其后继结点有以下两种情况:

  1. 若结点p的右标志为1,表明该结点的右指针是线索,其右指针所指向的结点是他的后继结点
  2. 若结点p的右标志为0,表明该结点有右孩子,无法直接找到其后继结点,它的后继节点应该是遍历其右子树时第一个访问的结点,即右子树中的最左下结点,只需沿着右孩子的左指针向下查找,当某结点的左标志为1时,就是所找的后继结点
template <typename DataType>
Node<DataType>*Bitree<DataType>::*next(Node<DataType>* p) {
    Node<DataType>* q = nullptr;
    if (p->rtag == 1) {
        q = p->rchild;//直接得到后继结点
    }
    else
    {
        q = p->rchild;//工作指针q指向结点q的右孩子
       while (q->ltag == 0) {//查找最左下节点
            q = q->lchild;
        }
    }
    return q;
} 

 4.中序遍历

在中序线索链表进行遍历,只需找到中序遍历序列中的第一个结点,然后依次找每一个结点的后继结点,直至某结点无后继位置

template <typename DataType>
void Bitree<DataType>::Inorder(){
    if (root == NULL) { return; }//如果线索链表为空,则空操作返回
    Node<DataType>* p = root;
    while (p->ltag == 0) {//查找遍历序列的第一个节点
        p = p->lchild;
        cout << p->data;
        while (p->rchild!=nullptr)//当结点p存在后继,依次访问其后继结点
        {
            p = next(p);
            cout << p->data;
        }
    }
} 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yoin.

感谢各位打赏!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值