树
树概念及结构
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
专有名词解释
结点:树中的数据元素都称之为结点;
根节点:最上面的结点称之为根,一颗树只有一个根且由根发展而来,从另外一个角度来说,每个结点都可以认为是其子树的根;
父节点:结点的上层结点,如图中,结点K的父节点是E、结点L的父节点是G;
子节点:节点的下层结点,如图中,节点E的子节点是K节点、节点G的子节点是L节点;
兄弟节点:具有相同父节点的结点称为兄弟节点,图中F、G、H互为兄弟节点;
结点的度数:每个结点所拥有的子树的个数称之为结点的度,如结点B的度为3;
树叶:度数为0的结点,也叫作终端结点,图中D、K、F、L、H、I、J都是树叶;
非终端节点(或分支节点):树叶以外的节点,或度数不为0的节点。图中根、A、B、C、E、G都是
树的深度(或高度):树中结点的最大层次数,图中树的深度为4;
结点的层数:从根节点到树中某结点所经路径上的分支树称为该结点的层数,根节点的层数规定为1,其余结点的层数等于其父亲结点的层数+1;
同代:在同一棵树中具有相同层数的节点;
- 每个节点有零个或多个子节点;
- 没有父节点的节点称为根节点;
- 每一个非跟节点有且仅有一个父节点;
- 除根节点外,其余结点被分成是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。
若一个节点有子树,那么该节点称为子树根节点的“双亲“,子树的跟是该节点的“孩子”。有相同双亲的节点互为“兄弟节点”。一个节点的所有子树上的任何节点都是该节点的后裔。从根节点到某个节点的路径上的所有节点都是该节点的祖先。
- 节点的度:节点拥有的子树的数目,一个节点含有的子树的个数称为该节点的度, 如上图:A结点的度为2;
- 叶子节点:度为0的节点称为叶子节点, 如上图:G、H、I节点为叶节点;
- 非终端节点或分支节点:度不为0的节点,如上图:B、D、C、E、F节点为分支节点;
- 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点, 如上图:A是B的父节点;
- 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点,如上图:B是A的孩子节点;
- 兄弟节点:具有相同父节点的节点互称为兄弟节点,如上图:B、C是兄弟节点;
- 树的度:树中节点的最大的度。一棵树中,最大的节点的度称为树的度, 如上图:树的度为2;
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
- 树的高度或深度:树中节点的最大层次,如上图:树的高度为4;
- 堂兄弟节点:双亲在同一层的节点互为堂兄弟,如上图:H、I互为兄弟节点;
- 节点的祖先:从根到该节点所经分支上的所有节点,如上图:A是所有节点的祖先;
- 子孙:以某节点为根的子树中任一节点都称为该节点的子孙,如上图:所有节点都是A的子孙;
-
无序数:如果树中节点的各子树之间的次序是不重要的,可以交换位置。
-
有序数:如果树中结点的各子树的次序是重要的,不可以交换位置。
- 森林:个或多个不相交的树组成。对森林加上一个跟,森林即成为树;删去跟,树即成为森林。
树的表示
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,实际中树有很多种表示方式,如:双亲表示法,孩子表示法、孩子兄弟表示法等等。我们这里就简单的了解其中最常用的孩子兄弟表示法。
typedef int DataType;
struct Node
{
struct Node* firstChild1;
struct Node* pNextBrother;
DataType data;
};
二叉树
二叉树的定义
二叉树(Binary tree)是树形结构的一个重要类型。二叉树特点是每个结点最多只能有两棵子树,且有左右之分。许多实际问题抽象出来的数据结构往往是二叉树形式,二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。
二叉树是每个节点最多有两个子树的树结构。
它有五种基本形态:二叉树可以是空集;根可以有空的左子树或右子树;左、右子树皆为空。
二叉树的特点
(1)每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
(2)二叉树的子树有左右之分,其子树的次序不能颠倒。
二叉树的种类
三种特殊形态
满二叉树
满二叉树:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。 第 n 层的结点数是 2 的 n-1 次方,总的结点个数是 2 的 n次方-1
每一层的结点数都达到最大值,则这个二叉树就是满二叉树。
也就是说,如果一个二叉树的层数为K(根节点是第1层),且结点总数是(2^k) -1 ,则它就是满二叉树。
完全二叉树
完全二叉树
: 叶结点只能出现在最底层的两层,且最底层叶结点均处于次底层叶结点的左侧。
一颗二叉树中,只有最下面两层节点的度可以小于2,并且最下层的叶节点集中在靠左的若干位置上。
叶子节点只能出现在最下层和次下层,且最下层的叶子节点集中在树的左部。显然,一颗满二叉树必定是一颗完全二叉树,而完全而二叉树不一定是满二叉树。
完全二叉树的特点:
(1)叶子结点只能出现在最下两层。
(2)最下层的叶子一定集中在左部连续位置。
(3)倒数第二层,若有叶子结点,一定都在右部连续位置。
(4)如果结点度为1,则该结点只有左孩子。
二叉排序/查找/搜索树
二叉排序/查找/搜索树
:即为 BST(binary search/sort tree)。满足如下性质:
① 若它的左子树不为空,则左子树上所有结点的值均小于它的根节点的值;
② 若它的右子树上所有结点的值均大于它的根节点的值;
③ 它的左、右子树也分别为二叉排序/查找/搜索树。
对二叉查找树进行中序遍历,得到有序集合。便于检索。
二叉搜索树又称二叉排序树,前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
下面这两棵树都是搜索树:
平衡二叉搜索树
平衡二叉树:(Self-balancing binary search tree,AVL) 首先是二叉排序树,此外具有以下性质:
① 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1;
② 并且左右两个子树也都是一棵平衡二叉树;
③ 不要求非叶节点都有两个子结点;
平衡二叉树的目的是为了减少二叉查找树的层次,提高查找速度。平衡二叉树的常用实现有红黑树、AVL、替罪羊树、Treap、伸展树等。
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树。
具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
如图:
最后一棵 不是平衡二叉树,因为它的左右两个子树的高度差的绝对值超过了1。
判断平衡二叉树
- 1. 是二叉搜索树
- 2. 任何一个节点的左子树或者右子树都是平衡二叉树(左右高度差小于等于1)
(1)下图不是平衡二叉树因为它不是二叉搜索树,违反第1条件。
(2)下图不是平衡二叉树因为有节点子树高度差大于1,违法第2条件。
(3)下图是平衡二叉树,因为符合1、2 条件。
相关概念
平衡因子BF
定义:左子树和右子树高度差
计算:左子树高度 - 右子树高度的值
别名:简称BF( Balance Factor 而不是 Boy Friend )
一般来说BF的绝对值大于 1,,平衡树二叉树就失衡,需要旋转纠正。
最小不平衡子树
距离插入节点最近的,且平衡因子的绝对值大于1的节点为根的子树。
在图三中,左边二叉树的节点45的BF = 1,插入节点43后,节点45的BF = 2。节点45是距离插入点43最近的BF不在[-1,1]范围内的节点,因此以节点45为根的子树为最小不平衡子树。
AVL树的平衡调整
整个实现过程是通过在一棵平衡二叉树中依次插入元素(按照二叉排序树的方式),若出现不平衡,则要根据新插入的结点与最低不平衡结点的位置关系进行相应的调整。分为LL型、RR型、LR型和RL型4种类型。
红黑树
红黑树:即 Red-Black Tree。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。 红黑树是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,它是在 1972 年由 Rudolf Bayer 发明的。红黑树是复杂的,但它的操作有着 良好的最坏情况运行时间,并且在 实践中是高效的:它可以在 O(log n) 时间内做查找,插入和删除, 这里的 n 是树中元素的数目。红黑树的特性:
- 每个节点是红色或者黑色;
- 根节点是黑色;
- 每个叶子节点(NIL)是黑色。(注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点);
- 每个红色节点的两个子节点都是黑色的。(从每个叶子到根的所有路径上不能有两个连续的红色节点);
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点(确保没有一条路径会比其他路径长出2倍);
当我们插入或删除节点时,可能会破坏已有的红黑树,使得它不满足以上5个要求,那么此时就需要进行处理,使得它继续满足以上的5个要求:
1、recolor
:将某个节点变红或变黑;
2、rotation
:将红黑树某些结点分支进行旋转(左旋或右旋);
红黑树可以通过红色节点和黑色节点尽可能的保证二叉树的平衡。主要是用它来存储有序的数据,它的时间复杂度是O(logN),效率非常之高。
二叉树的存储方式
存的目的是为了取,而取的关键在于如何通过父结点拿到它的左右子结点,不同存储方式围绕的核心也就是这。
二叉树可以链式存储,也可以顺序存储。
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在散落在各个地址的节点串联一起。
顺序存储
使用一组地址连续的存储单元存储,例如数组。为了在存储结构中能得到父子结点之间的映射关系,二叉树中的结点必须按层次遍历的顺序存放。具体是:
- 对于完全二叉树,只需要自根结点起从上往下、从左往右依次存储。
- 对于非完全二叉树,首先将它变换为完全二叉树,空缺位置用某个特殊字符代替(比如#),然后仍按完全二叉树的存储方式存储。
假设将一棵二叉树按此方式存储到数组后,左子结点下标=2倍的父结点下标+1,右子节点下标=2倍的父结点下标+2。若数组某个位置处值为#,代表此处对应的结点为空。
顺序存储是用数组来存储二叉树,顺序存储的方式如图:
数组来存储二叉树如何遍历的呢?
- 如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
可以看出顺序存储非常适合存储接近完全二叉树类型的二叉树,对于一般二叉树有很大的空间浪费,所以对于一般二叉树,一般用下面这种链式存储。
链式存储
对每个结点,除数据域外再多增加左右两个指针域,分别指向该结点的左孩子和右孩子结点,再用一个头指针指向根结点。对应的存储结构:
链式存储如图:
二叉树的遍历方式
前序遍历:中左右(根左右)。 即先访问根结点,再前序遍历左子树,最后再前序遍历右子 树。前序遍历运算访问二叉树各结点是以根、左、右的顺序进行访问的。
中序遍历:左中右(左根右)。 即先中前序遍历左子树,然后再访问根结点,最后再中序遍 历右子树。中序遍历运算访问二叉树各结点是以左、根、右的顺序进行访问的。
后序遍历:左右中(左右根)。 即先后序遍历左子树,然后再后序遍历右子树,最后访问根 结点。后序遍历运算访问二叉树各结点是以左、右、根的顺序进行访问的。
前序遍历:ABDHIECFG
中序遍历:HDIBEAFCG
后序遍历:HIDEBFGCA
深度优先遍历
前序遍历:先访问一棵树的根节点,再访问左子树,最后访问右子树。
中序遍历:先访问一棵树的左子树,再访问根节点,最后访问右子树。
后序遍历:先访问一棵树的左子树,再访问右子树,最后访问根节点。
如上面这棵树,对它进行遍历:
- 前序遍历:A B D NULL NULL NULL C E NULL NULL F NULL NULL
- 中序遍历:NULL D NULL B NULL A NULL E NULL C NULL F NULL
- 后序遍历:NULL NULL D NULL B NULL NULL E NULL NULL F C A
广度优先遍历
①层次遍历
层序遍历:首先访问第一层的根结点,然后从左到右访问第2层上的节点,接着访问第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
- 层序遍历:A B C D NULL E F NULL NULL NULL NULL NULL NULL
层序遍历的过程较为复杂,需要借助队列来实现:
开始先让根节点入队,之后每次让队头的结点出队,并将队头结点的左右结点入队,直到队头结点的左右结点均为空。这是不再入队,将队列中的数据依次出队,最后得到的就是层序遍历的结果。
下面以刚才的二叉树为例,给出了层序遍历的过程。
遍历总结
①前序遍历(递归法,迭代法)中左右
②中序遍历(递归法,迭代法)左中右
③后序遍历(递归法,迭代法)左右中