树(tree)
是n(n>=0)个结点的有限集,只有一个根节点,子树的数目没有限制,但一定是不想交的。树的定义用子递归的方式。节点的度
:节点拥有子树的数目。
二叉树(binary tree)
是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。
二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树 。
没有父节点的节点叫做根节点
,图中F为根节点,C为A和D的父节点
,A、D、H、G互为兄弟节点
,没有子节点的节点叫做叶子节点
或者叶节点,A 、B 、H、 M都是叶子节点。
小结:二叉树可以是空树(n>=0)。
二叉树特点:每个节点最多两颗子树,左子树,右子树(互不相交);左子树和右子树是有顺序的;即便树中某个节点只有一颗子树也要区分它是左子树还是右子树。
节点的高度=节点到叶子节点的最长路径 (边数)
节点的深度=根节点到这个节点所经历的边的个数
节点的层数=节点的深度+1
满二叉树
:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树 。
完全二叉树
:深度为k,有n个结点的二叉树当且仅当其每一个结点都与深度为k的满二叉树中编号从1到n的结点一一对应时,称为完全二叉树 。
左斜树
:所有节点都只有左子树
小结:完全二叉树的特点:是叶子结点只可能出现在最下层或次下层;最下层叶子节点集中在树的左部;同样节点数目的二叉树,完全二叉树深度最小。
满二叉树特点:是所有分支都有左子树和右子树,所有叶子节点都在最底层;满二叉树一定是完全二叉树,反过来不一定成立。
一种是基于指针或者引用的二叉 链式存储法
,一种是基于数组的 顺序存储法
。
1) 顺序存储
就是使用一维数组存储二叉树中节点,并且节点的存储位置就是数组的下标索引。
可以看出完全二叉树适合采用顺序存储结构。非完全二叉树会浪费一部分存储空间,极端情况下,右斜树会超级浪费空间。
2) 链式存储
二叉树每个节点有两个孩子,因此将节点数据结构定义为一个数据和两个指针域。
小结:二叉树有两种存储方式,分别是顺序存储和链式存储。顺序存储适合完全二叉树,非完全二叉树的顺序存储会造成空间的相对浪费,所以转为链式存储。相对来说链式存储法我们比较常用。
经典的方法:前序遍历、中序遍历和后序遍历。另外还有层次遍历。每个节点遍历一次,n个节点,遍历的时间复杂度都是 O(n)。
前序遍历(根左右):A
->B->D->E->C->F
中序遍历(左根右):D->B->E->A
->F->C
后序遍历(左右根):D->E->B->F->C->A
前中后序遍历也可以看做树的深度优先搜索(DFS)
;
层次遍历(按照高度一层一层访问):A->B->C->D->E->F
层次遍历也可以看做树的宽度优先搜索(BFS)
;
代码实现此处先不描述,可另行查找理解,递归法,迭代法处理等。
小结:已知前序和中序遍历,或者已知中序和后序遍历都可以确定二叉树。前序遍历第一个访问的根节点,中序遍历根节点在中间,后序遍历最后访问根节点。
二叉查找树
要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。
二叉查找树最大的特点就是,支持动态数据集合的快速插入、删除、查找操作。
特别是中序遍历二叉查找树,可以输出有序的数据序列,相当于升序排列,时间复杂度是 O(n),非常高效。因此,二叉查找树也叫作二叉排序树。
第一,散列表中的数据是无序存储的,如果要输出有序的数据,需要先进行排序。而对于二叉查找树来说,我们只需要中序遍历,就可以在 O(n) 的时间复杂度内,输出有序的数据序列。
第二,散列表扩容耗时很多,而且当遇到散列冲突时,性能不稳定,尽管二叉查找树的性能不稳定,但是在工程中,我们最常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在 O(logn)。
第三,笼统地来说,尽管散列表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快。加上哈希函数的耗时,也不一定就比平衡二叉查找树的效率高。
第四,散列表的构造比二叉查找树要复杂,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩容、缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。
最后,为了避免过多的散列冲突,散列表装载因子不能太大,特别是基于开放寻址法解决冲突的散列表,不然会浪费一定的存储空间。
综合这几点,平衡二叉查找树在某些方面还是优于散列表的,所以,这两者的存在并不冲突。我们在实际的开发过程中,需要结合具体的需求来选择使用哪一个。
1、红黑树特点
:
2、红黑树意义:
红黑树是一种平衡二叉查找树。它是为了解决普通二叉查找树在数据更新的过程中,复杂度退化的问题而产生的。
红黑树的高度近似 log2n ,所以它是近似平衡,插入、删除、查找操作的时间复杂度都是 O(logn)。因为红黑树是一种性能非常稳定的二叉查找树,所以,在工程中,但凡是用到动态插入、删除、查找数据的场景,都可以用到它。不过,它实现起来比较复杂。
小结:
散列表:插入删除查找都是O(1), 是最常用的,但其缺点是不能顺序遍历以及扩容缩容的性能损耗。适用于那些不需要顺序遍历,数据更新不那么频繁的。
跳表:插入删除查找都是O(logn), 并且能顺序遍历。缺点是空间复杂度O(n)。适用于不那么在意内存空间的,其顺序遍历和区间查找非常方便。
红黑树:插入删除查找都是O(logn), 中序遍历即是顺序遍历,稳定。缺点是难以实现,去查找不方便。其实跳表更佳,但红黑树已经用于很多地方了。
数据结构 | 查找 | 插入 | 删除 | 遍历 |
---|---|---|---|---|
数组 | O(N) | O(1) | O(N) | - |
有序数组 | O(logN) | O(N) | O(N) | O(N) |
链表 | O(N) | O(1) | O(N) | - |
有序链表 | O(N) | O(N) | O(N) | O(N) |
二叉树(一般情况) | O(logN) | O(logN) | O(logN) | O(N) |
二叉树(最坏情况) | O(N) | O(N) | O(N) | O(N) |
平衡树(一般情况和最坏情况) | O(logN) | O(logN) | O(logN) | O(N) |
哈希表 | O(1) | O(1) | O(1) | - |
3、红黑树的平衡调整:
调整的过程包含两种基础的操作:左右旋转和改变颜色,相对比较复杂,此处不过多描述。