大话数据结构-6.树

6.1 定义

树(Tree)是n(n>=0)个结点的有限集。n=0时为空树。
在任意一棵非空树中:
(1)有且仅有一个特定的称为根(Root)的结点;
(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、……、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。

  • 该定义设计到递归的方法

6.1.1 相关概念

  • 度(Degree)
  • 叶结点(Leaf)
  • 分支结点
  • 孩子
  • 双亲
  • 兄弟
  • 层次
  • 深度
  • 有序树/无序树
  • 森林

6.2 存储结构

6.2.1 双亲表示法

在这里插入图片描述

根据不同需要,设置不同存储结构

  • 双亲表示
下标dataparent
0A-1
1B0
2C0
3D1
4E2
5F2
6G3
7H3
8I3
9J4
  • 改进:添加长子域,如果树的度为2,可分清长子和次子。
下标dataparentfirstchild
0A-11
1B03
2C04
3D16
4E29
5F2-1
6G3-1
7H3-1
8I3-1
9J4-1
  • 改进:增加右兄弟,体现兄弟关系
下标dataparentrightsib
0A-1-1
1B02
2C0-1
3D1-1
4E25
5F2-1
6G37
7H38
8I3-1
9J4-1

6.2.2 孩子表示法

多重链表表示法:每个结点包含多个指针域,每个指针指向一个子树

6.2.2.1 两种方案

方案一:指针个数=树的度

在这里插入图片描述

在这里插入图片描述

方案二:增加位置存储当前节点指针域个数

在这里插入图片描述

在这里插入图片描述

6.2.2.2 具体方法

普通方法

在这里插入图片描述

改进方法(携带双亲信息)

在这里插入图片描述

6.2.1 孩子兄弟表示法(二叉树的来源)

  • 结点机构
    在这里插入图片描述

  • 方法实现(将复杂的树变成一棵二叉树)
    在这里插入图片描述

6.3 二叉树

6.3.1 二叉树定义

  • 二叉树(Binary Tree)是 n (n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的右子树和左子树的二叉树组成。
    (递归)

6.3.2 二叉树特点

  • 度不大于2
  • 左子树和右子树有顺序,次序不能颠倒
  • 只有一棵子树,也要区分左右

6.3.3 特殊二叉树

  • 斜树
  • 满二叉树:每一层皆满
  • 完全二叉树:与满二叉树的不同在于,完全二叉树最后一层不满且叶子结点集中在左部连续位置。

6.3.4 二叉树性质

性质1

  • 在二叉树的第i层上至多有 2i-1个结点(i>=1)

性质2

  • 深度为k的二叉树至多有2k-1个结点(k>=1)
  • 推导过程:
    等比和公式: S n = a 1 q k − 1 q − 1 S_n=a_1\frac{q^k-1}{q-1} Sn=a1q1qk1 a 1 a_1 a1 为等比首项,q为公比,k为等比项数。

性质3

  • 对任何一棵二叉树T,如果其叶子结点数为 n 0 n_0 n0,度为2的结点数为 n 2 n_2 n2,则 n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1
  • 推到过程:
    (1)二叉树结点总数为 n 0 + n 1 + n 2 n_0+n_1+n_2 n0+n1+n2
    (2)分支线总数为 n 0 × 0 + n 1 × 1 + n 2 × 2 n_0\times0+n_1\times1+n_2\times2 n0×0+n1×1+n2×2
    (3)结点总数-1=分支线总数
    (4) n 0 + n 1 + n 2 − 1 = n 1 + 2 n 2 n_0+n_1+n_2-1=n_1+2n_2 n0+n1+n21=n1+2n2 ⇒ \Rightarrow n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1

性质4

  • 具有n个结点的完全二叉树的深度为 ⌊ log ⁡ 2 n ⌋ + 1 \lfloor\log_2n\rfloor+1 log2n+1 ( ⌊ X ⌋ \lfloor X\rfloor X表示不大于X的最大整数)
  • 推导过程:
    (1)深度为k的满二叉树的结点数n一定是 2 k − 1 2^k-1 2k1,即 n = 2 k − 1 n=2^k-1 n=2k1 ⇒ \Rightarrow k = log ⁡ 2 ( n + 1 ) k=\log_2(n+1) k=log2(n+1)
    (2)完全二叉树的节点数一定介于深度为k的满二叉树与深度为(k-1)的满二叉树之间,即: 2 k − 1 − 1 < n ⩽ 2 k − 1 2^{k-1}-1<n\leqslant2^k-1 2k11<n2k1
    (3)n为整数,则 n > 2 k − 1 − 1 n>2^{k-1}-1 n>2k11 ⇒ \Rightarrow n ⩾ 2 k − 1 n\geqslant2^{k-1} n2k1 n ⩽ 2 k − 1 n\leqslant2^k-1 n2k1 ⇒ \Rightarrow n < 2 k n<2^k n<2k ,即 2 k − 1 ⩽ n < 2 k 2^{k-1}\leqslant n<2^k 2k1n<2k ⇒ \Rightarrow k − 1 ⩽ log ⁡ 2 n < k k-1\leqslant\log_2n<k k1log2n<k
    (4) log ⁡ 2 n \log_2n log2n必须为整数,所以 log ⁡ 2 n = k − 1 \log_2n=k-1 log2n=k1 ⇒ \Rightarrow k = ⌊ log ⁡ 2 n ⌋ + 1 k=\lfloor\log_2n\rfloor+1 k=log2n+1

性质5

  • 如果对一棵有n个结点的完全二叉树(其深度为 ⌊ log ⁡ 2 n ⌋ + 1 \lfloor\log_2n\rfloor+1 log2n+1)的结点按层序编号(从第1层到第 ⌊ log ⁡ 2 n ⌋ + 1 \lfloor\log_2n\rfloor+1 log2n+1层,没层从左到右),对任一结点 i( 1 ⩽ i ⩽ n 1\leqslant i\leqslant n 1in)有:
    1.如果 i=1,则结点 i 是二叉树的根,无双亲;如果i>1,则其双亲是节点 ⌊ i 2 ⌋ \lfloor\frac{i}{2}\rfloor 2i
    2.如果2i>n,则结点i无左孩子,结点i为叶子结点;否则其左孩子是结点2i;
    3.如果2i+1>n,则节点i无右孩子;否则右孩子是结点2i+1。

6.3.5 二叉树存储结构

  • 顺序存储:一维数组存储二叉树,数组下标体现节点间逻辑结构
  • 二叉链表:
    在这里插入图片描述

6.3.6 遍历二叉树

  • 按照某种次序访问二叉树所有结点,是每个结点被访问一次且仅被访问一次

6.3.6.1 前序遍历

  • 遍历顺序:中、左、右
/*二叉树的前序遍历递归算法*/
void PreOrderTraverse (BiTree T)
{
	if (T == NULL)
		return;
	printf("%c",T->data);/*显示结点数据,可以更改为其他对结点的操作*/
	PreOrderTraverse(T->lchild);/*再先序遍历左子树*/
	PreOrderTraverse(T->rchild);/*最后先序遍历右子树*/
}

6.3.6.2 中序遍历

  • 遍历顺序:左、中、右
/*二叉树的中序遍历递归算法*/
void InOrderTraverse (BiTree T)
{
	if (T == NULL)
		return;
	InOrderTraverse(T->lchild);/*中序遍历左子树*/
	printf("%c",T->data);/*再显示结点数据,可以更改为其他对结点的操作*/
	InOrderTraverse(T->rchild);/*最后中序遍历右子树*/
}

6.3.6.3 后序遍历

  • 遍历顺序:左、右、中
/*二叉树的后序遍历递归算法*/
void PostOrderTraverse (BiTree T)
{
	if (T == NULL)
		return;
	PostOrderTraverse(T->lchild);/*后序遍历左子树*/
	PostOrderTraverse(T->rchild);/*再后序遍历右子树*/
	printf("%c",T->data);/*最后显示结点数据,可以更改为其他对结点的操作*/
}

6.3.6.4 推导遍历结果

  • 已知前序(或后序)遍历序列和中序遍历序列,可以唯一确定一棵二叉树;
  • 已知前序遍历序列和后续遍历序列,不能唯一确定一棵二叉树。

6.3.7 建立二叉树

  • 与遍历代码相似,只不过将打印节点的地方,改成了生成结点、给结点赋值的操作而已。
/*按前序输入二叉树中节点的值(一个字符)*/
/* #表示空树,构造二叉链表表示二叉树T。*/
void CreateBiTree (BiTree  *T)
{
	TElemType ch;
	scanf("%c",&ch); /*使用`scanf`函数从标准输入(键盘输入)中读取一个字符*/
	if (ch == '#')
		*T=NULL;
	else
	{
		*T=(BiTree)malloc(sizeof(BiTNode)); /*使用malloc函数分配内存来创建新的节点*/
		if(!*T)              /*内存分配失败(即`malloc`函数返回`NULL`),*/
			exit(OVERFLOW);  /*则调用`exit`函数退出程序。*/ 
		(*T)->data=ch; /*生成根结点*/
		CreateBiTree(&(*T)->lchild); /*构造左子树*/
		CreateBiTree(&(*T)->rchild); /*构造右子树*/
	}
}

6.3.8 线索二叉树

  • 利用空指针存储前驱后继地址

  • 增加ltag和rtag 2个标识域,用于区分孩子和前驱后继
    在这里插入图片描述
    标识为0指向孩子,为1指向前驱后继

  • 线索化成就是在遍历的过程中修改空指针的过程,代码如下:

BiThTree pre; /*全局变量,始终指向刚刚访问过的结点*/
/*中序遍历进行中序线索化*/
void InThreading(BiThrTree p)
{
	if(p)
	{
		InThreading(p->lchild); /*递归左子树线索化*/
		if(!p->lchild) /*没有左孩子*/
		{
			p->LTag=Thread; /*前驱线索,Thread表示该结点有左孩子线索,也就是定义为1*/
			P->lchild=pre; /*左孩子指针指向前驱*/
		}
		if(!pre->rchild) /*前驱没有右孩子*/
		{
			pre->RTag = Thread; /*后续线索*/
			pre->rchild = p; /*前驱右孩子指针指向后继(当前结点p)*/
		}
		pre=p; /*保持pre指向p的前驱*/
		InThreading(p->rchild); /*递归右子树线索化*/
	}
}
  • 线索二叉树遍历图示、代码如下:

在这里插入图片描述

  • link为0,thread为1
/*T为头结点,头结点左链lchild指向根结点,头结点右链rchild指向中序遍历的*/
/*最后一个结点。中序遍历二叉线索链表表示的二叉树T*/
Status InOrderTraverse_Thr(BiThrTree T)
{
	BiThrTree p; /*定义名为 `p` 的 `BiThrTree` 类型的指针*/
	p = T->lchild; /*p指向根结点*/
	while (p!=T) /*空树或遍历结束时,p==T*/
	{
		while (P->LTag==Link) /*当LTag==0时循环到中序序列第一个结点*/
			p=p->lchild; /*执行循环A→B→D→H,到达H后,H->LTag!=link(即不等于0),退出循环*/
		printf("%c",p->data); /*显示H结点数据,可以更改为其他对节点操作*/
		while(p->RTag==Thread && p->rchild!=T)/*RTag==Thread就是等于1的意思*/
		{
			p=p->rchild;
			printf("%c",p->data);
		}
		p=p->rchild; /*p进至其右子树根*/
	}
	return OK;
}

注意:代码中出现2次p=p->lchild,因tag值不同而含义不同,第一次为后继,第二次为右孩子。

6.4 树、森林与二叉树转换

  • 树转二叉树,步骤如下:

    1. 所有兄弟之间加线;
    2. 仅保留双亲与长子之间的连线,删除与其他孩子的连线;
    3. 调整外形,使二叉树更具辨识度。
  • 森林转二叉树,步骤如下:

    1. 将每棵树转为二叉树;
    2. 首棵树不动,其它树依次将根结点接为首棵树根结点的右孩子。
  • 二叉树转树,步骤为树转二叉树的逆过程。

  • 二叉树转森林,步骤为森林转二叉树的逆过程。

  • 树与森林的遍历

    1. 树的先跟遍历、森林的前序遍历对应二叉树的前序遍历;
    2. 树的后跟遍历、森林的后序遍历对应二叉树的中序遍历。
      第2条理解有些难度,很容易跟二叉树的后序遍历对应。为什么是跟中序对应,原因是二叉树到树、森林的形变导致。

6.5 赫夫曼树及其应用

  • 赫夫曼树,带权路径长度WPL最小的二叉树,即最优二叉树。

  • 如何搭建赫夫曼树:

    1. 把有权值的叶子结点按权值从小到大排序;
    2. 取出其中权值最小的2个叶子结点组成一棵新的二叉树结点为N1,权值为2叶子结点之和,将N1放入排序池中重新排序;
    3. 再从排序池中取出其中最小的两个节点组建新的二叉树,以此类推直至结束。
      注意:从排序池中取出的结点就不再放回。
  • 赫夫曼树重要应用:赫夫曼编码

    1. 构建好赫夫曼树后,将路径上权值按左右分别替换为0、1,从根结点至叶子结点所过路径组成的二进制码,即为赫夫曼编码。
    2. 赫夫曼编码长短不一,为避免编码混淆,则必须是任一字符的编码都不是另一个字符的编码的前缀,即前缀编码。
    3. 赫夫曼编码、解码过程均需相同的赫夫曼树,即收发双方必须约定好同样的赫夫曼编码规则。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值