含义
搜索是指从元素集合找到某个特定元素的算法过程。
搜索过程通常返回True或False,分别表示元素是否存在。
有时,可以修改搜索过程,使其返回目标元素的位置。
顺序搜索
"""
存储于列表等集合中的数据项彼此存在线性或顺序的关系,每个数据项的位置与其他数据项相关。
元素都是有序的,可以顺序访问,由此可以进行顺序搜索。
时间复杂度: O(n)
"""
# 无序列表的顺序搜索
def sequentialSearch(alist, item):
pos = 0
found = False
while pos < len(alist) and not found:
if alist[pos] == item:
found = True
else:
pos += 1
return found
# 有序列表的顺序搜索
def orderedSequentialSearch(alist, item):
pos = 0
found = False
stop = False
while pos < len(alist) and not found and not stop:
if alist[pos] == item:
found = True
else:
if alist[pos] > item:
stop = True
else:
pos += 1
return found
二分搜索
# 有序列表的二分搜索(分治策略的例子)
# 分治:将问题分解成小问题,以某种方式解决小问题,然后整合结果,以解决最初的问题。
# 时间复杂度: O(logn)
def binarySearch(alist, item):
first = 0
last = len(alist) - 1
found = False
while first <= last and not found:
midpoint = (first + last) // 2
if alist[midpoint] == item:
found = True
else:
if item < alist[midpoint]:
last = midpoint - 1
else:
first = midpoint + 1
return found
# 递归版本
def binarySearch2(alist, item):
if len(alist) == 0:
return False
else:
midpoint = len(alist) // 2
if alist[midpoint] == item:
return True
else:
if item < alist[midpoint]:
return binarySearch2(alist[:midpoint], item)
else:
return binarySearch2(alist[midpoint+1:], item)
散列
散列函数一定要高效,以免它成为存储和搜索过程的负担。
如果散列函数过于复杂,引入的计算复杂度更高。
# 通过散列构建一个时间复杂度为O(1)的数据结构。
# 散列函数将散列表中的元素与其所属位置对应起来。
# 对散列表中的任一元素,散列函数返回一个介于0和m-1之间的整数。
# 散列函数可能存在冲突,也叫碰撞。显然,冲突给散列函数带来了问题。
# 给定一个元素集合,能将每个元素映射到不同的槽,这种散列函数称作完美散列函数。
# 构建完美散列函数的一个方法是增大散列表,使之能容纳每一个元素。
# 通常选择: 冲突数最少,计算方便,元素均匀分布于散列表中。
# 折叠法: 先将元素切成等长的部分(最后一部分的长度可能不同),然后将这些部分相加,得到散列值。
# 如436-555-4601,两位切割(43,65,55,46,01),相加得210,假设有11个槽,210除以11,得余数1,
# 即放到1号槽。也或者更进一步,在加总前每隔一个数反转一次。即(43,56,55,64,01)219%11=10。
# 平方取中法: 先将元素取平方,然后提取中间几位数。如果元素是44,先计算44的平方,然后提取中间两位,
# 44²=1936,中间两位93,然后取余,得5(93%11)。
# 为字符串构建简单的散列函数,针对异序词,下列散列函数得到值相同。此时可以引入字符位置作为权重因子。
# 即 c a t --->>> 99*1 + 97*2 + 116*3 = 641
def hash(astring, tablesize):
sum = 0
for pos in range(len(astring)):
sum += ord(astring[pos])
return sum%tablesize
# 处理冲突
# 当两元素被分配到同一个槽中时,必须通过一种系统化方法在散列表中安置第二个元素,即处理冲突。
# 如果散列函数是完美的,冲突就永远不会发生。然而,此前提往往不成立,因此处理冲突是散列计算重点。
# 解决方法:
# 一种方法:在散列表中找到另一个空槽,用于放置引起冲突的元素。简单做法是从初始散列开始,顺序
# 遍历散列表,直到找到一个空槽。此方法被称为开放定址法,它尝试在散列表中寻找下一个
# 空槽或地址。由于是逐个访问槽,因此也称作线性探测。
# 散列构建完成后,即可使用同样方法搜索元素。
# 缺点:线性探测会使元素出现聚集现象。即一个槽冲突太多,线性探测会填充满其附近槽。
# 为避免元素聚集,可扩展线性探测,不再依次查找,而是跳过一些槽,可使引起冲突的元素均匀。
# rehash(pos) = (pos+skip)%sizeoftable。跨步大小需要保证所有槽都可被访问到,否则
# 会浪费槽资源。要保证这一点,通常设散列表大小为素数。
# 平方探测: 跨步大小是一系列完全平方数。
# 另一种方法: 链接法
# 让每个槽指向一个元素集合(或链表)的引用。即允许一个位置上有多个元素。发生冲突时,元素
# 仍然被插入其散列值对应的槽中。不过,如果同一个位置上的元素越来越多,搜索会越困难。
# 优点: 平均算来,每个槽元素不多,因此搜索可能更高效。
#
# 分析散列搜索算法:
# 最好情况下为O(1),即常数阶。但是,因为可能发生冲突,所以通常不会如此简单。
# 分析散列情况时,最重要的信息就是载荷因子λ。如果λ很小,那么发生冲突的概率就小。反之变大。
# 平均次数:
# 线性探测:0.5*[1+1/(1-λ)]
# 失败: 0.5*[1+1/(1-λ)²]
# 链接法: 1+λ/2
# 失败: λ