zoukankan      html  css  js  c++  java
  • 【数据结构】二叉树

    表示

    typedef struct TNode *Position;
    typedef Position BinTree; /* 二叉树类型 */
    struct TNode{ /* 树结点定义 */
        ElementType Data; /* 结点数据 */
        BinTree Left;     /* 指向左子树 */
        BinTree Right;    /* 指向右子树 */
    };
    

    性质

    • 第i层至多有2^(i-1)个节点
    • 深度为k=》至多有2^k-1个节点
    • 终端节点数为n0,度为2的节点数为n2=》n0=n2+1
    • 完全二叉树具有n个节点=》深度为[log2n]+1(向上取整)
    • 完全二叉树按层序编号:
    1. i>1,则其双亲为[i/2](向上取整)
    2. 2i>n,则i没有左孩子;否则其左孩子为2i
    3. 2i+1>n,则i没有右孩子;否则其右孩子为2i+1

    遍历二叉树

    概念

    1. 先序遍历: a、访问根节点;b、前序遍历左子树;c、前序遍历右子树。
    2. 中序遍历: a、中序遍历左子树;b、访问根节点;c、中序遍历右子树。
    3. 后序遍历: a、后序遍历左子树;b、后续遍历右子树;c、访问根节点。

    举例

    • 先序序列:-+a*b-cd/ef
    • 中序遍历:a+b*(c-d)-e/f
    • 后序遍历:abcde-*+ef/-
    • 按层遍历:-+/a*efb-cd
    • 形象的图示

    实现

    //中序遍历
    void InorderTraversal( BinTree BT )
    {
        if( BT ) {
            InorderTraversal( BT->Left );
            /* 此处假设对BT结点的访问就是打印数据 */
            printf("%d ", BT->Data); /* 假设数据为整型 */
            InorderTraversal( BT->Right );
        }
    }
    //先序遍历
    void PreorderTraversal( BinTree BT )
    {
        if( BT ) {
            printf("%d ", BT->Data );
            PreorderTraversal( BT->Left );
            PreorderTraversal( BT->Right );
        }
    }
    //后序遍历
    void PostorderTraversal( BinTree BT )
    {
        if( BT ) {
            PostorderTraversal( BT->Left );
            PostorderTraversal( BT->Right );
            printf("%d ", BT->Data);
        }
    }
    //按层遍历
    void LevelorderTraversal ( BinTree BT )
    { 
        Queue Q; 
        BinTree T;
        if ( !BT ) return; /* 若是空树则直接返回 */
        
        Q = CreatQueue(); /* 创建空队列Q */
        AddQ( Q, BT );
        while ( !IsEmpty(Q) ) {
            T = DeleteQ( Q );
            printf("%d ", T->Data); /* 访问取出队列的结点 */
            if ( T->Left )   AddQ( Q, T->Left );
            if ( T->Right )  AddQ( Q, T->Right );
        }
    }
    

    还原二叉树

    要已知两种遍历结果,还原一棵二叉树:前序/后序/按层+中序遍历。

    也就是说,中序遍历是必要的。

    本部分内容在我的另一篇博客有详细解释:https://www.cnblogs.com/fighterkaka22/p/14203479.html

    波兰表达式

    观察前面遍历二叉树的举例,可以发现,该三个序列分别为波兰式、正常表达式和逆波兰式。

    线索二叉树

    背景

    当我们希望得到二叉树中某一个结点的前驱或者后继结点时,普通的二叉树是无法直接得到的,只能通过遍历一次二叉树得到。每当涉及到求解前驱或者后继就需要将二叉树遍历一次,非常不方便。因此考虑,是否能够改变原有的结构,将结点的前驱和后继的信息存储进来。

    原理

    记ptr指向二叉链表中的一个结点,以下是建立线索的规则:

    (1)如果ptr->lchild为空,则存放指向中序遍历序列中该结点的前驱结点。这个结点称为ptr的中序前驱;

    (2)如果ptr->rchild为空,则存放指向中序遍历序列中该结点的后继结点。这个结点称为ptr的中序后继;

    显然,在决定lchild是指向左孩子还是前驱,rchild是指向右孩子还是后继,需要一个区分标志的。因此,我们在每个结点再增设两个标志域ltag和rtag,注意ltag和rtag只是区分0或1数字的布尔型变量,其占用内存空间要小于像lchild和rchild的指针变量。

    二叉树线索化

    中序遍历进行二叉树线索化代码:

    void InThreading(BiThrTree B,BiThrTree *pre) {
      if(!B) return;
     
      InThreading(B->lchild,pre);   
    //--------------------中间为修改空指针代码---------------------
     
      if(!B->lchild){                   //没有左孩子 
        B->LTag = Thread;               //修改标志域为前驱线索
        B->lchild = *pre;               //左孩子指向前驱结点
      }
     
      if(!(*pre)->rchild){              //没有右孩子
        (*pre)->RTag = Thread;          //修改标志域为后继线索
        (*pre)->rchild = B;             //前驱右孩子指向当前结点
      }
     
      *pre = B;                         //保持pre指向p的前驱
    //---------------------------------------------------------
      InThreading(B->rchild,pre);
    }
    

    遍历线索二叉树

    //非递归遍历线索二叉树
    Status InOrderTraverse(BiThrTree T) {
      BiThrNode *p = T->lchild;
      while(p!=T){
        while(p->LTag==Link) p = p->lchild;    //走向左子树的尽头
        printf("%c",p->data );
        while(p->RTag==Thread && p->rchild!=T) {  //访问该结点的后续结点
          p = p->rchild; 
          printf("%c",p->data );
        }
        p = p->rchild;
      }
      return OK;
    }
    

    最优二叉树(赫夫曼树)

    概念

    • 结点的带权路径长度:从结点到树根之间的路径长度与结点上权的乘积。

    • 树的带权路径长度:所有叶子结点的带权路径长度之和。

    • 最优二叉树(赫夫曼树):WPL最小的二叉树。

    性质

    • 包含 n 个叶子结点的赫夫曼树中共有 2n – 1 个结点。
    • 哈夫曼树的结点的度数为 0 或 2, 没有度为 1 的结点。
    • 具有相同带权结点的哈夫曼树不惟一。

    赫夫曼算法

    1. 根据给定的n个权值{w1,w2,…,wn}构成二叉树集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为wi的根结点,其左右子树为空。

    1. 在F中选取两棵根结点权值最小的树作为左右子,构造一棵新的二叉树,且置新的二叉树的根结点的权值为左右子树根结点的权值之和

    2. 在F中删除这两棵树,同时将新的二叉树加入F中。

    3. 重复2、3,直到F只含有一棵树为止.(得到哈夫曼树)

    赫夫曼编码

    前缀编码:任意一个字符的编码都不是另一个字符的编码的前缀。

    举例:如果需传送的电文为 ‘ABACCDA‘:

    编码: A:0, C:10,B:110,D:111

    任意一个叶子结点都不可能在其它叶子结点的路径中。

    非二叉树

    实际上,所有的树都可以通过孩子兄弟表示法表示为二叉树。

  • 相关阅读:
    itoa
    sprintf用法 [转载]
    atoi 与 itoa的实现
    数组排序总结(冒泡,选择,插入,希尔)
    XML入门精解之文件格式定义(DTD)
    malloc()函数的工作机制 结构体的总结
    字符串指针与字符数组(ZT)
    sprintf函数你了解有多深!
    sprintf
    sql ldr 笔记
  • 原文地址:https://www.cnblogs.com/fighterkaka22/p/14209510.html
Copyright © 2011-2022 走看看