二叉树的基本概念以及应用(遍历、堆、哈夫曼树、二叉判定树、二叉搜索树、二叉平衡树)
完全二叉树
在完全二叉树中,只有最下面两层的结点的度可以小于2,最下面一层的叶子结点编号连续集中在靠左的位置上。
满二叉树
一棵深度为𝑘,并且有2^𝑘−1个节点的二叉树,为满二叉树。
二叉树的性质
在非空二叉树的第i层上最多有个2^(𝑖−1)节点
深度为𝑘的二叉树最多有2^𝑘−1个节点
具有n个节点的完全二叉树的深度k=⌊log_2 (n) ⌋+1=⌈log_2 (n+1) ⌉
二叉树的遍历
先序遍历:若二叉树为空,则空操作返回;否则①先访问根结点;②然后先序遍历左子树;③先序遍历右子树。
中序遍历:若二叉树为空,则空操作返回;否则①中序遍历根结点的左子树;②接着访问根结点;③中序遍历根结点的右子树。
后序遍历:若二叉树为空,则空操作返回;否则①后序遍历根结点的左子树;②后序遍历根结点的右子树;③最后访问根结点。
层次遍历:先访问第一层的结点,再对第二层的所有结点从左往右依次访问,直到访问完最后一层的所有结点。
(注意:先序、中序、后序都是递归实现即可,层次遍历类似于广度优先搜索,需要使用队列结构)
由“先序+中序”或者“后序+中序”的遍历结果可以得到唯一的一颗二叉树。
先/后序确定当前根节点:先序序列的第一个元素为根节点,后序序列的最后一个元素为根节点;
中序遍历结果确定左右子树的节点范围:在中序序列中找到根节点所在位置,前面的是左子树,后面的是右子树
struct Node///结点
{
int data;
int lc, rc;
}node[maxn*2]; ///还要算上内部节点的开销,所以开两倍maxn
int tot;///统计已经建立的顶点数量
int create(int len, int pre_st, int in_st) ///递归建树
{
if (len == 0)
return -1;
int me = tot++;
node[me].data = pre[pre_st];///先序序列第一个元素为根结点
for (int left = 0; left < len; ++left)
{
if (in[in_st+left] == pre[pre_st])
{
node[me].lc = create(left, pre_st + 1, in_st);///建左子树
node[me].rc = create(len – left – 1, pre_st + left + 1, in_st + left + 1);
return me;
}
}
}
vector<int>post;
void postorder(int root) ///后序遍历
{
if(root!=-1)
{
postorder(node[root].lc);
postorder(node[root].rc);
post.push_back(node[root].data);
}
}
堆
二叉堆是完全二叉树。二叉堆满足二个特性:1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。一般都用数组来表示堆,i结点的父结点下标就为(i-1)/2。它的左右子结点下标分别为2i+1和2i+2。
插入元素
目前数组所存元素0->n-1,新插入的元素都将插入到n位置上,然后再从(n-1)/2父结点开始从下至上调整最大堆。
void Insert_heap(int a[], int i)
{
for (int j = (i – 1) / 2; (j >= 0 && i != 0)&& a[i] > a[j]; i = j, j = (i – 1) / 2)
Swap(a[i], a[j]);
}///(i-1)/2表示父结点
删除元素
对堆的删除都是取出堆顶元素(最大、最小元素),此时先将n-1位置上的元素放到堆顶0号位置,再对0->n-2这样的堆进行从上至下的调整。
///从i节点开始调整,n为节点总数。从0开始计算i节点的子节点为 2*i+1, 2*i+2
void AdjustDown(int a[], int i, int n)
{
int j, temp;
temp = a[i];
j = 2 * i + 1;///左孩子
while (j < n)
{
if (j + 1 < n && a[j + 1] > a[j]) //在左右孩子中找最大的
j++;
if (a[j] <= temp)///最大的孩子也比父节点小
break;
a[i] = a[j]; //把较大的子结点往上移动,替换它的父结点
i = j;
j = 2 * i + 1;
}
a[i] = temp;
}
//在最大堆中删除数
void Delete_heap(int a[], int n)
{
Swap(a[0], a[n – 1]);
AdjustDown(a, 0, n – 1);
}
哈夫曼树
概念:
扩充二叉树:只存在度为0和2的结点组成的二叉树;
结点间的路径长度:树中一个结点到另一个结点所包括的边的数目;
树的内路径长度:从根到所有非叶子结点的路径长度之和;
树的外路径长度:从根到所有叶子结点的路径长度之和;
树的路径长度之和:从根到树 的所有结点的路径长度之和;
结点的带权路径长度:从根到该结点的路径长度与该结点的权值乘积;
树的带权路径长度WPL:树中所有叶子结点的带权路径长度之和
定理:设I和E分别是一棵扩充二叉树的内路径长度和外路径长度,n是树中非叶子结点的数目,则E=I+2n
哈夫曼树的特点:
哈夫曼树的结点个数不能是偶数(因为扩充二叉树只有度为0和2的结点);
一棵哈夫曼树的加权路径长度等于其中所有分支结点(不包括叶子结点)的权值之和;
哈夫曼树是带权路径长度最短的扩充二叉树,权值越大的结点离根结点越近
构建哈夫曼树:
用给定的权值构建n个左右子树都为空的二叉树,所有结点都包含在F集合中;从F中选择权值最小和次最小的两棵树作为左右子树(规定左子树比右子树小),然后构建新的二叉树,新二叉树的权值为左右子树权值之和,在F中删除左右子树权值,把新生成的结点权值加入其中;然后重复上述操作,直到F中只有一个结点即为根结点。
假设哈夫曼树中有n个叶子结点,则哈夫曼树中总共有2n-1个结点。实际存储时采用一维数组存储,0号存储单元不使用,1->n号单元分别存储叶子结点,n+1->2n-1单元存储哈夫曼树形成过程中的非叶子结点。
typedef struct
{
ElementType Data;///结点的数据域
int w; ///结点的权值
int parent,lchild,rchild;
}HFMTNode;
void CreateHFMTNode(HFMTNode Ht[],int N)
{
int i,j,k,lmin,rmin;///lmin和rmin为最小权值的两个结点位置
int min1,min2;///min1和min2为最小两个结点的权值
for(i=1;i<2N;i++)///初始化数组
Ht[i].parent=Ht[i].lchild=Ht[i].rchild[i]=-1;
for(i=N+1;i<2N;i++)///n+1->2n-1的位置存储新生成的非叶子结点
{
min1=min2=MAX;
lmin=rmin=-1;
for(k=1;k<=i-1;k++)///寻找当前最小的两个结点
{
if(Ht[k].parent==-1)///该结点还没被构造二叉树
{
if(Ht[k].w<min1)
{
min2=min1;rmin=lmin;
min1=Ht[k].w;lmin=k;
}
else if(Ht[k].w<min2)
{
min2=Ht[k].w;rmin=k;
}
}
}
Ht[lmin].parent=i;Ht[rmin].parent=i;
Ht[i].w=Ht[lmin].w+Ht[rmin].w;
Ht[i].lchild=lmin;Ht[i].rchild=rmin;
}
}
哈夫曼编码:
给使用频率较高的字符进行较短的编码,是一种可变长编码,同时哈夫曼编码也是一种前缀编码(任何一个字符编码都不是其它字符编码的前缀),可以保证解码不会产生二义性。一边规定左分支为0,右分支为1
二叉判定树
二叉判定树是用于描述解决问题的思路。二叉判定树的根结点是有序表中序号为mid=(n+1)/2的记录,左子树是与有序表1->mid-1的范围存储,根结点的右子树是mid+1]-> n相对应的位置。左子树都比根结点小,右子树都比根结点大。(注意:二叉判定树的形态只与表中元素个数n有关,与表中元素的关键字无关)
平均搜索长度:
搜索成功:As(n)=(每层结点数 × 比较次数(层数) / n
搜索失败:Af(n)=(每层外节点数×比较次数(上一层层数))/(n+1)
二叉搜索树
概念:所有结点的关键字的值都不一样,左子树都比根结点小,右子树都比根结点大,而且根结点的左右子树也都是二叉搜索树。
若中序遍历二叉搜索树,会得到一个关键字递增排列的有序序列。
已知一棵二叉搜索树的先序遍历序列,可以唯一确定一棵二叉树。
删除结点:
待删除的结点没有子树:直接删除
待删除的结点仅有一颗子树:用唯一子树覆盖父结点,原本指向被删除结点的指针改为指向被删除结点的子树
待删除的结点有两颗子树:使待删除结点的左子树代替被删除的结点,将被删除结点的右子树放置于被删除结点的左子树的最右边
二叉平衡树
可以是空树
假如不是空树,任何一个结点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过1
二叉平衡树是建立在二叉搜索树的基础上,为了维护二叉搜索树的高度,设置了平衡因子
调整二叉平衡树:
从新插入结点回溯,找到第一个非平衡结点标为s,s向下的子树为r
s-r为LL型:以r为结点向上提;
s-r为LR型:再将r的孩子标为u,以u为根结点,r和s分别为u的孩子(根据左小右大的原则分布)
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/95113.html