基本数据结构 -- 树简介

一、什么是树

  树是一个有限结点组成的集合。可以用递归的方式来定义一棵树:树可以是一个空集,若非空,则一棵树由一个根(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", 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\n", 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\n",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语言描述》

 

posted @ 2019-05-06 14:51  tongye  阅读(1194)  评论(0编辑  收藏  举报