大话数据结构学习笔记 - 二叉树

大话数据结构学习笔记 - 二叉树

二叉树的定义

二叉树(Binary Tree) n(n0) n ( n ≥ 0 ) 个结点的有限集合, 该集合或者为空集(称为空二叉树),或者由一个根节点和两棵互不相交的、分别称为根节点的左子树和右子树的二叉树组成

二叉树特点

特点

  • 每个结点最多有两棵子树,故不存在度大于2的结点
  • 左子树和右子树是有顺序的,次序不能任意颠倒
  • 即使树中某结点只有一棵子树,也要区分是左子树还是右子树

五种基本形态

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

特殊二叉树

  • 斜树:所有结点都只有左子树的二叉树叫左斜树,对应有右斜树,统称为斜树

  • 满二叉树:所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上的二叉树

    • 叶子只能出现在最下一层,否则不平衡
    • 非叶子节点的度一定是2
    • 同样深度的二叉树,满二叉树的结点个数最多,叶子数最多

    大话数据结构学习笔记 - 二叉树_第1张图片

  • 完全二叉树: 对一棵具有n个结点的二叉树按层序编号,如果编号为 i(1in) i ( 1 ≤ i ≤ n ) 的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树

    • 叶子结点只能出现在最下两层
    • 最下层的叶子一定集中在左部连续位置
    • 倒数二层,若有叶子结点,一定都在右部连续位置
    • 如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况
    • 同样结点数的二叉树,完全二叉树的深度最小

    大话数据结构学习笔记 - 二叉树_第2张图片

二叉树的性质

  • 在二叉树的第i层上至多有 2i1 2 i − 1 个结点 (i1) ( i ≥ 1 )
  • 深度为k的二叉树至多有 2k1 2 k − 1 个结点 (k1) ( k ≥ 1 )
  • 对任何一棵二叉树T, 如果其终端结点数为 n0 n 0 , 度为2的结点树为 n2 n 2 , 则 n0=n2+1 n 0 = n 2 + 1
  • 具有n个结点的完全二叉树的深度为 log2n+1 ⌊ l o g 2 n ⌋ + 1 (xx) ( ⌊ x ⌋ 表 示 不 大 于 x 的 最 大 整 数 )
  • 如果对一棵具有n个结点的完全二叉树(其深度为 log2n+1 ⌊ l o g 2 n ⌋ + 1 )的结点按层序编号(从第1层到第 log2n+1 ⌊ l o g 2 n ⌋ + 1 层,每层从左到右),对任一结点 i(1in) i ( 1 ≤ i ≤ n ) 有:
    • 如果 i = 1, 则结点i是二叉树的根,无双亲;如果 i>1 i > 1 , 则其双亲是结点 i/2 ⌊ i / 2 ⌋
    • 如果 2i>n 2 i > n , 则结点i无左孩子(结点i为叶子结点); 否则即 2in 2 i ≤ n 时其左孩子是结点 2i 2 i
    • 如果 2i+1>n 2 i + 1 > n , 则结点i无右孩子;否则 2i+1n 2 i + 1 ≤ n 时, 其右孩子是结点 2i+1 2 i + 1

二叉树的存储结构

二叉树顺序存储结构

顺序存储结构一般只用于 完全二叉树,否则会造成空间浪费

大话数据结构学习笔记 - 二叉树_第3张图片

二叉链表(链式存储结构)

二叉树每个结点最多有两个孩子,故结点结构为一个数据域和两个指针域

/* 二叉树的二叉链表结点结构定义 */
typedef struct BiTNode
{
    TElemType data;  // 结点数据
    struct BiTNode *lchild, *rchild;  // 左右孩子指针
}BiTNode, *BiTree;

遍历二叉树

二叉树的遍历(traversing binary tree)是指从根节点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。 由前序和后序无法得到唯一二叉树

前序遍历

规则是若二叉树为空,则空操作返回,否则先返回根节点,然后前序遍历左子树,再前序遍历右子树。下图遍历顺序为ABDGHCEIF

大话数据结构学习笔记 - 二叉树_第4张图片

递归版

void PreOrderTraverse(BiTree T)
{
    if(T == NULL)
        return;
    printf("%c", T->data);
    PreOrderTraverse(T->lchild);
    PreOrderTraverse(T->rchild);
}

非递归版

思想: 由前序遍历顺序可知,首先访问根节点,然后分别访问左子树和右子树。对于每个子树来说,以同样的顺序进行

步骤

  • 访问结点p, 然后将其入栈
  • 判断结点p的左子树是否为空,若不为空,将p左子树作为当前的结点p 。若为空,则取出栈顶结点并进行出栈操作,并将栈顶结点的右子树作为当前结点p
  • 知道pNULL并且栈为空,遍历结束
void PreOrderTraverseNoRecursive(BiTree T)
{
    stack s;
    BiTree p = T;
    while(p != NULL || !s.empty())
    {
        while(p != NULL)
        {
            printf("%c", p->data);
            s.push(p);
            p = p->lchild;
        }
        if(!s.empty())
        {
            p = s.top();
            s.pop();
            p = p->rchild;
        }
    }
}

中序遍历

规则是若树为空,则空操作返回,否则从根节点开始(注意并不是先访问根节点),中序遍历根节点的左子树,然后是访问根节点,最后中序遍历右子树。下图遍历顺序为GDHBAEICF

大话数据结构学习笔记 - 二叉树_第5张图片

递归版

void InOrderTraverse(BiTree T)
{
    if(T == NULL)
        return;
    InOrderTraverse(T->lchild);
    printf("%c", T->data);
    InOrderTraverse(T->rchild);
}

非递归版

思想:按中序遍历顺序,先访问左子树,再访问根节点,后访问右子树。对于每个子树来说,按同样顺序进行遍历。

步骤

  • 若当前结点p的左子树不为空,则将p入栈,并将p左子树置为当前节点,按相同规则进行
  • 若当前结点左子树为空,则取出栈顶元素并进行出栈操作,访问该栈顶结点,将当前结点p置为该栈顶结点的右子树
  • 知道pNULL并且栈为空遍历结束
void InOrderTraverseNoRecursive(BiTree T)
{
    stack s;
    BiTree p = T;
    while(p != NULL || !s.empty())
    {
        while(p != NULL)
        {
            s.push(p);
            p = p->lchild;
        }
        if(!s.empty())
        {
            p = s.top();
            s.pop();
            printf("%c", p->data);
            p = p->rchild;
        }
    }
}

后序遍历

规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根节点。下图遍历顺序为GHDBIEFCA

大话数据结构学习笔记 - 二叉树_第6张图片

递归版

void PostOrderTraverse(BiTree T)
{
    if(T == NULL)
        return;
    PostOrderTraverse(T->lchild);
    PostOrderTraverse(T->rchild);
    printf("%c", T->data);
}

非递归版

思想:后序遍历的非递归版本比较复杂,使用一个栈的话,过程比较繁琐。此处选择使用两个栈。

步骤

  • 将当前节点p入栈一s
  • 将栈一栈顶元素出栈,并入栈st
  • 然后将当前结点的左子树和右子树入栈一s
  • 按相同顺序进行,直到栈s为空
  • 最后,所有结点已经入栈st,且按照后序遍历的顺序存放,直接全部出栈,访问结点即可
void PostOrderTraverseNoRecursive(BiTree T)
{
    if(!T)
        return;
    stack s, st;
    BiTree p;
    s.push(T);
    while(!s.empty())
    {
        p = s.top();
        st.push(p);
        s.pop();
        if(p->lchild)
            s.push(p->lchild);
        if(p->rchild)
            s.push(p->rchild);
    }
    while(!st.empty())
    {
        printf("%c", st.top()->data);
        s.pop();
    }
}

层序遍历

规则是若树为空,则空操作返回,否则从树的第一层,也就是根节点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。下图遍历顺序为ABCDEFGHI

大话数据结构学习笔记 - 二叉树_第7张图片

思想:层序遍历由于层级的关系,需要使用队列存储,从左到右,从上到下。依次将该结点,该结点的左子树,该结点的右子树入队,即可保证顺序是层序排列。

步骤

  • 若根节点不为空,则将根节点入队,进入循环
  • 将首元素出队,并且访问该结点
  • 如果该结点有左孩子,将其左孩子入队
  • 如果该结点有右孩子,将其右孩子入队
/* 二叉树的层序遍历算法 */
void LevelOrderTraverse(BiTree T)
{
    if(T == NULL)
        return;
    queue q;
    BiTree p;
    q.push(T);
    while(!q.empty())
    {
        p = q.front();
        printf("%c", p->data);
        q.pop();
        if(p->lchild)
            q.push(p->lchild);
        if(p->rchild)
            q.push(p->rchild);
    }
}

二叉树的建立

/* 按前序输入二叉树中结点的值(一个字符), # 表示空树,构造二叉链表表示二叉树 T */
void CreateBiTree(BiTree &T)
{
    TElemType ch;
    scanf("%c", &ch);
    if(ch == '#')
        *T = NULL;
    else
    {
        (*T) = (BiTree)malloc(sizeof(BiTNode));
        if(!*T)
            exit(OVERFLOW);
        (*T)->data = ch;  // 生成根节点
        CreateBiTree(&(*T)->lchild);  // 构造左子树
        CreateBiTree(&(*T)->rchild);  // 构造右子树
    }
}

结语

有关二叉树的完整代码示例 code

有关于树的算法题还是很多的,后续会整理相关知识。Fighting

你可能感兴趣的:(C++,数据结构,C)