二叉树的基本概念以及应用(遍历、堆、哈夫曼树、二叉判定树、二叉搜索树、二叉平衡树)

完全二叉树

  在完全二叉树中,只有最下面两层的结点的度可以小于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. 可以是空树
  2. 假如不是空树,任何一个结点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过1
  3. 二叉平衡树是建立在二叉搜索树的基础上,为了维护二叉搜索树的高度,设置了平衡因子

调整二叉平衡树:

  • 从新插入结点回溯,找到第一个非平衡结点标为s,s向下的子树为r
  • s-r为LL型:以r为结点向上提;
  • s-r为LR型:再将r的孩子标为u,以u为根结点,r和s分别为u的孩子(根据左小右大的原则分布)
posted @ 2020-07-21 23:07  Littlejiajia  阅读(2091)  评论(0编辑  收藏  举报