备战百度笔试(C++后端开发学习日记番外篇)

2022.4.18 day 10 - 2022.4.19 day 11

前言

周一周二是复习的番外篇,另行总结如下。

八股文

声明:此部分参考徐学长的面试基础学习笔记

一、数据结构

AVL树

AVL 树是一种平衡二叉树,得名于其发明者的名字( Adelson-Velskii 以及 Landis)。AVL树是平衡二叉查找树,增加和删除节点后通过树形旋转重新达到平衡。右旋是以某个节点为中心,将它沉入当前右子节点的位置,而让当前的左子节点作为新树的根节点,也称为顺时针旋转。同理左旋是以某个节点为中心,将它沉入当前左子节点的位置,而让当前的右子节点作为新树的根节点,也称为逆时针旋转。

红黑树

红黑树是1972年发明的,称为对称二叉B树,1978年正式命名红黑树。主要特征是在每个节点上增加一个属性表示节点颜色,可以红色或黑色,红黑树和AVL树类似,都是在进行插入和删除时通过旋转保持自身平衡,从而获得较高的查找性能。与AVL树相比,红黑树不追求所有递归子树的高度差不超过1,保证从根节点到叶尾的最长路径不超过最短路径的2倍,所以最差时间复杂度是O(logn)。红黑树通过重新着色和左右旋转,更加高效地完成了插入和删除之后的自平衡调整。

红黑树在本质上还是二叉查找树,它额外引入了5个约束条件:

  • 节点只能是红色或黑色

  • 根节点必须是黑色

  • 所有NIL节点都是黑色的

  • 一条路径上不能出现相邻的两个红色节点

  • 在任何递归子树中,根节点到叶子节点的所有路径上包含相同数目的黑色节点

这五个约束条件保证了红黑树的新增、删除、查找的最坏时间复杂度均为O(logn)。如果一个树的左子节点和右子节点不存在,则均认定为黑色。红黑树的任何旋转在3次之内均可完成。

AVL树和红黑树的区别

红黑树的平衡性不如AVL树,它维持的只是一种大致的平衡,不严格保证左右子树的高度差不超过1。这导致节点数相同的情况下,红黑树的高度可能更高,也就是说平均查找次数会高于相同情况的AVL树。

在插入时,红黑树和AVL树都能在至多两次旋转内恢复平衡,在删除时由于红黑树只追求大致平衡,因此红黑树至多三次旋转可以恢复平衡,而AVL树最多需要O(logn)次。AVL树在插入和删除时,将向上回溯确定是否需要旋转,这个回溯的时间成本最差为O(logn),而红黑树每次向上回溯的步长为2,回溯成本低。因此面对频繁地插入与删除,红黑树更加合适。

数据库的索引为什么要用B+树,为什么不用红黑树或者B树?

B+树是一种特殊的平衡多路树,是B树的优化改进版本,它把所有的数据都存放在叶节点上,中间节点保存的是索引。这样一来相对于B树来说,减少了数据对中间节点的空间占用,使得中间节点可以存放更多的指针,使得树变得更矮,深度更小,从而减少查询的磁盘IO次数,提高查询效率。另一个是由于叶节点之间有指针连接,所以可以进行范围查询,方便区间访问。

而红黑树是二叉的,它的深度相对B+树来说更大,更大的深度意味着查找次数更多,更频繁的磁盘IO,所以红黑树更适合在内存中进行查找。

B树和B+树的区别

B树中每个节点同时存储key和data,而B+树中只有叶子节点才存储data,非叶子节点只存储key。且B+树的叶子节点上增加了一个指向相邻叶子节点的链表指针,形成了带有顺序指针的B+树,提高了区间访问的性能。由于B+树的数据都存储在叶子节点中,分支节点均为索引,方便扫库,只需要扫一遍叶子节点即可,但是B树因为其分支节点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引,而B树则常用于文件索引。

B+树的优点在于:

1、 由于B+树在非叶子节点上不含数据信息,因此在内存页中能够存放更多的key,数据存放得更加紧密,具有更好的空间利用率,访问叶子节点上关联的数据也具有更好的缓存命中率。

2、 B+树的叶子节点都是相连的,因此对整棵树的遍历只需要一次性遍历叶子节点即可。而B树则需要进行每一层的递归遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

但是B树也有优点,由于每个节点都包含key和value,因此经常访问的元素可能离根节点更近,访问也更迅速。

B-树,一个m阶的B树具有如下几个特征:

在这里插入图片描述

1、 根节点至少有两个子女

2、 每个中间节点都包含k-1个元素和k个孩子,其中m/2 <= k <= m

3、 每一个叶子节点都包含k-1个元素,其中m/2 <= k <= m

4、 所有的叶子节点都位于同一层

5、 每个节点中的元素从小到大排序,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。

B-树在查询中的比较次数其实不比二叉查找树少,尤其当单一节点中的元素数量很多时。但是相比磁盘IO的速度,内存中的比较耗时几乎可以忽略,所以只要树的高度足够低,IO次数足够少,就可以提升查找性能。相比之下节点内部元素多一些也没有关系,仅仅是多了几次内存交互,只要不超过磁盘页的大小即可。这就是B-树的优势之一。

B+树,一个m阶的B+树具有如下几个特征:

在这里插入图片描述

1、 有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。

2、 所有的叶子节点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子节点本身依关键字的大小自小而大顺序链接。

3、 所有的中间节点元素都同时存在于子节点中,在子节点元素中是最大(或最小)元素。

需要注意的是,根节点的最大元素,也就等同于整个B+树的最大元素。以后无论插入删除多少元素,始终要保持最大元素在根节点当中。

在B-树中,无论中间节点还是叶子节点都带有卫星数据。而在B+树中,只有叶子节点带有卫星数据,其余中间节点仅仅是索引,没有任何数据关联。

  • 首先,B+树的中间节点没有卫星数据,所以同样大小的磁盘页可以容纳更多的节点元素。这就意味着,数据量相同的情况下,B+树的结构比B-树更加“矮胖”,因此查询时IO次数也更少。

  • 其次,B+树的查询必须最终查找到叶子节点,而B-树只要找到匹配元素即可,无论匹配元素处于中间节点还是叶子节点。因此,B-树的查找性能并不稳定(最好情况是只查根节点,最坏情况是查到叶子节点)。而B+树的每一次查找都是稳定的。

  • 最后,B-树的范围查询只能依靠繁琐的中序遍历。而B+树只要先找到下限,再通过链表指针遍历就可以了。

B+树的优点:

1、 单一节点存储更多的元素(这样该节点下分支变多了,树变矮胖了),使得查询的IO次数更少。

2、 所有查询都要查找到叶子节点,查询性能稳定。

3、 所有叶子节点形成有序链表,便于范围查询。

快速排序(分治思想)

  • 选pivot,i = 0
  • j = size - 1,将比pivot小的数放在pivot,j–,大于或等于它的数就留在右边
  • 再对左右区间重复第二步,直到各区间只有一个数
//快速排序
void quick_sort(int s[], int l, int r)
{
   
   
    if (l < r)
    {
   
   
        //Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1
        int i = l, j = r, x = s[l];
        while (i < j)
        {
   
   
            while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
                j--;  
            if(i < j) 
                s[i++] = s[j];  
            while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数
                i++;  
            if(i < j) 
                s[j--] = s[i];
        }
        s[i] = x;
        quick_sort(s, l, i - 1); // 递归调用 
        quick_sort(s, i + 1, r);
    }
}

归并排序(分治思想)

归并排序是用分治思想,分治模式在每一层递归上有三个步骤:

  • 分解(Divide):将n个元素分成个含n/2个元素的子序列。
  • 解决(Conquer):用合并排序法对两个子序列递归的排序。
  • 合并(Combine):合并两个已排序的子序列已得到排序结果。

递归法

  • 开辟一个辅助数组用来存放临时结果,然后将其给原数组赋值
template<typename T>
void merge_sort_recursive(T arr[], T reg[], int start, int end) {
   
   
    if (start >= end)
        return;
    int len = end - start, mid = (len >> 1) + start;//移位操作,等效为除以2
    int start1 = start, end1 = mid;
    int start2 = mid + 1, end2 = end;//将一个数组划分成两个子区间,分别调用归并排序
    merge_sort_recursive(arr, reg, start1, end1);
    merge_sort_recursive(arr, reg, start2, end2);
    int k = start;
    while (start1 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值