5. 树--二叉树的表示及其遍历

二叉树

定义

  • 一个有穷的结点集合
  • 这个集合可以为空
  • 若不为空,则它是由根结点和称为其左子树TL 和右子树TR 的两个不相交的二叉树组成。
  • 二叉树具体五种基本形态
    • image
    • image
    • image
    • image
    • image
  • 二叉树的子树有左右顺序之分

特殊二叉树

斜二叉树(Skewed Binary Tree)

image

满二叉树(Full Binary Tree)

又称完美二叉树(Perfect Binary Tree)

image

完全二叉树(Complete Binary Tree)

n 个结点的二叉树,对树中结点按从上至下、从左到右顺序进行编号,编号为i(1in) 结点与满二叉树中编号为i 结点在二叉树中位置相同,只会在最后一层出现右边才会出现没有结点的情况。满二叉树也是完全二叉树。

非完全二叉树

image

二叉树的性质

  • 一个二叉树第i 层的最大结点数为:2i1i1
  • 深度为k 的二叉树有最大结点总数为:2k1k1
  • 对任何非空二叉树T,若n0 表示叶结点的个数、n1 是度为1的非叶结点个数、n2 是度为2的非叶结点个数,那么两者满足关系n0=n2+1
    例:

    image

    • n0=4n1=2
    • n2=3
    • 所以可以推出n0=n2+1

抽象数据类型定义

  • 类型名称:二叉树
  • 数据对象集:一个有穷的结点集合。若不为空,则由根结点和其左右二叉子树组成
  • 操作集:BTBinTreeItemElementType ,重要的操作有:
    1. Boolean IsEmpty(BinTree BT):判断BT是否为空
    2. BinTree CreatBinTree():创建一个二叉树
    3. void Traversal(BinTree BT):遍历,按某顺序访问每个结点
      • void PreOrderTraversal(BinTree BT):先序遍历—根、左子树、右子树
      • void InOrderTraversal(BinTree BT):中序遍历—左子树、根、右子树
      • void PostOrderTraversal(BinTree BT):后序遍历—左子树、右子树、根
      • void LevelOrderTraversal(BinTree BT):层序遍历—从上到下、从左到右

顺序存储结构

完全二叉树

按从上至下、从左到右顺序存储

n 个结点完全二叉树的结点父子关系
* 非根结点(序号i1 )的父结点的序号是i/2
* 结点(序号为i )的左孩子结点的序号是2i (如果2in ,则没有左孩子)
* 结点(序号为i )的右孩子结点的序号是2i+1 (如果2i+1n ,则没有右孩子)

image

结点 序号
A 1
B 2
O 3
C 4
S 5
M 6
Q 7
W 8
K 9

一般二叉树

一般二叉树也可以采用这种结果,但会造成空间浪费,不推荐使用

image

结点 序号
A 1
B 2
O 3
NULL 4
NULL 5
M 6
NULL 7
NULL 8
NULL 9
NULL 10
NULL 11
NULL 12
C 13

链式存储

结构定义

typedef struct TreeNode *BinTree;
typedef BinTree Position;
struct TreeNode {
    ElementType Data;
    BinTree Left;
    BinTree Right;
}

image

二叉树的递归遍历

先序遍历

遍历过程:
1. 访问根结点
2. 先序遍历其左子树
3. 先序遍历其右子树

实现

void PreOrderTraversal(BinTree BT) {
    if (NULL != BT) {
        printf("%d ", BT->Data);
        PreOrderTraversal(BT->Left);
        PreOrderTraversal(BT->Right);
    }
}

image

遍历结果:A B D F E C G H I

在整个遍历过程中,根结点在子树遍历时总是第一个输出的

中序遍历

遍历过程:
1. 中序遍历其左子树
2. 访问根结点
3. 中序遍历其右子树

实现

void InOrderTraversal(BinTree BT) {
    if (NULL != BT) {
        InOrderTraversal(BT->Left);
        printf("%d ", BT->Data);
        InOrderTraversal(BT->Right);
    }
}

image

遍历结果:D B E F A G H C I

中序遍历时,根结点的左子树输出在根结点之前,右子树输出在根结点之后

后序遍历

遍历过程:
1. 后序遍历其左子树
2. 后续遍历其右子树
3. 访问根结点

实现

void PostOrderTraversal(BinTree BT) {
    if (NULL != BT) {
        PostOrderTraversal(BT->Left);
        PostOrderTraversal(BT->Right);
        printf("%d ", BT->Data);
    }
}

image

遍历结果:D E F B H G I C A

在整个遍历过程中,根结点在子树遍历时总是最后一个输出的

总结

  • 先序、中序和后序遍历过程中经过结点的路线一样,只是访问各结点的时机不同
  • 图中在从入口到出口的曲线上用 三种符号分别标记出了先序、中序和后序访问各结点的时刻

image

二叉树的非递归遍历

非递归算法实现的基本思路:使用

中序遍历

  1. 遇到一个结点,就把它压栈
  2. 当左子树遍历结束后,从栈顶弹出这个结点并访问它
  3. 然后按其右指针再去中序遍历该结点的右子树

实现

void InOrderTraversal(BinTree BT) {
    BinTree T = BT;
    Stack S = CreatStack(MaxSize); // 创建并初始化栈S

    while (NULL != T || !IsEmpty(S)) {
        while (NULL != T) { // 一直向左并将沿途结点压入栈中
            Push(S, T);
            T = T->Left;
        }

        T = Pop(S);     // 结点弹出栈
        printf("%d ", T->Data); // 访问结点
        T = T->Right;   // 转向右子树
    }
}

先序遍历

  1. 遇到一个结点,先访问结点,然后把它压栈
  2. 当左子树遍历结束后,从栈顶弹出这个结点但不访问
  3. 然后按其右指针再去前序遍历该结点的右子树

实现

void PreOrderTraversal(BinTree BT) {
    BinTree T = BT;
    Stack S = CreatStack(MaxSize); // 创建并初始化栈S

    while (NULL != T || !IsEmpty(S)) {
        while (NULL != T) { // 一直向左并将沿途结点压入栈中
            printf("%d ", T->Data); // 访问结点
            Push(S, T);
            T = T->Left;
        }

        T = Pop(S);     // 结点弹出栈
        T = T->Right;   // 转向右子树
    }
}

后序遍历

  1. 后序遍历思路与前序遍历相似,不过是按照根、右子树、左子树的方式遍历以后逆序输出
  2. 使用两个栈来进行,栈1保存访问的结果,栈2进行遍历
  3. 遇到一个结点,先把这个结点压入栈1,然后同时压入栈2
  4. 当右子树遍历结束后,从栈2中弹出这个结点但不访问
  5. 然后按照其左指针再去遍历该结点的左子树
  6. 遍历结束以后,输出栈1的值,即为后序遍历的结果

实现

void PostOrderTraversal(BinTree BT) {
    BinTree T = BT;
    Stack S = CreatStack(MaxSize); // 创建并初始化栈S
    Stack Resut_S = CreatStack(MaxSize); // 保存访问结果的栈
    while (NULL != T || !IsEmpty(S)) {
        while (NULL != T) { // 一直向左并将沿途结点压入栈中
            Push(Resut_S, T);   //  保存访问结果
            Push(S, T);
            T = T->Right;
        }

        T = Pop(S);     // 结点弹出栈
        T = T->Left;   // 转向左子树
    }

    // 逆序输出
    while (!IsEmpty(Resut_S)) {
        T = Pop(Resut_S);     // 结点弹出栈
        printf("%d ", T->Data); // 访问结点
    }
} 

层序遍历

使用队列来实现,层序基本过程:先根结点入队,然后:
1. 从队列中取出一个元素
2. 访问该元素所指结点
3. 若该元素所指结点的左右孩子结点非空,则将其左右孩子的指针顺序入队列

实现

void LevelOrderTraversal(BinTree BT) {
    Queue Q;
    BinTree T;

    if (NULL == BT)         // 如果是空树则直接返回
        return;

    Q = CreatQueue(MaxSize);    // 创建并初始化队列Q
    AddQ(Q, BT);
    while (!IsEmptyQ(Q)) {
        T = DeleteQ(Q);
        printf("%d ", T->Data); // 访问取出队列的结点
        if (NULL != T->Left)
            AddQ(Q, T->Left);
        if (NULL != T->Right)
            AddQ(Q, T->Right);
    }
}

二叉树遍历的应用

输出二叉树中叶子结点

在二叉树的遍历算法中增加检测结点的“左右子树是否都为空”

实现

以前序遍历为例

void PreOrderTraversal(BinTree BT) {
    if (NULL != BT) {
        if (NULL == BT->Left && NULL == BT->Right) 
            printf("%d ", BT->Data);
        PreOrderTraversal(BT->Left);
        PreOrderTraversal(BT->Right);
    }
}

求二叉树的高度

image

实现
int PostOrderGetHeight(BinTree BT) {
    int HL, HR, MaxH;

    if (NULL != BT) {
        HL = PostOrderGetHeight(BT->Left);      // 求左子树深度
        HR = PostOrderGetHeight(BT->Right);     // 求右子树深度
        MaxH = (HL > HR) ? HL : HR;             // 取左右子树较大的深度

        return MaxH + 1;                        // 返回树的深度
    }

    return 0;   // 空树深度为0
}

二元运算表达式树及其遍历

三种遍历可以得到三种不同的访问结果:
* 先序遍历得到前缀表达式
* 中序遍历得到中缀表达式(中缀表达式会受到运算符优先级的影响,输出的时候不能直接输出,需要做额外处理)
* 后序遍历得到后缀表达式

image

  • 先序遍历:+ + a b c + d e f g
  • 中序遍历:a + b c + d e + f g,这一部分是的运算符优先级是有问题的
  • 后序遍历:a b c + d e f + g +

由两种遍历序列确定二叉树

中序遍历和前序遍历(后序遍历)可以唯一确定一棵二叉树

以先序和中序遍历序列来确定一棵二叉树

  • 根据先序遍历序列第一个结点确定根结点
  • 根据根结点在中序遍历序列中分割出左右两个子序列
  • 左子树和右子树分别递归使用相同的方法继续分解

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章