查找是一个很基本的针对数据集的操作。按照数据集的数据结构可以分为如下几部分。
1.数组的查找
2.哈希表的查找
3.树的查找
4.其他:串的查找,基于索引查找
1.数组
数组的基本查找策略是顺序搜索与折半查找。若数据无序,则顺序查找,挨个与目标关键字进行比较,时间复杂度为O(n);若数组有序,则可以采用折半查找,折半查找实质是在构造一棵二叉的比较树,这棵比较树左右子树深度之差最大为1;因此n个节点的比较树其深度为ceil(log2 n),最大的比较次数为ceil(log2 n)。
折半查找构成的比较树与排序构成的比较树有点点类似(主要是都是通过比较构造树,仅此)。(我觉得折半查找的本质就是在构造一颗二叉查找树,但是没有在书中找到类似的描述) 折半比较树叶子节点与非叶子节点均为数组中的值,而对排序的比较树而言,非叶子节点表示还有元素间的大小关系没有比较出来,每一个叶子节点对应一个可能的排序结果,n个元素有n!个可能的排序结果,因此叶子节点数量为n!。通过排序的比较过程,可以类推在最坏情况下,对n个元素进行排序,至少需要的比较次数为ceil(log2 n!)。
2.哈希表的查找
哈希表期望的是能够以O(1)时间对数据元素进行查找。通过定义装载因子a(表中记录元素与哈希表元素),可以计算出每次进行成功查找,或者不成功查找的期望查找长度。
通过链接法解决冲突:成功查找的期望查找长度O(1+a), 不成功查找的平均查找长度也为O(1+a)
开放寻址解决冲突:成功查找的平均查找长度1/a(1+ln(1/(1-a)); 不成功的查找长度为1/(1-a)
通过开放寻址解决冲突时,哈希表的期望查找长度与占用内存空间(即哈希表长度)是一对矛盾。(假设在记录n一定的情况下)我们希望平均查找长度越短越好,希望哈希表长度越短越好。哈希表越短,装载因子越大,发生冲突可能性越高,平均查找长度越长;反之哈希表越长,则平均查找长度越短。哈希表在存储同样的记录时,比数组与二叉排序树会占用更多的空间。
(电子书的算法导论把hash翻译成杂凑,很奇怪,专门去查了下,原来hash的本意是杂凑,google到很多繁体翻译为杂凑,呵呵~~不过已经习惯哈希或者散列的翻译)
链接法将所有映射到同一地址的关键字放在一个动态链表里,发生冲突的可能性比开放寻址小,因此平均查找长度要略小一点;链接法中各链表上的节点空间是根据记录动态申请的,因此当数据记录的数量无法确定时,采用链接法有更好的效果;若采用开放寻址,如果装载因子较小,则浪费空间;若装载因子较大,则会增大平均查找长度(当然,记录数较多时,采用链接法也会增大平均查找长度;但采用开放寻址的增大会更显著一些)开放寻址的装载因子必须有a<1;链接法的装载因子可以a>1;链接法删除节点直接删除单链表上的节点即可;开放寻址貌似只需要设置一个指定的标志位即可。链接法的指针域需要额外的空间,当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装载因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度(啊,好绕口!这两者的比较其实是个比较矛盾的比较。因为两者都需要额外的存储空间,一句话,具体情况具体分析)。链接法需要一个额外的数据结构来支撑:链表;开放寻址需要设计一个仔细的探测序列进行探测。
3.树的查找
树的查找的重点是二叉查找树。一颗高度为h的二叉查找树可以在O(h)时间复杂度内实现任何一种基本的动态集合操作,比如查询,最大值,最小值,插入,删除等。(算法导论Chapter13)
平衡二叉查找树是基于二叉查找树的改进。其目的是通过减少树的高度来改善操作的时间复杂度。一颗n结点的平衡二叉树的高度为floor(log2 n)。红黑树是平衡二叉查找树的一种。因此红黑树可以在O(log n)时间复杂度下完成树的各项动态操作。
注意:由于平衡二叉查找树在各种操作之后仍然需要保持树是平衡查找的,所以操作有点点麻烦。。很猥琐,不想看...
还有一种二叉查找树的推广:B树。其与红黑树的不同之处是,红黑树是二叉的,Btree是m叉的。
4.其他
串的查找已经写过了,蛮力搜索,KMP,BM,DFA。
链表的查找是顺序搜索,没其他办法。
栈和队列的查找则是。。。这两个数据结构不支持的方法。。。如果要让他们支持查找,还真要改改数据结构。
基于索引的查找则是先查找索引,然后在索引项目下面继续查找。