什么是查找
查找可以定义如下
在一群元素中,找到这样一个元素,它满足给定的条件,这群元素具有相同的结构,其组织形式可以多种多样,比如,我们常见的数组,链表,树,图等等
而查找算法的目的就是要尽可能快的找到满足给定条件的元素,设想有一种方法,能让你直接拿到该元素,那么该算法的时间复杂度,就是O(1). 当然,这是最理想的情况,现实中我们要根据不同的数据结构,使用不同的方法,来不断的接近理想值。
那么,所谓的查找算法,为什么有的快,有的慢呢?你可以这样去理解。其实所有的查找算法都在做一件事,那就是排除
每一次查找操作,其实都是在做排除,如果说有一种方法能以O(1)的复杂度,找到元素,那么其实说明,它可以一下子排除候选集中的所有不相关元素,当然,这种算法,还是最理想化的。不同查找算法的优劣其实也就是看它能不能一次排除更多的元素,比如暴力穷举法,这种方式就是一次只能排除一个元素,显然是最慢的,如果一次能排除一半的元素,那么这就是二分,如果每一次都能排除一大半的元素呢?
所以学习查找算法时,要考虑这个算法,一次能排除多少元素,那么排除的越多,就是越好的。
因为讲查找都是基于某一数据结构,所以本篇文章介绍各个数据结构下的查找算法,讲解每个算法时,关注的是它排除了多少元素。(其实也就是时间复杂度了)
一 数组
无序数组
1. 顺序查找
这个就是暴力穷举了,每次只能排除一个,不提,时间复杂度是0(n)。
有序数组
有序数组当然是有序了,不过,它有一个特点,就是它的每一个元素相当于把整个候选集分成了两部分,每次可以排除一部分。这就比暴力穷举好多了。
1. 二分查找
这个大家比较熟悉了,每次可以排除一半,时间复杂度是O(log2N)
其实二分查找是一种盲目的猜,猜的就是中间元素就是要找的元素。那么,我们能不能猜的更准一点呢?其实中有的。
2. 插值查找
为什么叫插值,其实我也说不清楚,管它呢。。。
现在想象一下如下这个数组
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
这个数组也是有序的,但有一个特点,每个元素相差一样,说明它们之间是均匀分布的,那么让你来查找一个75,你会怎么办?
现在闭眼想象一下,元素都是均匀分布的,所以,你知道,50肯定在中间,90肯定接近末尾,20肯定离开头比较近。那么,我们可以每次查找时,找到最接近的那个数字,而它的位置大概就在
要找的位置 = (距离开头的距离 / 总的距离)* 真实的距离
所以,我们每次比较的位置就是通过以上公式来计算的,这样,每次就不像2分查找那样,傻傻的只排除一半了。
适用情况:均匀分布
3. Fibonacci 查找
其实也是一种划分数组,进行排除的方式,据说性能优于二分查找,但好像看不懂证明,所以,知道就好,用到再说,因为你不知道什么情况下优于二分查找
数据查找的参考
https://www.cnblogs.com/penghuwan/p/8021809.html#_label2
二 树
这节介绍数下的查找方法,树的排除和数组的排除类似,数组是排除一部分,树更形象一点,排除一个分支。又另一种方式来说,就是剪枝。
1 无序树(不限于二叉树)
这种没办法,使用深度或广度方式吧。
2 二叉有序树
这种类似二分查找,每次排除一个分支
3 2-3 查找树
这种树长这样。
其实就是有两种结点,2node和3node,和二叉树长的大差不差。查找方式也几乎雷同,只是构建这颗树比较有难度。。。
4 红黑树
红黑树就是树2-3树变成了二叉树,2-3树中的3结点变成红色链接,同样的,这个结构的难度在于建立这个树,查找方法和二叉查找树一样。
5 再谈,二叉查找树,2-3树,红黑树
以上说了,二叉查找树,2-3树,红黑树,整了这么多,最好也是O(log2N)的效率,而且2-3,红黑,似乎还要比二叉查找树复杂啊?
其实2-3树,红黑树都有一个目的,就是要尽量接近平衡二叉树,只有接近了平衡二叉树,才能有O(log2N)的。
这里我们其实还忽略了另一个方面,就是这个结构并不仅仅用来查找,在实际项目中,还要增加,删除结构中的元素,对于二叉查找树,随着增加,删除的增多,慢慢的变的不平衡了,不平衡了,查找效率就下来了,而为了维持平衡,要付出巨大的插入,删除代价,这显然是不可接受的(因为我们的实际业务不仅仅是要求查找效率高)
而2-3树,红黑树就在插入和删除时,能尽量维持平衡,这也就在插入,删除,查找之间找到了一种平衡点。
6 B 树
B树是2-3树的更一般化,它也是一种平衡树,结点也是有序的,每个结点可以有多个值,所以,很好理解嘛,它长这样,它的查找算法,你也大概能猜到吧
7 B+ 树
B+树和B树类似,关于B树,B+树的应用,可以参考[数据库基础概念]中关于索引的讨论一文
三 散列表
我们知道元素是在计算机中存储的,那么每个元素,就有一个地址,找到了这个地址,想当于找到了这个元素。
其实无论是数组也好,树也好,我们拿一个元素来检查是不是要找的值,实际上是先通过地址来得到元素的。所以说,地址很关键。
最简单的地址就是数组的Index了。
那么说,能不能直接定位到要查找元素的地址呢?假如我们有一个函数,可以根据元素的值,来计算出它应该出现的地址。是不是就不能一个个去比较了呢?就像下面这个公式
address = f(x) x为要查找的元素
而散列表的思想就是来自于此。以上函数就是散列表中最关键的hash函数。
散列表一般长这样
一般散列表的结构都是有一个数组+链表来组成的。理想情况下,可以根据要查找的元素,直接计算出地址。但那是理想情况下。
散列表的关键设计就是散列函数了,这个函数要让值均匀分布才好
四 另话
其实,以上讨论的都是在一个数据集上的查找操作。
在实际应用中,我们的数据集上还上增加和删除操作。
有的算法和数据结构对查找有好,但是对增加和删除不友好。
所以我们要根据自己的数据集的特点,看查找,增加,删除,更新这些操作在该数据集上的频率等等,再选择是否需要使用这个数据结构。
当然还要考虑到实际计算机中不同存储体系的性能差距,如B树,B+树在数据库中的应用就体现了这一点。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。