zoukankan      html  css  js  c++  java
  • 基本数据结构 -- 树简介

    一、什么是树

      树是一个有限结点组成的集合。可以用递归的方式来定义一棵树:树可以是一个空集,若非空,则一棵树由一个根(root)结点 r 以及 0 个或多个非空的(子)树 T1、T2、T3、...,Tk 组成,这些子树中的每一棵的根都被来自根 r 的一条有向的边(edge)所连接。每一棵子树的根叫做根 r 的儿子(child),而 r 是每一棵子树的根的父亲(parent)

    树的几个概念:

    1)树叶(leaf):从上述的递归定义可以知道,一棵树是由 N 个结点和 N-1 条边组成的集合。每个结点都可以有零个或任意多个儿子,没有儿子的结点称为树叶(leaf);

    2)兄弟(sibling):具有相同父亲的结点称为兄弟(sibling);

    3)路径(path):从结点 n1 到 nk 路径定义为结点 n1、n2、n3、...、nk 的一个序列,使得对于 1 <= i  <= k,结点 ni 是 ni+1 的父亲。这个路径的长为该路径上的边的条数,即 k-1。从每一个结点到它自己有一条长为 0 的路径。在一棵树中,从根到每个结点恰好存在一条路径

    4)深度(depth):对于任意结点 ni,ni 的深度为从根到 ni 的唯一路径的长,根的深度为0;

    5)高(height):对于任意结点 ni,ni 的高为从 ni 到一片树叶的最长路径的长,树的高等于它的根的高(只有一个根结点的树的高度为0,空树的高度为 -1);

    6)结点的度:一个结点的儿子的个数。

    图 1-1 树

      如图所示,为一棵树,其中,A是根结点;B、C、D、E、F 为 A 的儿子,A 为它们的父亲;B 和 C有相同的父亲,它们是兄弟(sibling);从结点 A 到结点 M 的路径为 A、D、H、M;结点 B 的深度为1;结点 D 的高度为 3;整棵树的高度为 4。

    二、二叉树

      二叉树是一棵树,其中每个结点最多有两个儿子,分别为左子结点和右子结点。二叉树的平均深度是 O(√N)。

    2.1 二叉树的实现

      因为一棵二叉树的每个结点最多只能有两个子结点,故而我们可以用指针直接指向它们。树结点的声明在结构上类似于双链表的声明,在声明中,一个结点由关键字(Key)和两个指向其他结点的指针(Left 和 Right)组成:

    typedef int ElementType;
    
    struct TreeNode {
        ElementType Element;
        struct TreeNode *Left;
        struct TreeNode *Right;
    };
    
    typedef struct TreeNode *PtrToNode;
    typedef PtrToNode BinaryTree;

      这段代码声明了一个结构,该结构包含一个 ElementType 类型的数据,用于保存结点数据;一个 Left 指针,指向结点的左子树;和一个 Right 指针,指向结点的右子树。

    2.2 二叉树的遍历

      按一定的顺序,依次访问二叉树中的所有结点的操作称为遍历。有三种方式可以对二叉树进行遍历,以下面这幅图为例进行介绍:

    图 2-1 二叉树

    1)先序遍历

      先访问根结点,然后遍历左子树,再遍历右子树。对图 2-1 所示的二叉树进行先序遍历结果为:A-B-D-G-H-E-C-F-I-J。 先序遍历一棵二叉树的C语言实现如下:

    /* 先序遍历,递归实现 */
    void PreOrderRecursion(BinaryTree T)
    {
        if (T != NULL) {
            printf("%d	", T->Element);
            PreOrderRecursion(T->Left);
            PreOrderRecursion(T->Right);
        }
    }

      上述代码用递归的方式实现了先序遍历二叉树。需要注意的是判断条件,要时刻判断二叉树是否为空,这在递归中尤为重要。递归实现先序遍历的代码十分简洁且易懂。但是,递归调用的空间复杂度较大,当输入规模很大时,会占用相当多的内存,也容易造成堆栈的溢出。

      可以使用非递归的方式来实现先序遍历,大致思想如下:首先,将根结点保存到一个数据结构里,然后访问左子树,待左子树访问完后,取出根结点,再访问右子树;每访问一个子树之前,都将这棵子树的根结点保存,待需要访问其右子树时再取出。可以知道的是,先保存进去的结点数据后取出,是一个后入先出结构,因此使用栈来保存根结点的数据十分合适。

    2)后序遍历

      先遍历左子树,再遍历右子树,最后访问根结点。对图 2-1 所示的二叉树进行后序遍历的结果为:G-H-D-E-B-I-J-F-C-A。

    /* 后序遍历,递归实现 */
    void PostOrderRecursion(BinaryTree T)
    {
        if (T != NULL) {
            PostOrderRecursion(T->Left);
            PostOrderRecursion(T->Right);
            printf("%d
    ", T->Element);
        }
    }

    3)中序遍历

      先遍历左子树,然后访问根结点,再遍历右子树。对图 2-1 所示的二叉树进行中序遍历的结果为:G-D-G-B-E-A-C-I-F-J。

    /* 中序遍历,递归实现 */
    void InOrderRecursion(BinaryTree T)
    {
        if(T != NULL){
            InOrderRecursion(T->Left);
            printf("%d
    ",T->Element);
            InOrderRecursion(T->Right);
        }
    }

    三、一些特殊的二叉树

    3.1 满二叉树

      满二叉树是一棵深度为 k,且有 2^(k-1) 个结点的二叉树,如下图所示:

    图 2-2 满二叉树

      这就是一个满二叉树,可以看到,满二叉树的所有叶子都在同一层  ——  最后一层,非叶子结点的度一定为2。在同样深度的二叉树中,满二叉树的结点数是最多的,叶子数也是最多的。

    3.2 完全二叉树

      完全二叉树,除了最后一层外,其余层都是满的,且最后一层的叶子结点都几种在树的左边。满二叉树就是完全二叉树的一个特例。如下图:

    图 2-3 完全二叉树

    3.3 二叉查找树(二叉排序树)

      二叉查找树是二叉树的一种,更适合于进行查找操作。对于二叉查找树,树中的每个结点 X 的左子树中所有关键字的值小于 X 的关键字值,而它的右子树中所有关键字值大于 X 的关键字值。这意味着,该树的所有元素可以以某种统一的方式进行排序。二叉查找树的平均深度是 O(logN)。 

     

    图 2-4 二叉查找树

    参考资料:

    《算法导论 第三版》

    《数据结构与算法分析--C语言描述》

  • 相关阅读:
    Java基础---Java 开发工具IntelliJ IDEA 安装
    Java基础---Java循环区别
    Java基础---JavaJShell脚本工具
    Java基础---Java方法
    Java基础---Java三元运算
    Java基础---Java变量
    Java基础---Java数据类型
    Java基础---Java常量
    “ERROR: Cannot read property 'fileCoverage' of undefined...”
    矩阵乘积
  • 原文地址:https://www.cnblogs.com/tongye/p/9805860.html
Copyright © 2011-2022 走看看