1 树的概念
树是一种数据结构,其中一个元素可以有两个或者多个数据元素,具有一对多的特点,用树结构来存储文件。树的基本概念如下:
- 结点的度:子结点的个数。
- 树的度:树的度等于所有结点度中度最高的值。
- 叶子结点:度为0的结点,即没有子结点的结点。
- 分支结点:除了叶子结点以外的结点,即度不为0的结点。
- 内部结点:除了根结点以及叶子结点或在分支结点的基础之上在去掉根结点。
- 树的深度:树中结点的最大层。
树分为二叉查找树(二叉排序树)、平衡二叉树(AVL树)、红黑树、B-树、B+树、字典树(trie树)、后缀树、广义后缀树。实际项目中很常见的是AVL树和红黑树,本文先从最基础的二叉树开始分析。
2 二叉分类及特点
二叉树是树的特殊一种,常见的二叉树结构有斜树、满二叉树、完全二叉树。二叉树具有如下特点:
1、每个结点最多有两颗子树,结点的度最大为2。
2、左子树和右子树是有顺序的,次序不能颠倒。
3、即使某结点只有一个子树,也要区分左右子树。
斜树
所有的结点都只有左子树(图 1中左斜树),或者只有右子树(图 1中右斜树)。斜树的数据存储结构和线性链表存储结构一致,效率低。在后期文章华容道算法中会对比二叉树排序与链表排序所产生时间上的效率,链表排序计算华容道算法程序需要运行两个小时,平衡二叉树排序华容道算法程序需要运行时间为微妙级别。
图 1
满二叉树
满二叉树所有的分支结点都存在左子树和右子树,并且所有的叶子结点都在同一层上。满二叉树如图 2所示。
图 2
完全二叉树
对一棵具有n个结点的二叉树按层序排号,如果编号为i的结点与同样深度的满二叉树编号为i结点在二叉树中位置完全相同,就是完全二叉树。满二叉树必须是完全二叉树,反过来不一定成立。
图 3
证明完全二叉树深度log2n+1
满二叉树是完全二叉树,对于深度为k的满二叉树中结点数量是2k-1=n,完全二叉树结点数量最多2k
-1,同时完全二叉树倒数第二层肯定是满的(倒数第一层有结点,那么倒是第二层序号和满二叉树相同),所以完全二叉树的结点数最少大于少一层的满二叉树,为2(k-1)
-1。
根据上面推断得出: 2(k-1)-1<n≤2k
-1,因为结点数n为整数, n≤2k
-1可以推出n<2k
,n>2(k-1)
-1可以推出 n≥2(k-1)
,所以2(k-1)≤
n<2k
。即可得k-1≤log2n
<k ,则log2n
<k≤log2n+1
,而k作为整数因此k=log2n
+1,证明完毕。这个证明是很基础的数学。
- 二叉树遍历
- 创建二叉排序树
将{5,7,3,4,1,2,0,6,9,8,10}数字创建一个递增二叉树排序如图 4所示,创建代码如图 5所示。后续排序例程以图 4二叉树为准。
图 4
图 5
前序遍历
基本思想:先访问根结点,再先序遍历左子树,最后再先序遍历右子树即根—左—右,图 3前序遍历结果5 3 1 0 2 4 7 6 9 8 10。前序递归遍历、前序非递归遍历代码如图 6所示。递归遍历代码实现比较简单,非递归程序编写按照下面逻辑编写也能够顺利执行。
对于任一结点p:
- 访问结点p,并将结点p入栈;
- 判断结点p的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点p,循环置a;若不为空,则将p的左孩子置为当前结点p;
- 直到p为空,并且栈为空,则遍历结束。
图 6
中序遍历
基本思想:先中序遍历左子树,然后再访问根结点,最后再中序遍历右子树即左—根—右。图 3中序遍历结果是:0 1 2 3 4 5 6 7 8 9 1,中序遍历结果及得到升序序列。中序递归遍历程序如图 7所示。
图 7
后序遍历
基本思想:先后序遍历左子树,然后再后序遍历右子树,最后再访问根结点即左—右—根。后序遍历结果是10 9 8 7 6 5 4 3 2 1 0,及得到了降序排序序列。后续递归遍历程序如图 8所示。
图 8
查找最大值与最小值
查找二叉树中最大值和最小值选项,程序结构如图 9所示。
图 9
二叉树与链表效率对比
对于n个节点的二叉查找树(平衡的二叉树)深度为log2n+1,那么二叉查找的时间复杂度是O(log2n
),而链表时间复杂度是O(n)。假如有216
=65536个节点,当插入一个新的节点,并且保证该节点数据不能与原节点重复,那么久需要做比对。链表需要比对65536才能保证数据没有重复,而平衡二叉树中最多比对log265536
=16次,当结点数据更多,需要重复比对次数越多,链表效率就显得非常低,在讲解华容道算法中会用链表和红黑树做时间效率对比。
本文相对比较基础,参考SSDFans的深入浅出SSD文章写作方式,由浅入深依次贯穿二叉树—平衡二叉树—红黑树—linux内核Hash表。