二叉树

二叉树

二叉树定义与性质

定义

每个结点最多有两个子树

性质

1.非空二叉树上叶子结点数=度为2的结点数+1(树的结点数=所有结点度数+1)$n_0=n_2+1$
2.非空二叉树第k层最多有$2^{k-1}$个结点
3.高度为h的二叉树最多有$2^h-1$个结点
4.n个结点的完全二叉树的高度为$\lceil$$log_2(n+1)$$\rceil$ 或者$\lfloor$$log_2n$$\rfloor+1$
5.n个结点的完全二叉树 叶结点的个数$n_0$为$n_0=n-\lfloor$$n/2$$\rfloor$

特殊二叉树

1.满二叉树
2.完全二叉树:每个结点编号都与相同深度的满二叉树的结点一一对应
3.二叉排序树:左子树的结点关键字均小于根结点 右字数的结点关键字均大于根结点
4.平衡二叉树:树上任一结点的左子树和右子树的深度之差不超过1

二叉树的存储结构

二叉树的顺序存储

满二叉树和完全二叉树适合顺序存储 按满二叉树的编号分配位置 不存在的空结点置0

二叉树的链式存储

1
2
3
4
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

二叉树的遍历

先序遍历+中序遍历可确定二叉树(先序遍历第一个为根结点 根结点将中序遍历序列划分为左右序列再以此类推)
后序遍历+中序遍历可确定二叉树(后序遍历最后一个为根结点 将中序遍历序列左右划分))
层次遍历+中序遍历可确定二叉树
以此二叉树为例进行遍历

![二叉树遍历例子](binary-tree.png)

先序遍历(根左右)

若二叉树非空

  • 1.访问根结点
  • 2.先序遍历左子树
  • 3.先序遍历右子树
  • 先序遍历递归算法: 时间复杂度 $O(n)$
    1
    2
    3
    4
    5
    6
    7
    void PreOrder(BiTree T){
    if(T!=null){
    visit(T);
    PreOrder(T->lchild);
    PreOrder(T->rchild);
    }
    }
    序列为 `1 2 4 6 3 5`

    中序遍历(左根右)

    若二叉树非空

  • 1.中序遍历左子树
  • 2.访问根结点
  • 3.中序遍历右子树
  • 中序遍历递归算法: 时间复杂度 $O(n)$
    1
    2
    3
    4
    5
    6
    7
    void InOrder(BiTree T){
    if(T!=null){
    InOrder(T->lchild);
    visit(T);
    InOrder(T->rchild);
    }
    }
    序列为 `2 6 4 1 3 5`

    中序遍历非递归算法效率高于递归算法 且需要栈来实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void InOrder2(BiTree T){
    InitStack(S);BiTree p;//p始终指向栈顶元素
    while(p||IsEmpty(S)){
    if(p){
    Push(S,p);p=p->lchild;//根结点进栈 遍历左子树
    }
    else{
    Pop(S,p);visit(p);
    p=p->rchild;//结点出栈 并被访问 然后遍历右子树
    }
    }
    }

    后序遍历(左右根)

    若二叉树非空

  • 1.后序遍历左子树
  • 2.后序遍历右子树
  • 3.访问根结点
  • 后序遍历递归算法: 时间复杂度 $O(n)$
    1
    2
    3
    4
    5
    6
    7
    void PostOrder(BiTree T){
    if(T!=null){
    PostOrder(T->lchild);
    PostOrder(T->rchild);
    visit(T);
    }
    }
    序列为 `6 4 2 5 3 1`

    层次遍历

    按照每一层从左到右的顺序遍历整棵树 借助一个队列实现层次遍历
    层次遍历算法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void LevelOrder(BiTree T){
    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);
    }
    }

    序列为 1 2 3 4 5 6

    线索二叉树

    tag为0:child指向孩子结点 tag为1:child指向前驱/后继结点

    ltag lchild data rchild rtag
    左标识位 左孩子/前驱结点 元素位 右孩子/后继结点 右标识位
    线索二叉树结构体代码:
    1
    2
    3
    4
    5
    typedef struct ThreadNode{
    ElemType data;
    struct ThreadNode *lchild,*rchild;
    int ltag,rtag;
    }ThreadNode,*ThreadTree;

    中序线索二叉树递归算法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    递归函数InThread:
    void InThread(ThreadTree &p,ThreadTree &pre){
    if(p!=null){
    InThread(p->lchild,pre);
    if(p->lchild==null){
    p->lchild==null;p->ltag=1;
    }
    if((pre!=null)&&(pre->rchild==null)){
    pre->rchild=p;
    pre->rtag=1;
    }
    pre=p;
    InThread(p->rchild,pre)
    }
    }
    主函数CreateInThread
    void CreateInThread(ThreadTree T){
    ThreadTree pre=null;
    if(T!=null){
    InThread(T,pre);
    pre->rchild=null;
    pre->rtag=1;
    }
    }

    树与二叉树的应用

    二叉排序树(BST)

    平衡二叉树(AVL)

    哈弗曼树/哈弗曼编码

    树 森林

    树存储结构

    1.双亲表示法:采用一组连续空间来存储结点 每个结点都有一个伪指针来表示其双亲结点的下标 根结点下标为0伪指针为-1
    寻找双亲结点效率高 寻找孩子结点效率低

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #define Maxsize
    typedef struct{//树结点定义
    ElemType data;
    int parent;//双亲下标
    }PTNode;
    typedef struct{//树类型定义
    PTNode nodes[Maxsize];
    int n;//结点数
    }PTree;

    2.孩子表示法:将每个结点的孩子结点都用单链表连接起来 n个结点有n个孩子链表 按层次遍历给结点下标标号 根结点为0
    寻找孩子结点效率高 寻找双亲结点效率低

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #define Maxsize
    typedef struct{//单链表中孩子结点
    int child;//孩子结点下标
    struct CNode *next;//下一个孩子结点的指针
    }CNode;
    typedef struct{//每一个结点
    ElemType data;
    struct CNode *child;//第一个孩子结点的指针
    }CNode;
    typedef struct{//树类型定义
    PTNode nodes[Maxsize];
    int n;//结点数
    }CTree;

    3.孩子兄弟表示法(二叉树表示法):左指针指向结点的第一个孩子结点 右指针指向结点的下一个兄弟结点(左孩子右兄弟)
    可以方便的实现树转化为二叉树的操作 易于查找孩子结点 缺点:查找双亲结点很麻烦 需为每个结点添加一个parent域指向父结点

    1
    2
    3
    4
    typedef struct CSNode{
    ElemType data;
    struct CSNode *firstchild,*nextsibling;
    }CSNode,*CSTree;

    树 森林与二叉树转换

    1.树与二叉树转换(左孩子右兄弟方法):结点的左指针指向第一个孩子结点 右指针指向它在树中相邻的兄弟结点
    2.森林与二叉树转换:将森林中每一个树按照左孩子右兄弟转化为多个二叉树 然后将这几个二叉树的根结点都当做相邻的兄弟结点 然后连接起来构成一个大的二叉树
    卐:二叉树转化为树或森林都是唯一的

    树和森林的遍历

    个人见解 转化为对应的二叉树再进行遍历写出序列

    森林 二叉树
    先根遍历 先序遍历 先序遍历
    后根遍历 中序遍历 中序遍历
    End of reading! -- Thanks for your supporting