1、什么是树算法
树是一个有限结点组成的集合。能够用递归的方式来定义一棵树:树能够是一个空集,若非空,则一棵树由一个根(root)结点 r 以及 0 个或多个非空的(子)树 T一、T二、T三、...,Tk 组成,这些子树中的每一棵的根都被来自根 r 的一条有向的边(edge)所链接。每一棵子树的根叫作根 r 的儿子(child),而 r 是每一棵子树的根的父亲(parent)。数据结构
树的几个概念:spa
1)树叶(leaf):从上述的递归定义能够知道,一棵树是由 N 个结点和 N-1 条边组成的集合。每一个结点均可以有零个或任意多个儿子,没有儿子的结点称为树叶(leaf);指针
2)兄弟(sibling):具备相同父亲的结点称为兄弟(sibling);code
3)路径(path):从结点 n1 到 nk 的路径定义为结点 n1、n2、n3、...、nk 的一个序列,使得对于 1 <= i <= k,结点 ni 是 ni+1 的父亲。这个路径的长为该路径上的边的条数,即 k-1。从每个结点到它本身有一条长为 0 的路径。在一棵树中,从根到每一个结点刚好存在一条路径。blog
4)深度(depth):对于任意结点 ni,ni 的深度为从根到 ni 的惟一路径的长,根的深度为0;排序
5)高(height):对于任意结点 ni,ni 的高为从 ni 到一片树叶的最长路径的长,树的高等于它的根的高(只有一个根结点的树的高度为0,空树的高度为 -1);递归
6)结点的度:一个结点的儿子的个数。内存
图 1-1 树io
如图所示,为一棵树,其中,A是根结点;B、C、D、E、F 为 A 的儿子,A 为它们的父亲;B 和 C有相同的父亲,它们是兄弟(sibling);从结点 A 到结点 M 的路径为 A、D、H、M;结点 B 的深度为1;结点 D 的高度为 3;整棵树的高度为 4。
2、二叉树
二叉树是一棵树,其中每一个结点最多有两个儿子,分别为左子结点和右子结点。二叉树的平均深度是 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、一些特殊的二叉树
3.1 满二叉树
满二叉树是一棵深度为 k,且有 2^(k-1) 个结点的二叉树,以下图所示:
图 2-2 满二叉树
这就是一个满二叉树,能够看到,满二叉树的全部叶子都在同一层 —— 最后一层,非叶子结点的度必定为2。在一样深度的二叉树中,满二叉树的结点数是最多的,叶子数也是最多的。
3.2 彻底二叉树
彻底二叉树,除了最后一层外,其他层都是满的,且最后一层的叶子结点都几种在树的左边。满二叉树就是彻底二叉树的一个特例。以下图:
图 2-3 彻底二叉树
3.3 二叉查找树(二叉排序树)
二叉查找树是二叉树的一种,更适合于进行查找操做。对于二叉查找树,树中的每一个结点 X 的左子树中全部关键字的值小于 X 的关键字值,而它的右子树中全部关键字值大于 X 的关键字值。这意味着,该树的全部元素能够以某种统一的方式进行排序。二叉查找树的平均深度是 O(logN)。
图 2-4 二叉查找树
参考资料:
《算法导论 第三版》
《数据结构与算法分析--C语言描述》