数据结构----树,二叉树
树
概念
树是一个非线性的数据结构,它是一个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 / -
线索二叉树
在二叉链表的基础上,增加两个标志域
leftChild | LTag | data | RTag | rightChild |
---|---|---|---|---|
指向左孩子 | 0 | A | 1 | 指向后继 |
指向前驱 | 1 | A | 0 | 指向右孩子 |
以这种结构构成的二叉链表称为线索链表;
指向结点前驱后继的指针叫做线索;
加上线索的二叉树称为线索二叉树;
对二叉树进行某中次序遍历使其成为线索二叉树的过程叫做线索化
意义:
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