数据结构----树,二叉树

概念

树是一个非线性的数据结构,它是一个n(n>=0)个结点的有限集合

当n=0时,为空树

当n>0时

1、有且仅有一个节点可称为根(Root)
2、除根节点以外的节点可以分成m(m > 0)个互不相交的有限集T₀、T₁、… 、Tm,其中每个子集本身也是一棵树,称为根(Root)的子树

请添加图片描述

结构定义

是一个递归的定义,即在树的定义中由运用到树的概念。

分等级的分类方式都可导致一个树的结构

eg:目录、年级主任-班主任-班长-同学

表示形式
以此图为例

嵌套集合
请添加图片描述

广义表
请添加图片描述

凹入表示法
请添加图片描述

基本术语

结点:包含一个数据元素及若干指向其子树的分支
结点的度:结点拥有的子树个数

如上图,B结点的度为2,D结点的度为1

叶子结点(终端结点):度为0的结点

如上图,E、F、G、I 为叶子结点

分支结点(非终端结点):度不为0的结点

除根结点外,其余分支结点也被称为内部结点

树的度:树内各点的度的最大值

如上图,树的度为3

孩子/双亲结点:结点子树的根称为该结点的孩子,而该结点为其双亲

如上图,B、C、D为A的孩子结点,A为B、C、D的双亲结点 注意!A不是E、F、G、H、I 的双亲结点

兄弟结点:拥有相同双亲结点的结点之前互称为兄弟结点

如上图,B、C、D互为兄弟结点

结点的祖先:从根到该结点所经分支上的所有节点都是该节点的祖先

如上图,A、D、H为I的祖先

结点的子孙:以某结点为根的子树中的任意一个结点都为该结点的子孙

如上图,任意结点都是A的子孙

结点的层次:从根开始定义,根为第一层,根的孩子为第二层,依此类推。

堂兄弟结点:双亲在同一层

如上图,F、G、H互为堂兄弟

树的深度:树中结点的最大层次叫做树的深度

如上图,树的深度为4

如果一棵树中的结点的各子树,从左往右是有次序(不可互换位置),则称该树为有序树。反之,称为无序树

森林:m(m >= 0)棵互不相交的树的集合

存储表示

双亲表示法
每个结点都存放其双亲的下标

优点:知道一个结点可以快速找到其双亲
缺点:寻找孩子需要遍历整个数组

请添加图片描述

#define CAPACITY 10 //数组容量
typedef char TreeNodeType;//结点数据类型
struct TreeNode
{
	TreeNodeType data;//数据域
	int parentIndex;//双亲的下标
};
struct TreeNode tree[CAPACITY];//创建数组

孩子表示法
顺序存储所有结点,每个结点中存放指向其孩子的指针

优点:知道一个结点可以快速找到其孩子
缺点:寻找双亲需要遍历整个数组和所有孩子链表

请添加图片描述

#define CAPACITY 10 //数组容量
typedef char TreeNodeType;//结点数据类型
struct ChildNode
{
	int index;//孩子在数组中的下标
	struct ChildNode* next;//指向下一个孩子
};
struct TreeNode
{
	TreeNodeType data;//数据域
	struct ChildNode* pFirstChild;//存放孩子链表的头指针
};
struct TreeNode tree[CAPACITY];//创建数组

孩子兄弟表示法

口诀:左孩子,右兄弟
优点:结构清晰,拓展方便

请添加图片描述

typedef char TreeNodeType;//结点数据类型
struct TreeNode
{
	TreeNodeType data;//数据域
	struct TreeNode* firstChild;//指向第一个孩子结点
	struct TreeNode* nextBrother;//指向下一个兄弟结点
};

二叉树

概念

每个结点的度最大为2的树

二叉树具有左右之分,其次序不能任意调换

性质

1、每棵二叉树的第i层上最多有 2(i-1) 个结点

证明:
∵ 每个结点的度最大为2
∴ 下一层结点数量最多为上一层结点数量2倍
∵ 根结点有且只有一个
∴ 第一层结点数量为1
∴ 第2层结点数量最多为 1 x 2 个结点,第3层结点数量最多为 1 x 2 x 2 个结点
∴ 归纳可得第i层上最多有 2(i-1) 个结点

2、深度为k的二叉树,结点数量最多为2k-1个结点(k >= 1)

证明:
由性质1可得:
请添加图片描述

3、任何二叉树的叶子节点数(度为0的结点)数量为 n0 , 度为2的结点数量为 n2, 均满足n0 = n2 +1

证明:
根据二叉树的概念,设度为1的结点数量为n₁,总数量为n,可得关系式 n = n0 + n1 + n2
设分支数为B,可得关系式 n = B + 1
∵分支均由n1, n2 导出
n = n1 + 2n2 + 1
联立①②可得,n0 = n2 + 1

满二叉树以及完全二叉树

一棵深度为k,且拥有2k-1个结点(k>=1)的二叉树被称为满二叉树
一棵深度为k,拥有n个结点,当且仅当其每一个结点都和深度为k的满二叉树中编号从1 ~ n-1的结点一一对应的时,称之为完全二叉树

完全二叉树的前k-1层是一个棵满二叉树,第k层是深度为k的满二叉树减去x(0 <= x < 2k-1)个最右边结点
完全二叉树的叶子结点只会在最高两层出现

完全二叉树的性质

4、具有n个结点的完全二叉树的深度为(log2n)向下取整 + 1

证明:
由性质2和完全二叉树的概念可得
2k-1 <= n < 2k
k -1 <= log2n < k
∵ k为的整数
∴ k = (log2n)向下取整 + 1

5、对一棵有n个结点的完全二叉树,对其结点进行按层进行排序(根为1),则有
parent(双亲结点下标) = child(孩子结点下标) / 2,
leftChild(左孩子结点下标) = 2 x parent,
rightChild(右孩子结点下标) = 2 x parent + 1

(均向下取整)

二叉树的存储结构

顺序存储

将二叉树存储在一个数组中,根据性质4可快速找到其双亲和孩子结点下标

完全二叉树:
请添加图片描述
一般二叉树:使用NULL或者0将其补充为一个完全二叉树
请添加图片描述

优点:查找速度快
缺点:
对于非完全二叉树会有空间浪费
增加删除新的结点时可能需要扩容

链式存储

使用链表存储二叉树

二叉链:
请添加图片描述

typedef int BinaryTreeType//数据类型
struct BinaryTreeNode
{
	BinaryTreeType data;//数据域
	struct BinaryTreeNode* leftChild;//左孩子
	struct BinaryTreeNode* rightChild;//右孩子
};

三叉链:请添加图片描述

typedef int BinaryTreeType//数据类型
struct BinaryTreeNode//数据域
{
	BinaryTreeType data;
	struct BinaryTreeNode* leftChild;//左孩子
	struct BinaryTreeNode* rightChild;//右孩子
	struct BinaryTreeNode* parent;//双亲
};

二叉树的遍历

请添加图片描述

层序遍历
从根开始,遍历完一层再到下一层

如上图,遍历顺序为A、B、C、D、E、F、G

前序遍历(先序遍历)
按照根→左→右的顺序沿一定路径经过路径上所有的结点

如上图,遍历顺序为A、B、D、E、C、F、G

bool PrintElement(BinaryTreeType e)
{
	printf(e);
	return true;
}

//使用二叉链表存储结构,Visit是对数据元素操作的应用函数
bool PreOrderTraverse(BinaryTreeNode* root, bool(*Visit)(BinaryTreeType))
{
	if (root != NULL)
	{
		Visit(root->data);//对根进行操作
		PreOrderTraverse(root->leftChild, Visit);//遍历左子树
		PreOrderTraverse(root->rightChild, Visit);//遍历右子树
	}
	else
	{
		return true;//遍历完成
	}
}

中序遍历
按照左→根→右的顺序沿一定路径经过路径上所有的结点

如上图,遍历顺序为D、B、E、A、F、C、G

//使用二叉链表存储结构,Visit是对数据元素操作的应用函数
bool InOrderTraverse(BinaryTreeNode* root, bool(*Visit)(BinaryTreeType))
{
	if (root != NULL)
	{
		InOrderTraverse(root->leftChild, Visit);//遍历左子树
		Visit(root->data);//对根进行操作
		InOrderTraverse(root->rightChild, Visit);//遍历右子树
	}
	else
	{
		return true;//遍历完成
	}
}

后序遍历
按照左→右→根的顺序沿一定路径经过路径上所有的结点

如上图,遍历顺序为D、E、B、F、G、C、A

//使用二叉链表存储结构,Visit是对数据元素操作的应用函数
bool PostOrderTraverse(BinaryTreeNode* root, bool(*Visit)(BinaryTreeType))
{
	if (root != NULL)
	{
		PostOrderTraverse(root->leftChild, Visit);//遍历左子树
		PostOrderTraverse(root->rightChild, Visit);//遍历右子树
		Visit(root->data);//对根进行操作
	}
	else
	{
		return true;//遍历完成
	}
}

已知前序(后序)和中序遍历顺序可以唯一确定一棵二叉树
前序(后序)可以确定一个树的根(前序第一个,后序最后一个),中序可以通过根确定左右子树,根的左边为左子树,右边为右子树

已知一棵二叉树的前序遍历是A、B、D、E、C、F、G,中序是D、B、E、A、F、C、G,求该二叉树的后序和层序。
解:
根据前序遍历确定二叉树T0的根为A,根据中序遍历确定T0的左子树T1为{D、E、B},右子树T2为{F、C、G}
根据前序{B、D、E}确定T1的根为B,根据中序{D、B、E}确定T1的左子树T3为{D},右子树T4为{E}
根据前序{C、F、G}确定T2的根为C,根据中序{F、C、G}确定T2的左子树T5为{F},右子树T6为{G}
可得图为
请添加图片描述
因此,
后序遍历为D、E、B、F、G、C、A
层序遍历为A、B、C、D、E、F、G

逆波兰式

又称为后缀表达式,是一种没有括号,并严格遵循“从左到右”运算的后缀式表达方法

使用二叉树表示 a + b * (c - d) - e / f

请添加图片描述
先序遍历该二叉树得 波兰式(前缀表示)

– + a * b - c d / e f

中序遍历该二叉树得 中缀表示

a + b * c - d - e / f

后序遍历该二叉树得 逆波兰式(后缀表示)

a b c d - * + e f / -

线索二叉树

在二叉链表的基础上,增加两个标志域

leftChildLTagdataRTagrightChild
指向左孩子0A1指向后继
指向前驱1A0指向右孩子

以这种结构构成的二叉链表称为线索链表
指向结点前驱后继的指针叫做线索
加上线索的二叉树称为线索二叉树
对二叉树进行某中次序遍历使其成为线索二叉树的过程叫做线索化

意义:
1、在以二叉链表存储时,可以直接得知其前驱或后继信息
2、提高二叉链表的存储密度(n个节点的二叉链表中必有n+1个空链域)

中序线索链表
虚线为线索,实线为指针
请添加图片描述
存储表示

typedef enum PointerTag
{
Link,
Thread
};//Link == 0:指针;Thread == 1:线索

typedef int BinaryTreeType//数据类型
struct BinaryTreeNode
{
	BinaryTreeTypedata;//数据域
	struct BinaryTreeNode* leftChild;//左孩子指针
	struct BinaryTreeNode* rightChild;//右孩子指针
	PointerTag LTag;//左标志
	PointerTag RTag;//右标志
};

森林与二叉树的关系

树与二叉树的转换

二叉树和树都可以使用二叉链表进行存储,则将二叉链表作为媒介,可导出树和二叉树之间的关系。
请添加图片描述

森林与二叉树的转换

每棵树的根都没有兄弟。因此在森林中,可以将第 i-1 棵树的根作为第 i 棵树的兄弟

请添加图片描述

树的遍历

请添加图片描述

先序(前序)遍历
先访问根结点,再依次先序遍历根的每棵子树

如上图,先序序列为 A、B、E、F、C、G、D、H、I

后序遍历
先依次后序遍历根的每棵子树,再访问根结点

如上图,后序序列为E、F、B、G、C、I、H、D、A

森林的遍历

请添加图片描述

先序遍历(前序遍历)
1、访问森林中第一棵树的根结点
2、先序遍历第一棵树中根结点的子树森林
3、先序遍历除去第一棵树之后剩余的树构成的森林

如上图,前序序列为 A B C D E F G H I J

中序遍历
1、访问森林中第一棵树的根结点的子树森林
2、访问第一棵树的根结点
3、中序遍历除去第一棵树之后剩余的树构成的森林

如上图,前序序列为 B C D A F E H J I G

上图森林对应的二叉树分别进行先序和中序遍历可得到相同的序列
当二叉链表作为树的存储结构时,树的先根遍历和后根遍历可借用二叉树的先序遍历和中序遍历实现

Huffman树

基本概念

路径:由树中从一个结点到另一个结点之间的分支构成
路径长度:路径上的分支数目
树的路径长度:树根到每一个结点的路径长度和
树的带权路径长度(WPL):树的路径带权路径长度为树中所有叶子结点的带权路径长度之和
最优二叉树(Huffman树):n个权值{w1,w2,w3, … ,wn}构造一棵n个结点的二叉树,每个叶子带权wi,其中WPL最小的二叉树

构造过程

请添加图片描述

Huffman编码

需求:
1、对每个字符设计长度不等的编码,且让电文中出现次数较多的字符采用尽可能短的编码
2、任意一个字符的编码都不是另一个字符的编码的前缀

将每个字符出现频率视作权重,字符视作结点,由此进行构建Huffman树。
如上图,假设采用左0右1的方式进行编码,则有
a:0
b:01
c:110
d:111

  • 13
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值