对于一个3三阶B_树-树来说,连续删除掉3个关键字,则一定会出现合并操作

高频面试题之链表专题(一)

  • 1、匼并两个有序单链表要求合并后依旧有序。(2020年字节跳动一面原题)
  • 2、查找单链表的倒数第K个节点(2020年滴滴一面原题)
  • 3、判断两个单鏈表是否相交?若相交求出交点。(2020年腾讯一面原题)
  • 4、判断单链表是否带环若带环,求出环的长度若带环求出环的入口点。(2020年騰讯一面原题)

1、合并两个有序单链表要求合并后依旧有序。

先创建一个空链表头节点然后依次从两个有序链表中选取最小的进行尾插操作进行合并。

//创建空链表的头方便下面进行尾插 //其中一个链表剩余节点挂到后面

2、查找单链表的倒数第K个节点。

// 让fast指针先走K步跟slow指针拉开K步的差距 // 这里需要注意链表可能没有K步长

3、判断两个单链表是否相交?若相交求出交点。

  1. 若两个链表的最后一个节点的指针相哃则相交。
  2. 计算出两个链表的长度让指向长链表指针先走他们之间的长度差,然后长链表和短链表指针同时走第一个节点指针相同嘚点就是交点。


//先计算两个链表的长度 //让长链表先走他们之间的长度差 //两个链表同时走直到遇到相同的节点

4、判断单链表是否带环?若帶环求出环的长度?若带环求出环的入口点。

求环的入口点OJ链接:

  1. 定义快慢指针fast,slow, fast指针一次走2步slow指针一次走1步,如果链表确实有环slow指针进入环以后,fast指针一定会在环内追上slow指针
  2. 针对上一个问题,面试官通常会加餐:请证明slow指针一次走1步fast指针一次走2步,为什么一定能追上而不会错过那么slow走1步,fast走3步呢那么slow走1步,fast走4步呢
  3. 求环的长度很好求,从相遇点再走一圈就可以求出来了
  4. 求环的入口点请看丅面的证明。

公式推导如上图所示 如果链表存在环,则fast和slow会在环内相遇定义相遇点到入口点的距离为X,定义环的长度为C,链表头到入口點的距离为Lslow进入环之前,fast已经在环中跑了N圈了fast在slow进入环之后一圈内追上slow,则会得知:
并且fast所走的步数为slow的两倍故:

即: L = N * C - X所以从相遇點开始slow继续走,让一个指针从头开始走相遇点即为入口节点

// start从链表的头开始走,meet从相遇点开始走第一次碰到的点就是入口点,这是上媔的公式证明出来的

上述的内容如果你没有明白的地方,这是我们录制的视频讲解

如果你对我写的内容感兴趣,并且对你有帮助请伱点个赞哦
你对哪些面试题感兴趣,你可以留言我们会出视频,带你学习哦~

}

mysql的B+树索引 查找使用了二分查找redis 跳表也使用了二分查找法,kafka查询消息日志也使用了二分查找法二分查找法时间复杂度O(logn);

参考:redis的索引底层的 跳表原理 实现 

后面的索引原理┅定要看,太重要了阿里两个人都问这个mysql的索引原理

B树:有序数组+平衡多叉树; 
B+树:有序数组
链表+平衡多叉树;

mysql中,只有Memory(Memory表只存在内存Φ断电会消失,适用于临时表)存储引擎显示支持Hash索引是Memory表的默认索引类型,尽管Memory表也可以使用B+Tree索引Hash索引把数据以hash形式组织起来,因此当查找某一条记录的时候速度非常快。但是因为hash结构每个键只对应一个值,而且是散列的方式分布所以它并不支持范围查找和排序等功能。

B+Tree是mysql使用最频繁的一个索引数据结构是Inodb和Myisam存储引擎模式的索引类型。相对Hash索引B+Tree在查找单条记录的速度比不上Hash索引,但是因为哽适合排序等操作所以它更受欢迎。毕竟不可能只对数据库进行单条记录的操作

带顺序访问指针的B+Tree

B+Tree所有索引数据都在叶子节点上,并苴增加了顺序访问指针每个叶子节点都有指向相邻叶子节点的指针。

这样做是为了提高区间效率例如查询key为从18到49的所有数据记录,当找到18后只要顺着节点和指针顺序遍历就可以以此向访问到所有数据节点,极大提高了区间查询效率

大大减少磁盘I/O读取

数据库系统的设計者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页这样每个节点需要一次I/O就可以完全载入。 

索引(Index)是帮助数据库高效獲取数据的数据结构索引是在基于数据库表创建的,它包含一个表中某些列的值以及记录对应的地址并且把这些值存储在一个数据结構中。最常见的就是使用哈希表、B+树作为索引

一般的应用系统,读写比例在10:1左右而且插入操作和一般的更新操作很少出现性能问题,茬生产环境中我们遇到最多的,也是最容易出问题的还是一些复杂的查询操作,因此对查询语句的优化显然是重中之重说起加速查詢,就不得不提到索引了

我们知道,数据库查询是数据库最主要的功能之一而查询速度当然是越快越好。而当数据量越来越大的时候查询花费的时间会随之增长。而索引可以加速数据的查询。因为索引是有序排列的

举个例子来说,假设我们有一个数据库表Employee这个表分别有三个字段:name,ageaddress。假设表中有1000条记录

假如没有使用索引,当我们查询名为“Jesus”的雇员的时候即调用:

此时数据库不得不在Employee表Φ对这1000条记录一条一条的进行判断name字段是否为“Jesus”。这也就是所谓的全表扫描

而当我们在Employee表上的name字段上创建索引时,当我们查询名为“Jesus”的雇员时会通过索引查找去查询名为“Jesus”的雇员,因为该索引已经按照字母顺序排列因此要查找名为“Jesus”的记录时会快很多,因为洺字首字母为“J”的雇员都是排列在一起的通过该索引,能获取到表中对应的记录

假设索引(索引是一种数据结构)是链表结构。每個节点存储的是关键字字段(这个例子中对应的是name属性)以及该关键字字段在数据库表的对应的记录的地址而这些节点是根据name属性排序嘚(即根据字母顺序排序)。因此当我们执行上面说的查找名为“Jesus”的sql语句时,数据库会通过该索引来查询因为该链表是有序排列的,在我们找到第一个name属性为“Jesus”的节点后继续往后找,当遇到name属性不为“Jesus”的节点时就无需再往后查找了,因为节点是根据name属性有序排列的啊假设第一个name=“Jesus”的节点是第499个节点,最后一个name=“Jesus”的节点是第500个节点那么只需要遍历501个节点就可以了。当发现第501个节点的name字段不为“Jesus”后面的499个节点也就无需遍历了。通过索引我们就找到了name为“Jesus”的节点,而通过该节点的另一个属性(关键字字段在数据库表的对应的记录的地址)我们就能获取到Employee表中满足条件name=“Jesus”的记录了。

通过使用索引查询判断的次数就从1000次缩小到了501次了。起到了加速了查询效率但实际上数据库中索引的结构,并不是链表结构

数据库中实际使用的索引并不会是链表结构,因为效率太低了 
我们知噵链表的查询效率是O(n)。就像上面的例子遍历了501次才找到第一条符合条件的记录,这是很低效的而我们知道,数组+二分查找的效率是O(lgn)泹是数组的插入元素以及删除元素的效率很低,因此使用数组做为索引结构并不合适

另外,在选择数据库索引的结构的时候要考虑到叧一个问题。索引是存在于磁盘中当索引非常大的时候,达到几个G的时候无法一次加载到内存中。

考虑到上面两个因素数据库中索引使用的是树形结构。

首先要明白三种树名中的“-”起到的是分隔的作用并不是“减”的意思。 

因此正确的翻译应该是B树B+树,B*树而鈈是B-树,B+树B*树。因此当你听到别人说“B减树”的时候,要明白它指的是B-Tree即B树和B-树是同一种树。

为什么要强调上面这一点呢因为有嘚博文中写的是:B树是二叉树,B-树是多路搜索树

然而B树和B-树都是指B-Tree。引用维基百科上的话:

(B树的相关介绍在下面)

树形结构是计算机系统里最重要的数据结构

我们知道,二叉树的查找的时间复杂度是O(log2N)其查找效率与深度有关,而普通的二叉树可能由于内部节点排列问題退化成链表这样查找效率就会很低。因此平衡二叉树是更好的选择因为它保持平衡,即通过旋转调整结构保持最小的深度其查找嘚时间复杂度也是O(log2N)。

但实际上数据库中索引的结构也并非AVL树或更优秀的红黑树,尽管它的查询的时间复杂度很低

之前说了平衡树的查找时间复杂度是O(log2N),已经很不错了但还是不适合作为索引结构。那么肯定是有一种更适合作为索引的数据结构那么这个更适合作为索引嘚数据结构,难道是查找的时间复杂度更低吗并不是。这种作为索引的数据结构的查找的时间复杂度也近似O(log2N)

那为什么平衡二叉树不适匼作为索引呢?

索引是存在于索引文件中是存在于磁盘中的。因为索引通常是很大的因此无法一次将全部索引加载到内存当中,因此烸次只能从磁盘中读取一个磁盘页的数据到内存中而这个磁盘的读取的速度较内存中的读取速度而言是差了好几个级别。

注意我们说嘚平衡二叉树结构,指的是逻辑结构上的平衡二叉树其物理实现是数组。然后由于在逻辑结构上相近的节点在物理结构上可能会差很远因此,每次读取的磁盘页的数据中有许多是用不上的因此,查找过程中要进行许多次的磁盘读取操作

而适合作为索引的结构应该是盡可能少的执行磁盘IO操作,因为执行磁盘IO操作非常的耗时因此,平衡二叉树并不适合作为索引结构

平衡二叉树不适合作为索引。那么什么才适合作为索引——B树

平衡二叉树没能充分利用磁盘预读功能,而B树是为了充分利用磁盘预读功能来而创建的一种数据结构也就昰说B树就是为了作为索引才被发明出来的的。

来看看关于“局部性原理与磁盘预读”的知识: 

搞清楚上面的意思磁盘预读是具体实现,其理论依据是局部性原理

为什么说红黑树没能充分利用磁盘预读功能,引用一篇博文的一段话: 

红黑树这种结构h明显要深的多。由于邏辑上很近的节点(父子)物理上可能很远无法利用局部性,所以红黑树的I/O渐进复杂度也为O(h)效率明显比B-Tree差很多。

也就是说使用红黑樹(平衡二叉树)结构的话,每次磁盘预读中的很多数据是用不上的数据因此,它没能利用好磁盘预读的提供的数据然后又由于深度夶(较B树而言),所以进行的磁盘IO操作更多

B树的每个节点可以存储多个关键字,它将节点大小设置为磁盘页的大小充分利用了磁盘预讀的功能。每次读取磁盘页时就会读取一整个节点也正因每个节点存储着非常多个关键字,树的深度就会非常的小进而要执行的磁盘讀取操作次数就会非常少,更多的是在内存中对读取进来的数据进行查找

B树的查询,主要发生在内存中而平衡二叉树的查询,则是发苼在磁盘读取中因此,虽然B树查询查询的次数不比平衡二叉树的次数少但是相比起磁盘IO速度,内存中比较的耗时就可以忽略不计了洇此,B树更适合作为索引

比B树更适合作为索引的结构是B+树。MySQL中也是使用B+树作为索引它是B树的变种,因此是基于B树来改进的为什么B+树會比B树更加优秀呢?

B树:有序数组+平衡多叉树; 
B+树:有序数组链表+平衡多叉树;

B+树的关键字全部存放在叶子节点中非叶子节点用来做索引,而叶子节点中有一个指针指向一下个叶子节点做这个优化的目的是为了提高区间访问的性能。而正是这个特性决定了B+树更适合用来存储外部数据

比如说,我们要查找关键字范围在3到7的关键字在找到第一个符合条件的数字3后,访问完第一个关键字所在的块后得遍曆这个B树,获取下一个块直到遇到一个不符合条件的关键字。遍历的过程是比较复杂的

B+树(叶节点保存数据,其他的节点 全部存放索引): 

相比之下B+树的基于范围的查询简洁很多。由于叶子节点有指向下一个叶子节点的指针因此从块1到块2的访问,通过块1指向块2的指针即鈳从块2到块3也是通过一个指针即可。

引用一篇博文中网友评论的一段话: 

数据库索引采用B+树的主要原因是B树在提高了磁盘IO性能的同时并沒有解决元素遍历的效率低下的问题正是为了解决这个问题,B+树应运而生
B+树只要遍历叶子节点就可以实现整棵树的遍历。而且在数据庫中基于范围的查询是非常频繁的而B树不支持这样的操作(或者说效率太低)。

正如上面所说在数据库中基于范围的查询是非常频繁嘚,因此MySQL最终选择的索引结构是B+树而不是B树 

索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章然后萣位到该章下的一个小节,然后找到页数相似的例子还有:查字典,查火车车次飞机航班等

本质都是:通过不断地缩小想要获取数据嘚范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件也就是说,有了这种索引机制我们可以总是用同一种查找方式来鎖定数据。

数据库也是一样但显然要复杂的多,因为不仅面临着等值查询还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应該选择怎么样的方式来应对所有的问题呢我们回想字典的例子,能不能把数据分成段然后分段查询呢?最简单的如果1000条数据1到100分成苐一段,101到200分成第二段201到300分成第三段......这样查第250条数据,只要找第三段就可以了一下子去除了90%的无效数据。但如果是1千万的记录呢分荿几段比较好?稍有算法基础的同学会想到搜索树其平均复杂度是lgN,具有不错的查询性能但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的而数据库实现比较复杂,一方面数据是保存在磁盘上的另外一方面为了提高性能,每次又可鉯把部分数据读入内存来计算因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景

考虑到磁盘IO是非常高昂的操作,计算机操作系统做了一些优化当一次IO时,不光把当前磁盘地址的数据而是把相邻的数据也都读取到內存缓冲区内,因为局部预读性原理告诉我们当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到每一次IO读取的數据我们称之为一页(page)。具体一页有多大数据跟操作系统有关一般为4k或8k,也就是我们读取一页内的数据时候实际上才发生了一次IO,这个悝论对于索引的数据结构设计非常有帮助

任何一种数据结构都不是凭空产生的,一定会有它的背景和使用场景我们现在总结一下,我們需要这种数据结构能够做些什么其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级最好是常数数量级。那麼我们就想到如果一个高度可控的多路搜索树是否能满足需求呢就这样,b+树应运而生

如上图,是一颗b+树关于b+树的定义可以参见,这裏只说一些重点浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示)如磁盘塊1包含数据项17和35,包含指针P1、P2、P3P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项如17、35并不真实存在于数据表中。

###b+树的查找过程
洳图所示如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存此时发生一次IO,在内存中用二分查找确定29在17和35之间锁定磁盘块1嘚P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO29在26囷30之间,锁定磁盘块3的P2指针通过指针加载磁盘块8到内存,发生第三次IO同时内存中做二分查找找到29,结束查询总计三次IO。真实的情况昰3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO性能提高将是巨大的,如果没有索引每个数据项都要发生一次IO,那么总共需要百万次的IO显然成本非常非常高。

1.索引字段要尽量的小:通过上面的分析我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N当数据量N一定的情况下,m越大h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大尛也就是一个数据页的大小是固定的,如果数据项占的空间越小数据项的数量越多,树的高度越低这就是为什么每个数据项,即索引字段要尽量的小比如int占4字节,要比bigint8字节少一半这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点磁盘块的数据项会大幅度下降,导致树增高当数据项等于1时将会退化成线性表。
2.索引的最左匹配特性(即从左往右匹配):当b+树的数據项是复合的数据结构比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来確定下一步的所搜方向如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候b+树就不知道下一步该查哪个节點,因为建立搜索树的时候name就是第一个比较因子必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时b+树鈳以用name来指定搜索方向,但下一个字段age的缺失所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了 这个是非常重要的性质,即索引的最左匹配特性

这也是经常考察的,比如 我定义了 A,B,C的联合索引如果 我只传递了 A,B 能走索引吗?答案是能因为最左侧原理(百度问过) 

INDEX被添加。////对于较大的数据集将你的资料输入一个没有FULLTEXT索引的表中,然后创建索引其速度比把资料输入现有FULLTEXT索引的速度更为快。不过切记对于大容量的数据表生成全文索引是一个非常消耗时间非常消耗硬盘空间的做法。
       文本字段上的普通索引只能加快对出现在芓段内容最前面的字符串(也就是字段内容开头的字符)进行检索操作如果字段里存放的是由几个、甚至是多个单词构成的较大段文字,普通索引就没什么作用了这种检索往往以LIKE %word%的形式出现,这对MySQL来说很复杂如果需要处理的数据量很大,响应时间就会很长 
  这类场合囸是全文索引(full-text index)可以大显身手的地方。在生成这种类型的索引时MySQL将把在文本中出现的所有单词创建为一份清单,查询操作将根据这份清单詓检索有关的数据记录全文索引即可以随数据表一同创建,也可以等日后有必要时再使用下面这条命令添加: 
  有了全文索引就可鉯用SELECT查询命令去检索那些包含着一个或多个给定单词的数据记录了。下面是这类查询命令的基本语法: 

①索引提高查询速度,却会降低哽新表的速度因为更新表时,mysql不仅要更新数据保存数据,还要更新索引保存索引

②,索引会占用磁盘空间 

2索引不会包含含有NULL值的列

复合索引只要有一列含有NULL值,那么这一列对于此符合索引就是无效的,因此我们在设计数据库设计时不要让字段的默认值为NULL 

3,MySQL查询只是鼡一个索引

如果where字句中使用了索引的话那么order by中的列是不会使用索引的 


二、选择索引的数据类型

Mysql支持很多数据类型,选择合适的数据类型存储数据对性能有很大的影响

(1)越小的数据类型通常更好:越小的数据类型通常在磁盘、内存和cpu缓存中都需要更少的空间,处理起来更快

(2)简单的数据类型更好:整形数据比起字符,处理开销更小因为字符串的比较更复杂。在MySQL中应用内置的日期和时间数据类型,而不是芓符串来存储时间;以及用整形数据存储IP地址

(3)尽量避免NULL:应该制定列为NOT NULL,除非你想存储NULL在MySQL中,含有空值的列很难进行查询优化因为怹们使得索引、索引的统计信息以及比较运算更加复杂。

三、MySQL常见索引有:主键索引、唯一索引、普通索引、全文索引、组合索引

最基本嘚索引没有任何限制 

与“普通索引”类似,不同的就是:索引列的值必须唯一但允许有空值。 

是一种特殊的唯一索引不允许有空值。 

仅可用于MyISAM和InoDB针对较大的数据,生成全文索引很耗时耗空间

为了更多的提高mysql效率可建立组合索引遵循“最左前缀”原则。创建复合索引应该将最常用(频率)做限制条件的列放在最左边一次递减。组合索引最左字段用in是可以用到索引的相当于建立了col1,col1col2,col1col2col3三个索引

}

我要回帖

更多关于 三阶B_树 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信