查找算法
1. 简介
查找也称为搜索,是指从集合中找到指定元素的过程。在算法编程过程中经常有查找的需求,编程语言也内置了便于查找的函数,如python中可以借助 in 查询指定元素是否在列表中,使用起来是非常方便的,这里将深入的了解查找过程的原理。
本文主要介绍顺序查找、二分查找、散列查找。
2. 顺序查找
顺序查找的原理就是从数据结构的第一个元素开始,逐个遍历,直到遍历完列表或者查找到指定元素。顺序查找一般用在无序的线性结构(如单链表)。
python实现顺序查找算法;
查询指定元素是否在列表中,若在,返回元素位置,否则返回False
def sequenceSearch(numlist, target): for i in range(len(numlist)): if numlist[i] == target: return i return False
顺序查找的时间复杂度为: O ( n ) O(n) O(n)
最好情况是第一个元素就是指定元素,此时只需要查询1次,而最坏的情况是查询 n n n次(指定元素在列表中,且最后一个元素才是指定元素,或者指定元素不在列表中),而普通情况为 n / 2 n/2 n/2(指定元素不在列表时为 n n n)
3. 二分查找
二分查找也称二分搜索、折半查找,是对有序列表进行搜索的方法,无序列表则需要先排序后再搜索。二分查找从中间开始查询,每一次查找遍历可排除一半的元素。
python实现二分查找算法:使用列表实现顺序查找;
查询指定元素是否在列表中,若在,返回元素位置,否则返回False:
def binarySearch(numlist, target): left = 0 right = len(numlist)-1 while left <= right: mid = (left + right)//2 if numlist[mid] == target: return mid else: if numlist[mid] > target: right = mid - 1 else: left = mid +1 return False
二分查找的时间复杂度为: O ( l o g n ) O(logn) O(logn)
记查找次数为 i i i次, n n n为列表长度,那么存在 n / 2 i = 1 n/2^i=1 n/2i=1 , i = l o g n i = logn i=logn。
4. 散列查找
4.1 散列查找基本思想
散列(哈希)查找的基本思路是通过某种转化关系将元素与其存储位置一一对应,那么只要知道了元素,就可以通过转化关系获取元素的存储位置,搜索一次即可,算法复杂度为 O ( 1 ) O(1) O(1)。
以上的转化关系称为散列函数(哈希函数),散列函数的作用就是将元素及其所处位置对应起来。散列函数在映射中起着非常重要的作用,是散列查找算法实现的核心部分,找到一个适合的散列函数是使用散列查找算法的前提。
散列查找例子
存在一个集合{20, 26, 29, 52, 77, 93},使用散列查找。
使用一个长度为7列表,选用常见的取余函数作为散列函数,将集合元素作为被除数,列表长度作为除数,即将集合{20, 26, 29, 52, 77, 93}中的每一个元素对应除以7,得到对应的余数集合为{6, 5, 1, 3, 0, 2}(称为散列值),接着按{6, 5, 1, 3, 0, 2}顺序将元素对应放入列表中得到以下存储结构。
当需要查找时,比如查找26,那么使用散列函数计算得到5,检查列表索引为5的位置是否有元素即可知道26是否在列表集合中。def hashSearch(target): listlen = 7#指定列表长度 numlist=[20, 26, 29, 52, 77, 93] hashlist = [None for i in range(listlen)] for i in numlist: hashlist[i%listlen] = i if hashlist[target%listlen] == target: return target%listlen else: return False
在上面散列查找的例子中,{20, 26, 29, 52, 77, 93}这六个元素使用取余函数存储在长度为7的列表中,刚好是完全一对一映射的关系,这样的散列函数称为完美散列函数,但在实际操作中很难保证散列函数是完美的,例如{20, 26, 29, 52, 77, 93, 7},77和7取余后就都是0,这是就会存在冲突,创建完美散列函数是困难的,而且可能会耗费巨大的内存空间,通常情况下,创建散列函数的目标便转化为“冲突尽量少,计算方便,元素尽可能的分布在散列表中”,因而在使用散列查找时,除了散列函数,处理冲突也是需要解决的关键问题。
常用哈希函数:
① 取余函数
② 折叠法:将元素分割成等长的几部分(最后一部分位数可以不同),然后将这几部分的相加,得到散列值;如11011123,按两位数切割,得到11,01,11,23,相加得到散列值36。
③ 平方取中法:取元素平方后的中间几位作为散列地址;1102=12100,取中间三位数得到散列值210;
④ 随机数法:取元素作为随机函数种子,生成随机值作为散列地址;在python中可使用random.seed()实现import random random.seed(2) # random.randint(0,100) #输出7
常用处理冲突方法:
① 开放定址法:当放置元素引冲突时,从头按顺序遍历散列表,直到找到一个空槽存放当前元素,该方法容易产生“聚集”现象(元素在散列表中存放集,不均匀);
② 再散列法:在产生地址冲突时,计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间;
③ 链接法:让每个槽存放一个链表,相同散列值的元素依次存放在对应位置的链表中。