二叉查找树
二叉查找树
二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。
- 中序遍历二叉查找树,可以输出有序的数据序列,时间复杂度是 O(n),非常高效。
- 支持重复数据的二叉查找树
- 在实际的软件开发中,一般利用对象的某个字段作为键值(key)来构建二叉查找树,我们把对象中的其他字段叫作卫星数据。
- 如果存储的两个对象键值相同,这种情况该怎么处理呢?
- 二叉查找树中每一个节点不仅会存储一个数据,因此我们通过链表和支持动态扩容的数组等数据结构,把值相同的数据都存储在同一个节点上。
- 每个节点仍然只存储一个数据。在查找插入位置的过程中,如果碰到一个节点的值,与要插入数据的值相同,我们就将这个要插入的数据放到这个节点的右子树。
- 放在右子树,比放在左子树,操作更简单
- 当要查找数据的时候,遇到值相同的节点,我们并不停止查找操作,而是继续在右子树中查找,直到遇到叶子节点,才停止。 这样就可以把键值等于要查找值的所有节点都找出来。
- 对于删除操作,我们也需要先查找到每个要删除的节点,然后再按前面讲的删除操作的方法,依次删除。
- 二叉查找树的查找操作(前提: 不存在键值相同的情况)
- 先取根节点,如果它等于我们要查找的数据,那就返回。
- 如果要查找的数据比根节点的值大,那就在右子树中递归查找。
- 如果要查找的数据比根节点的值小,那就在左子树中递归查找;
|
|
- 二叉查找树的插入操作(前提: 不存在键值相同的情况)
从根节点开始,依次比较要插入的数据和节点的大小关系。
- 如果要插入的数据比节点的数据大,
- 并且节点的右子树为空,就将新数据直接插到右子节点的位置;
- 如果不为空,就再递归遍历右子树,查找插入位置。
- 同理,如果要插入的数据比节点数值小,
- 并且节点的左子树为空,就将新数据插入到左子节点的位置;
- 如果不为空,就再递归遍历左子树,查找插入位置。
|
|
- 二叉查找树的删除操作(前提: 不存在键值相同的情况)
针对要删除节点的子节点个数的不同,我们需要分三种情况来处理。
- 如果要删除的节点没有子节点,我们只需要直接将父节点中,指向要删除节点的指针置为 null。
- 如果要删除的节点只有一个子节点(只有左子节点或者右子节点),我们只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。
- 如果要删除的节点有两个子节点,这就比较复杂了。
- 找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。
- 然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了)
- 删除过程遵循第一条规则或者第二条规则。
|
|
- 时间复杂度分析 不管操作是插入、删除还是查找,时间复杂度其实都跟树的高度成正比,也就是 O(height)。
如何求一棵包含 n 个节点的完全二叉树的高度?
树的高度就等于最大层数减一,为了方便计算,我们转换成层来表示。
- 包含 n 个节点的完全二叉树中,第一层包含 1 个节点,第二层包含 2 个节点,第三层包含 4 个节点,依次类推,下面一层节点个数是上一层的 2 倍,第 K 层包含的节点个数就是 2^(K-1)。
- 对于完全二叉树来说,最后一层的节点个数有点儿不遵守上面的规律了。它包含的节点个数在 1 个到 2^(L-1) 个之间(我们假设最大层数是 L)
- 所以,n满足如下关系:
借助等比数列的求和公式,我们可以计算出,L 的范围是[log2(n+1), log2n +1]。完全二叉树的层数小于等于 log2n +1,也就是说,完全二叉树的高度小于等于 log2n。n >= 1+2+4+8+...+2^(L-2)+1 n <= 1+2+4+8+...+2^(L-2)+2^(L-1)
有了如此高效的散列表,为什么还需要二叉树?
-
散列表中的数据是无序存储的,如果要输出有序的数据,需要先进行排序。而对于二叉查找树来说,我们只需要中序遍历,就可以在 O(n) 的时间复杂度内,输出有序的数据序列。
-
散列表扩容耗时很多,而且当遇到散列冲突时,性能不稳定,尽管二叉查找树的性能不稳定,但是在工程中,我们最常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在 O(logn)。
-
笼统地来说,尽管散列表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快。加上哈希函数的耗时,也不一定就比平衡二叉查找树的效率高。
-
散列表的构造比二叉查找树要复杂,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩容、缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。
平衡二叉查找树
平衡二叉查找树中“平衡”的意思,其实就是让整棵树左右看起来比较“对称”、比较“平衡”,不要出现左子树很高、右子树很矮的情况。
这样就能让整棵树的高度相对来说低一些,相应的插入、删除、查找等操作的效率高一些。
最常用的平衡二叉查找树是红黑树。
红黑树中的节点,一类被标记为黑色,一类被标记为红色。除此之外,一棵红黑树还需要满足这样几个要求:
- 根节点是黑色的;
- 每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据;
- 任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的;
- 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点;