博客园  :: 首页  :: 新随笔  :: 管理

6.二叉树

Posted on 2020-07-03 19:42  wsg_blog  阅读(157)  评论(0编辑  收藏  举报

index 数据结构与算法

二叉树

二叉树具有五种基本形态:

空二叉树、只有一个根节点、根结点只有左子树、根结点只有右子树、根节点既有左子树又有右子树

特殊二叉树:

  • 斜树 斜树一定要是斜的,但是往哪斜还是有讲究。所有结点都只有左子树的二叉树叫左斜树;所有结点都只有右子树的二叉树叫右斜树。这两者统称为斜树
  • 满二叉树 在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上。
  • 完全二叉树 其所有结点与同样深度的满二叉树,它们按层序编号相同的结点,是一一对应的。

二叉树的性质

二叉树有一些需要理解并记住的特性,以便我们更好的使用它。

  • 在二叉树的第i层上至多有2^(i-1)个结点(i>=1)
  • 深度为k的二叉树至多有2^k -1个结点(k>=1)
  • 对任何一颗二叉树T,如果其终端结点数位n0,度为2的结点数为n2,则n0=n2+1
  • 具有n个结点的完全二叉树的深度为log_2 n +1
  • 如果对一颗有n个结点的完全二叉树(其深度为)的结点按层序编号(从第1层到第层,每层从左到右),对任一结点i(1<=i<=n)

二叉树的存储结构

顺序存储结构

前面谈到树的存储结构,并且谈到顺序存储对树这种一对多的关系结构实现起来比较困难。但是二叉树是一种特殊的树。
由于它的特殊性,使得用顺序存储结构也可以实现。
二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系,
比如双亲与孩子的关系,左右兄弟的关系等。

先来看看完全二叉树的顺序存储,一颗完全二叉树...

                A
        B               C
    D       E       F       G
  H   I   J

将这颗二叉树存入数组中,相应的下标对应其同样的位置,
1 2 3 4 5 6 7 8 9 10
A B C D E F G H I J

当然对于一般的二叉树,尽管层序编号不能反映逻辑关系,但是可以将其按完全二叉树编号,只不过,把不存在的结点设置为"^"而已。

                A
        B               C
    ^       E       ^       G
  ^   ^    J
1   2   3   4   5   6   7   8   9   10
A   B   C   ^   E   ^   G   ^   ^   J

虽然可以表示,但会有空间的浪费,对于数据比较多的树来说,树的顺序存储结构一般只用于构建完全二叉树

链式存储结构

既然顺序存储适用性不强,我们就要考虑链式存储结构。二叉树,每个结点最多有两个孩子,所以它设计一个数据域和两个指针域是比较自然的想法,
我们称这样的链表叫做二叉链表。

typedef struct BiTNode
{
    //结点数据
    TElemType data;
    //左右孩子指针
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

就如同树的存储结构中讨论的一样,如果有需要,还可以再增加一个指向双亲的指针域,那样就称之为三叉链表。由于与树的存储结构类似,这里就不详述了。

遍历二叉树

二叉树的遍历方式可以很多,如果我们限制了 从左到右二的 习惯方式,那么主要分为四种:
1.前序遍历 ABDGH-CEIF
2.中序遍历 GDHBAE-ICF
3.后序遍历 GHDBIEFCA
4.层序遍历 ABCDEFGHI

                A
            B       C
          D       E     F
        G   H       I

有同学会说,研究这么多遍历的方法干什么呢?
我们用图形的方式表现树的结构,应该说是非常直观和容易理解,但是对于计算机来说,它只有循环、判断等方式来处理,也就是说,
它只会处理线性序列,而我们刚才提到的四种遍历方法,其实都是在把树中的结点变成某种意义的线性序列,这就给程序实现带来了好处。
另外不同的遍历提供了对结点依次处理的不同方式,可以在遍历过程中对结点进行各种处理。

前序遍历算法

二叉树的定义是递归的方式,所以,实现遍历算法也可以采用递归,而且极其简洁明了。

//二叉树的前序遍历递归算法
void PreOrderTraverse(BiTree T)
{
    if(T == NULL)
        return;
    //显示结点数据,可以更改为其他对节点操作
    printf("%c", T->data);
    //再先序遍历左子树
    PreOrderTraverse(T->lchild);
    //再后续遍历右子树
    PreOrderTraverse(T->rchild);
}
中序遍历算法
void InOrderTraverse(BiTree T)
{
    if (T == NULL)
        return;
    //中序遍历左子树
    InOrderTraverse(T->lchild);
    //显示节点数据,可以更改为其他对节点操作
    printf("%c", T->data);
    //最后中序遍历右子树
    InOrderTraverse(T->rchild);
}
后续遍历算法
void PostOrderTraverse(BiTree T)
{
    if (T == NULL)
        return;
    //先后序遍历左子树
    PostOrderTraverse(T->lchild);
    //在后序遍历右子树
    PostOrderTraverse(T->rchild);
    //显示结点数据,可以更改为其他对结点操作
    printf("%c". T->data);
}

赫夫曼树

压缩优化的二叉树存储结构

赫夫曼编码

为了解决当年远距离通信(主要是电报)的数据传输的最优化问题。通过树结构对字符重新编码

  • 将出现频率高的字符放在离根节点近的位置

  • 使用二叉树路径表示字符编码

压缩后的编码表

字母 A B C D E F
二进制字符 01 1001 101 00 11 1000