树
1.定义
树是n(n大于等于0)个结点的有限集合。在任意一棵非空树中应满足:
(1)有且仅有一个特定的称为根的结点。
(2)当n大于1时,其余结点可分为m个互不相交的有限集合T1、T2、…、Tm,其中每个集合本身又是一棵树,并且称为根结点的子树。
2.性质
(1)结点数=总度数+1;
(2)区分:
树的度(度为m的树):各结点的度的最大值,任意结点的度小于等于m,至少有一个结点度=m(有m个孩子),且该树至少有m+1个结点。
m叉树:每个结点最多只能有m个孩子的树,允许所有结点的度都小于m,且可以是空树。
(3)高度为h的m叉树至多有 m h − 1 m − 1 \frac{m^{h} - 1}{m - 1} m−1mh−1个结点。(第一层最多m的0次,第二层m的1次,第三层m的2次,进行等比数列求和)
(4)设非空二叉树中度为0(叶子结点)、1和2的结点个数分别是n0、n1、n2,则n0=n2+1.*
3.特殊的树
满二叉树与完全二叉树
满二叉树:一棵高度为h,且含有 2 h − 1 2^{h} - 1 2h−1个结点的二叉树。
完全二叉树:当且仅当其每个结点都与高度为h的满二叉树中编号为1-n的结点一一对应时,称为完全二叉树。
满二叉树一定是完全二叉树,而完全二叉树不一定是满二叉树。
满二叉树 完全二叉树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N7secfxO-1644293252580)(数据结构学习笔记.assets/image-20220206171928471.png)]
完全二叉树的特点:
(1)只有最后两层可能有叶子结点;
(2)最多只有一个度为1的结点;
(3)按层序从1开始编号,结点i的左孩子为2i,右孩子为2i+1,结点i的父节点为 ⌊ i / 2 ⌋ \left\lfloor {i/2} \right\rfloor ⌊i/2⌋。
(4)当 i < ⌊ n / 2 ⌋ i < \left\lfloor {n/2} \right\rfloor i<⌊n/2⌋时为分支结点,当 i > ⌊ n / 2 ⌋ i > \left\lfloor {n/2} \right\rfloor i>⌊n/2⌋时为叶子结点。
对于完全二叉树而言,如果某结点只有一个孩子,那么一定是左孩子。
具有n个结点的完全二叉树的高度h为 ⌈ l o g 2 ( n + 1 ) ⌉ \left\lceil {{log}_{2}\left( n + 1 \right)} \right\rceil ⌈log2(n+1)⌉或 ⌊ l o g 2 n ⌋ + 1 \left\lfloor {{log}_{2}n} \right\rfloor + 1 ⌊log2n⌋+1.
**[推导]**高为h的满二叉树共有 2 h − 1 2^{h} - 1 2h−1个结点,高为h-1的满二叉树共有 2 h − 1 − 1 2^{h-1} - 1 2h−1−1个结点,则:
2 h − 1 − 1 < n ≤ 2 h − 1 2^{h - 1} - 1 < n \leq 2^{h} - 1 2h−1−1<n≤2h−1,则 h − 1 < l o g 2 ( n + 1 ) ≤ h h - 1 < {log}_{2}\left( {n + 1} \right) \leq h h−1<log2(n+1)≤h,故h= ⌈ l o g 2 ( n + 1 ) ⌉ \left\lceil {{log}_{2}\left( n + 1 \right)} \right\rceil ⌈log2(n+1)⌉。
另一边,高为h的完全二叉树至少有 2 h − 1 2^{h-1} 2h−1个结点,至多有 2 h − 1 2^{h} - 1 2h−1个结点,故 2 h − 1 ≤ n ≤ 2 h 2^{h - 1} \leq n \leq 2^{h} 2h−1≤n≤2h,则 h − 1 < l o g 2 n < h h - 1 < {log}_{2}n < h h−1<log2n<h,故h= ⌊ l o g 2 n ⌋ + 1 \left\lfloor {{log}_{2}n} \right\rfloor + 1 ⌊log2n⌋+1.
(5)(性质*的推论)对于完全二叉树,可以由结点数n推出度为0、1、2的结点个数为n0、n1、n2。
**[推导]**完全二叉树最多只有一个度为1的结点,即n1=0或1,而n0=n2+1,即n0+n2一定为奇数,则:
若完全二叉树有2k(偶数)个结点,必有n1=1,n0=k,n2=k-1;
若完全二叉树有2k-1(奇数)个结点,必有n1=0,n0=k,n2=k-1。
二叉排序树
一个二叉树或是空二叉树,是具有以下性质的二叉树:
左子树上所有结点的关键字均小于根节点的关键字;右子树上所有结点的关键字均大于根节点的关键字。
左子树和右子树又各是一棵二叉排序树。
平衡二叉树
树上任一结点的左子树和右子树的深度之差不超过1。平衡二叉树是“胖胖的、丰满的树”,能有更高的搜索效率。
4.二叉树的存储结构
(1)顺序存储
#define MaxSize 100
struct TreeNode{
ElemType value; //结点中的数据元素
bool isEmpty; //结点是否为空
}
定义一个长度为MaxSize的数组t:TreeNode t[MaxSize]
,按照从上至下、从左至右的顺序依次存储完全二叉树中的各个结点。初始化时利用循环将所有结点标记为空:t[i].isEmpty=true;
二叉树的顺序存储中,要把二叉树的结点编号与完全二叉树对应起来。最坏情况是:高度为h且只有h个结点的单支树(所有结点只有右孩子),也至少需要 2 h − 1 2^{h} - 1 2h−1个存储单元。顺序存储只适合存储完全二叉树。
(2)链式存储
typedef struct BiTNode
{
ElemType data; //数据域
struct BiTNode *lchild,*rchild; //左、右孩子指针
}BiTNode,*BiTree;
//定义一棵空树
BiTree root=NULL;
//插入根结点
root= (BiTree)malloc(sizeof(BiTNode));
root->data={1};
root->lchild=NULL;
root->rchild=NULL;
//插入新结点
BiTNode *p= (BiTNode *)malloc(sizeof(BiTNode));
p->data={2};
p->lchild=NULL;
p->rchild=NULL;
root->lchild=p; //作为根结点的左孩子
![img](数据结构学习笔记.assets/JYGT@8_4%7d4A@_Y%7dGZV%7d3CT0.jpg)
由图可知,在n个结点的二叉链表中,除头结点以外,其余结点均有前驱,则有n-1个前驱,故在n个结点的二叉链表共有n+1个空链域。(空链域可用于构造线索二叉树)
若在二叉树中,要实现方便找父结点,则可定义三叉链表:struct BiTNode *parent;
5.二叉树的遍历
先中后序遍历
先序遍历:根左右(NLR) 中序遍历:左根右(LNR) 后序遍历:左右根(LRN)
后序遍历(InOrder)的操作如下:
(1)若二叉树为空,则什么也不做;
(2)若二叉树非空:
【1】先序遍历左子树; 【2】先序遍历右子树; 【3】访问根结点。
//后序遍历
void PostOrder(BiTree T)
{
if(T!=NULL)
{
PostOrder(T->lchild); //递归遍历左子树
PostOrder(T->rchild); //递归遍历右子树
visit(T); //访问根结点
}
}
先序遍历实操步骤:
从根节点出发,画一条路:如果左边还有没走的路,优先往左边走;走到路的尽头(空结点)则往回走,如果左边没路了,就往右边走;如果左、右都没路了,则往上面走。先序遍历时,每个结点都会被路过三次,当第一次路过时访问结点。
![image-20220206215440767](数据结构学习笔记.assets/image-20220206215440767.png)
层序遍历
算法思想:
(1)初始化一个辅助队列;
(2)根结点入队;
(3)若队列非空,则队头结点出队,访问该结点,并将其左、右孩子插入队尾(若有)。
void LevelOrder(BiTree T)
{
LinkQueue Q;
InitQueue(Q); //初始化辅助队列
BiTree p;
EnQueue(Q,T); //将根节点入队
while(!IsEmpty(Q)) //队列不空则循环
{
DeQueue(Q,p); //队头结点出队
visit(p); //访问出队结点
if(p->lchild!=NULL)
EnQueue(Q,p->lchild); //左孩子入队
if(p->rchild!=NULL)
EnQueue(Q,p->rchild); //右孩子入队
}
}
6.线索二叉树
概述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kbYImIZX-1644293252581)(数据结构学习笔记.assets/image-20220207152838458.png)]
线索指向的是该序的前驱/后继。
线索二叉树找前驱/后继
中序线索二叉树
中序遍找历中序后继:
左 根 右
左 根 (左 根 右)
左 根 ((左 根 右) 根 右)
在中序线索二叉树中找到指定结点*p的中序后继next:
(1)若p->rtag==1,则next=p->rchild
(2)若p->rtag==0,则p必有有孩子。
//找到以p为根的子树中,第一个被中序遍历的结点
ThreadNode *Firstnode(ThreadNode *p)
{
//循环找到最左下结点(不一定是叶结点)
while(p->ltag==0) p=p->lchild;
return p;
}
//在中序线索二叉树中找到结点p的后继结点
ThreadNode *NextNode(ThreadNode *p)
{
if(p->rtag==0) return Firstnode(p->rchild);
else return p->rchild; //rtag==1直接返回后继线索
}
//对中序线索二叉树进行中序遍历(利用线索实现的非递归算法)
void Inorder(ThreadNode *T)
{
for(ThreadNode *p=FirstNode(T);p!=NULL;p=NextNode(p))
visit(p);
}
先序线索二叉树
先序遍历找先序后继:(假设有左孩子)
根 左 右
根 (根 左 右) 右
[结论]若p有左孩子,则先序后继为左孩子。
(假设没有左孩子)
根 右
根 (根 左 右)
[结论]若p有右孩子,则先序后继为右孩子。
在中序线索二叉树中找到指定结点*p的先序后继next:
(1)若p->rtag==1,则next=p->rchild
(2)若p->rtag==0,则p必有右孩子。
(仅改用三叉链表或从根开始遍历寻找才能完成)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEDrruad-1644293252582)(数据结构学习笔记.assets/image-20220207163326461.png)]
后序线索二叉树
后序遍历找后序前驱:(假设有右孩子)
左 右 根
左 (左 右 根) 根
[结论]若p有右孩子,则后序前驱为左孩子。
(假设没有右孩子)
左 根
(左 右 根) 根
[结论]若p没有右孩子,则后序前驱为左孩子。
在中序线索二叉树中找到指定结点*p的后续前驱next:
(1)若p->ltag==1,则next=p->lchild
(2)若p->ltag==0,则p必有左孩子。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bbS94EFb-1644293252583)(数据结构学习笔记.assets/image-20220207164018118.png)]
7.树的存储结构
(1)双亲表示法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IeI4n96O-1644293252584)(数据结构学习笔记.assets/image-20220207165231551.png)]
(2)孩子表示法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6uVa92rE-1644293252585)(数据结构学习笔记.assets/image-20220207165311301.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ylDU533f-1644293252586)(数据结构学习笔记.assets/image-20220207165325823.png)]
(3)孩子兄弟表示法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wA1PR5io-1644293252587)(数据结构学习笔记.assets/image-20220207165704155.png)]
类似地,可以实现森林和二叉树的转换。
typedef struct CSNode
{
ElemType data;
struct CSNode *firstchild,*nextsibling; //看作左指针与右指针,代表的是第一个孩子和右兄弟指针
}CSNode,*CSTree;
8.二叉排序树
二叉排序树又称二叉查找树(BST,Binary Search Tree),在该树中,左子树结点值<根结点值<右子树结点值。对该数进行中序遍历,可以得到一个递增的有序序列。
查找
算法
![image-20220207172849063](数据结构学习笔记.assets/image-20220207172849063.png)
查找效率分析
查找长度:在查找运算中,需要对比关键字的次数称为查找长度,反映了查找操作时间复杂度。查找成功的平均查找长度称ASL(Average Search Length)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BCZSFekq-1644293252588)(数据结构学习笔记.assets/image-20220207225108830.png)]
若树高为h,找到最下层的一个结点需要对比h次。最好情况:n个结点的二叉树最小高度为 ⌊ l o g 2 n ⌋ + 1 \left\lfloor {{log}_{2}n} \right\rfloor + 1 ⌊log2n⌋+1,平均查找长度= O( l o g 2 n {log}_{2}n log2n);
最坏情况:每个结点只有一个分支,树高h=结点数n。平均查找长度=O(n)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LRrZUaRw-1644293252589)(数据结构学习笔记.assets/image-20220207225428931.png)]
插入
若原二叉排序树为空,则直接插入结点;否则,若关键字k小于根结点值,则插入到左子树,若关键字k大于根结点值,则插入到右子树。由于新插入的结点一定是叶子,且该算法是递归实现的,故最坏的空间复杂度为O(h)(h为树的高度)。
//在二叉排序树插入关键字为k的新结点(递归实现)
int BST_insert(BSTree &T,int k)
{
if(T==NULL)
{
T=(BSTree)malloc(sizeof(BSTNode));
T->key=k;
T->lchild=T->rchild=NULL;
return 1; //返回1,插入成功
}
else if(k==T->key) //树中存在相同关键字的结点,插入失败
return 0;
else if(k<T->key) //插入到T的左子树
return BST_Insert(T->lchild,k);
else //插入到T的左子树
return BST_Insert(T->rchild,k);
}
删除
先搜索找到目标结点:
(1)若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质(即左子树结点值<根结点值<右子树节点值);
(2)若结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置;
(3)若结点z有左、右两棵子树,则令z的直接后继(或直接前驱)替代z,然后从二叉排序树中删去原来位置上的直接后继(或直接前驱),这样就转换成了第一或第二种情况。
[注]进行中序遍历,可以得到一个递增的有序序列,故z的后继即为z的右子树中最左下结点(该结点一定没有左子树,然后可以参考第二种情况)。
9.平衡二叉树
定义
平衡二叉树(Balanced Binary Tree),简称平衡树(AVL树),其树上任一结点的左子树和右子树的高度之差不超过1。
结点的平衡因子=左子树高-右子树高。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oiOT5X3Q-1644293252591)(数据结构学习笔记.assets/image-20220207225710023.png)]
在插入操作中,每次调整的对象都是“最小不平衡子树”,只要将最小不平衡子树调整平衡,则其他祖先结点都会恢复平衡。
调整最小不平衡子树A
目标:【1】恢复平衡;【2】保持二叉排序树的特性(即左子树结点值<根结点值<右子树结点值)。
只有左孩子才能右旋,只有有孩子才能左旋。
LL插入
在A的左孩子的左子树中插入导致不平衡
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yid56aw0-1644293252592)(数据结构学习笔记.assets/image-20220207230211251.png)]由于在结点A的左孩子(L)的左子树(L)上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。
RR插入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3b6n8fUI-1644293252593)(数据结构学习笔记.assets/image-20220207230528747.png)]
由于在结点A的右孩子(R)的右子树(R)上插入了新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树。
代码思路:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LLInONd1-1644293252594)(数据结构学习笔记.assets/image-20220207231352617.png)]
LR插入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zaVH4hCf-1644293252595)(数据结构学习笔记.assets/image-20220207231636433.png)]
由于在A的左孩子(L)的右子树(R)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置。
RL插入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lPMWfyyH-1644293252596)(数据结构学习笔记.assets/image-20220207231813793.png)]
查找效率分析
在平衡二叉树中,树上任一结点的左子树和右子树的高度之差不超过1,假设以 n h n_{h} nh表示深度为h的平衡树中含有的最少结点数,则有n0=0,n1=1,n2=2,且有 n h = n h − 1 + n h − 2 + 1 n_{h} = n_{h - 1} + n_{h - 2} + 1 nh=nh−1+nh−2+1。
含有n个结点的平衡二叉树的最大深度为 O ( l o g 2 n ) O\left( {log}_{2}n \right) O(log2n),即平衡二叉树的平均查找长度为 O ( l o g 2 n ) O\left( {log}_{2}n \right) O(log2n)。
10.哈夫曼树
一些定义
结点的权:有某些现实含义的数值;
结点的带权路径长度(WPL):从树的根到该结点的路径长度(经过的边数)与该结点上权值的乘积。
在含有n个带权叶结点的二叉树中,带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DmtONLJm-1644293252598)(数据结构学习笔记.assets/image-20220208114559064.png)]
哈夫曼树的构造及其特点
给定n个权值分别为w1, w2,…, wn的结点,构造哈夫曼树的算法描述如下:
(1)将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F;
(2)构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和;
(3)从F中删除刚才选出的两棵树,同时将新得到的树加入F中;
(4)重复步骤2)和3),直至F中只剩下一棵树为止。
特点:
【1】每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大;
【2】哈夫曼树的结点总数为2n − 1;
【3】哈夫曼树中不存在度为1的结点;
【4】哈夫曼树并不唯一,但WPL必然相同且为最优。
哈夫曼编码
可变长度编码:允许对不同字符用不等长的二进制位表示。
若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OcC96bfo-1644293252599)(数据结构学习笔记.assets/image-20220208120601782.png)]
值<根结点值<右子树结点值)。
只有左孩子才能右旋,只有有孩子才能左旋。
LL插入
在A的左孩子的左子树中插入导致不平衡
[外链图片转存中…(img-yid56aw0-1644293252592)]由于在结点A的左孩子(L)的左子树(L)上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。
RR插入
[外链图片转存中…(img-3b6n8fUI-1644293252593)]
由于在结点A的右孩子(R)的右子树(R)上插入了新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树。
代码思路:
[外链图片转存中…(img-LLInONd1-1644293252594)]
LR插入
[外链图片转存中…(img-zaVH4hCf-1644293252595)]
由于在A的左孩子(L)的右子树(R)上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置。
RL插入
[外链图片转存中…(img-lPMWfyyH-1644293252596)]
查找效率分析
在平衡二叉树中,树上任一结点的左子树和右子树的高度之差不超过1,假设以 n h n_{h} nh表示深度为h的平衡树中含有的最少结点数,则有n0=0,n1=1,n2=2,且有 n h = n h − 1 + n h − 2 + 1 n_{h} = n_{h - 1} + n_{h - 2} + 1 nh=nh−1+nh−2+1。
含有n个结点的平衡二叉树的最大深度为 O ( l o g 2 n ) O\left( {log}_{2}n \right) O(log2n),即平衡二叉树的平均查找长度为 O ( l o g 2 n ) O\left( {log}_{2}n \right) O(log2n)。