文章目录
前言
应小伙伴的号召,打算写一篇关于数据结构的文章,也算是一段数据结构学习的总结吧
本篇文章不会详细讲解什么是二分查找,什么是跳表、字典、查找树,因为这类资料是非常多的(其中跳表会在下一篇文章中单独进行讲解)。本篇文章侧重点在于这些查找的方式各自的优劣处与如何选择的问题。建议读者先大致了解这些东西是什么再来阅读本篇文章,相信会有一定的启发作用。
这篇文章的议题主要是关于查找数据,围绕查找这一话题展开介绍各种用于查找的数据结构,这类数据结构非常多,各自有各自的优劣之处和自己的特色。本篇的议题有如下几个方面:
- 为何Redis使用跳表做有序集合?
- 为何MySQL使用查找树(B+Tree)作为索引实现?
- 字典表
- 二分查找(算法,但与议题相符,也一并讨论)
1. 快速查找的几种方式
要理解为什么中间件要选择某种数据结构,首先需要学习一下各自查找的数据结构和算法
很多快速的查找是 logN 时间复杂度,看起来不快,但实际计算一下会发现其其实相当恐怖,例如n如果等于2的32次方,就是42亿左右(java中int的正负范围),如果在42亿个数据中进行查找一个数据,仅仅需要32次!
1.1 二分查找
首先先来讲一个相对简单的算法,虽然不是数据结构但我也将其列入此篇文章中,是因为有如下优势:
- 其作为搜索算法,具有查找某个值相当快(log(N)的时间复杂度)
- 支持范围查找,这点有时候还是比较关键的
- 甚至比其他几种查找的方式更节省内存,只需要一个数组就可以撑起一个二分查找算法。对比字典,需要冗余数组空间,在解决哈希冲突时如果使用链表,还需要保存指针信息。对比查找树跳表,需要保存指针信息
但是其也具有一定的局限性:
- 此算法支撑的数据结构只能是数组,因为其使用下标访问是O(1)的复杂度,如果换成链表,遍历第N个位置的数据是O(N)的复杂度,所以链表无法作为其数据结构
- 只有有序的数据才可以使用二分查找
此时想象一个场景,一个有序的数组,其适用于二分查找,如果有新的数据来到,需要做增删的操作,那么就需要从数组中插入一条数据,我们知道,数组是一段连续的内存地址且容量固定(在分配时就指定好了),那么从中间插入一条数据需要将中间到结尾的数据全部都往后移动一位,如果容量不够,又需要新分配一个数组,然后将数据全部拷贝过去,从这点来看,二分查找是不适用于频繁插入和删除操作的场景,这里我们得出结论,如果有一次排序,多次查找的场景,才可以使用二分查找,并且这种方法又快又节省内存。
单值查找
在MySQL查询中,IN 函数就使用了二分查找的方式,像这么一条语句:
select * from city where city_id in (1,2,7,3,5,10)
假设 city_id 不是主键也没有索引,此时MySQL会先进行全表扫描,将 city 表中的数据先全部取出来,然后一条一条的将数据中的 city_id 列与 in 列表进行二分查找对比,看看此行数据是否在 in 列表中,如果是,加入结果集。
如果不用二分查找呢?假设 in 列表中有 n 个条件,表中有 m 条数据