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 层的最大结点数为: 2i1 i1
  • 深度为 k 的二叉树有最大结点总数为: 2k1 k1
  • 对任何非空二叉树T,若 n0 表示叶结点的个数、 n1 是度为1的非叶结点个数、 n2 是度为2的非叶结点个数,那么两者满足关系 n0=n2+1
    例:

    image

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

抽象数据类型定义

  • 类型名称:二叉树
  • 数据对象集:一个有穷的结点集合。若不为空,则由根结点和其左右二叉子树组成
  • 操作集: BTBinTree ItemElementType ,重要的操作有:
    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

结点序号
A1
B2
O3
C4
S5
M6
Q7
W8
K9

一般二叉树

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

image

结点序号
A1
B2
O3
NULL4
NULL5
M6
NULL7
NULL8
NULL9
NULL10
NULL11
NULL12
C13

链式存储

结构定义

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值