搜索(或查找)是指从元素集合中找到某个特定元素的算法过程。
静态查找是指只在数据元素集合中查找是否存在关键字等于某个给定关键字的数据元素。
动态查找除包括静态查找的要求外,还包括在查找过程中同时插入数据元素集合中不存在的数据元素,或者从数据元素集合中删除已存在的某个数据元素的要求。
顺序搜索
存储于列表等集合(线性表)中的数据彼此之间存在线性或顺序的关系,每个数据项的位置与其他数据项相关。从集合的一端开始,用给定数据元素的关键字逐个与顺序表中各数据元素的关键字进行比较,若在集合中查找到要查找的元素,则查找成功,函数返回数据元素所在的位置,否则查找失败。
顺序搜索又可分为无序表顺序搜索和有序表(数据元素已经排序)顺序搜索。
无序列表(顺序表)搜索
Python 实现无序表顺序搜索:
def sequentialSearch(alist, item):
pos = 0
found = False
size = len(alist)
while pos < size and not found:
if alist[pos] == item:
found = True
else:
pos = pos + 1;
return found, pos
slist = [54, 26, 93, 17, 77, 31, 44, 55, 20, 65]
print(sequentialSearch(slist, 17))
执行结果:
(True, 3)
C 实现无序表顺序搜索:
#include <stdio.h>
#include "list.h"
typedef int DataType;
int seqSearch(List s, DataType item)
{
int i = 0;
while (i < s.size && s.items[i] != item)
{
i++;
if (s.items[i] == item)
return i;
else
return -1;
}
}
有序列表(顺序表)搜索
Python 实现有序表顺序搜索:
def orderedSequentialSearch(alist, item):
pos = 0
found = False
stop = False
size = len(alist)
while pos < size and not found and not stop:
if alist[pos] == item:
found = True
else:
if alist[pos] > item:
stop = True
else:
pos = pos + 1
return found, pos
oslist = [17, 20, 26, 31, 44, 54, 55, 65, 77, 93]
print(orderedSequentialSearch(oslist, 54))
执行结果:
(True, 5)
C 实现有序表顺序搜索:
#include <stdio.h>
#include "list.h"
int orderSeqSearch(List s, DataType item)
{
int i = 0;
while (i < s.size && s.items[i] < item)
{
i++;
if (s.items[i] == item)
return i;
else
return -1;
}
}
二分搜索
二分搜索可以更高效的在有序表中查找目标元素。在顺序搜索时,如果第一个元素不是目标元素,最多还要比较 n-1 次。但二分搜索不是从第一个元素开始搜索列表,而是从中间的元素着手。如果这个元素就是目标元素,那就立即停止搜索;如果不是,则可以利用列表有序的特征,排除一半的元素。
Python 实现二分搜索:
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, midpoint
二分搜索的递归版本:
def binarySearch(alist, item):
if len(alist) == 0:
return False
else:
midpoint = len(alist) // 2
if alist[midpoint] == item:
return True, midpoint
else:
if item < alist[midpoint]:
return binarySearch(alist[:midpoint], item), midpoint
else:
return binarySearch(alist[midpoint+1:], item), midpoint
C 实现二分搜索:
int binarySearch(List s, DataType item)
{
int low = 0;
int high = s.size -1;
int mid;
while (low <= high)
{
mid = (low + high) / 2;
if (s.items[mid] == item)
{
return mid;
}
else if (s.items[mid] < item)
{
low = mid + 1;
}
else if (s.items[mid] > item)
{
high = mid - 1;
}
}
return -1;
}
索引顺序表
当顺序表中的数据元素个数非常大时,在顺序表上建立索引表可以很好的提高查找速度。
索引表与书籍目录的用途和构造方法上类同,把在其上建立索引表的顺序表称作主表。主表中存放着数据元素的全部信息,索引标准只存放主表中要查找数据元素的主关键字和索引信息。
要使索引表的查找效率,索引表必须有序,主表中的元素可以按关键字有序或按关键字分段有序。
下图展示了一个主表和一个按关键字 key 建立的索引表的结构图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aHQ0gj54-1616748375554)(.\res\索引表结构图.png)]
key 为被索引的若干个数据元素中关键字的最大值,link 为被索引的若干个数据元素中死一个数据元素的位置编号。
有的时候需要在主表上再建立若干个次关键字索引表,一般方法是:先在主表上建立了一个与主表项完全相同,但只包含索引关键字和该数据元素在主表中位置信息的索引表,再在这样的索引表上建立索引表(把与主表项完全相同,但只包含索引关键字和该数据在元素在主表中位置信息的索引表称作完全索引表)。所以当数据元素个数非常庞大时,可以按照建立索引表的方法对索引表在建立索引表,这样的索引表称为二级索引表,二级以上的索引结构称作多级索引结构。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3LQoCdKA-1616748375559)(.\res\带完全索引表的索引表结构图.png)]
等长索引表:索引表中的每个索引项对应主表中的数据元素个数是相等的。
不等长索引表:索引表中不同的索引项和主表中数据元素的个数的对应关系是不同的。不等长索引表结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iV6jDDSb-1616748375562)(.\res\不等长索引表结构图.png)]
索引顺序表上的查找过程,包括在索引表上的查找和在主表上的查找两部分。
假设索引表的长度为 m,主表中每个子表的长度为 s,并假设在索引表上和主表上均采用顺序表查找算法,则索引表上查找算法的平均查找长度为:
A
S
L
=
m
+
1
2
+
s
+
1
2
=
m
+
s
2
ASL = \frac{m + 1}{2} + \frac{s + 1}{2} = \frac{m + s}{2}
ASL=2m+1+2s+1=2m+s
散列(Hash)
散列表是元素集合,其中的元素以一种便于查找的方式存储。散列表中的每个位置通常被称为槽,其中可以存储一个元素。槽用一个从 0 开始的整数标记,例如 0 号槽、1 号槽、2 号槽。初始情形下,散列表中没有元素,每个人槽都是空的。
散列函数将散列表中的元素与其所属位置对应起来。
取余散列函数: h(item) = item%n,n 为散列表的大小
举个例子:
假设有一个集合 [54, 26, 93, 17, 77, 31],散列表大小为 11,利用取余函数计算散列值。
元素 散列值 54 10 26 4 93 5 17 6 77 0 31 9 计算除散列值后,将每个元素插入到相应的位置,散列表大小为 11,则存在 11 个槽,但元素散列值只有 6 个,所以只会有 6 个槽被占用。占用率被称为载荷因子:
λ = 元 素 个 数 散 列 表 大 小 λ = \frac{元素个数}{散列表大小} λ=散列表大小元素个数
在本例中:
λ = 6 11 λ = \frac{6}{11} λ=116
那么此时的散列表内容为:
0 1 2 3 4 5 6 7 8 9 10 77 None None None 26 93 17 None None 31 54 搜索目标元素时,仅需使用散列函数计算出该元素的编号,并查看对应的槽中是否有值。因为计算散列值并找到相应位置所需的时间是固定的,所以搜索操作的时间复杂度是 O(1)。
给定一个元素集合,能将每个元素映射到不同的槽,这种散列函数被称作完美散列函数。创建一个理想的散列函数,要求冲突数最少,计算方便,元素均匀分布于散列表中。我们可以使用折叠法和平方取中放来扩展取余函数。那么当两个元素的散列值相同呢?
冲突
散列函数将两个元素都放入同一个槽,这种情况叫“冲突”,也叫“碰撞”。
处理冲突
当两个元素被分到同一个槽时,必须通过一种系统方法在散列函数表中安置第二个元素。这个过程被称为处理冲突。
开放定制法:从起始散列值开始,顺序遍历列表,直到找到一个空槽来放置冲突元素,由于时逐个访问槽,因此这个方法被称作线性探测。线性探测有个缺点,就是会使散列表中的元素聚集现象,也就是说,如果一个槽发生太多冲突,线性探测会填满其附近的槽,而这会影响到后续插入的元素。解决这个问题,可以使用再散列和平方探测法。
Python 实现散列表,映射抽象数据类型:
class HashTable:
def __init__(self):
self.size = 11
self.slots = [None] * self.size
self.data = [None] * self.size
def hashfunction(self, key, size):
return key%size
def rehash(self, oldhash, size):
return (oldhash + 1)%size
def put(self, key, data):
hashvalue = self.hashfunction(key, len(self.slots))
if self.slots[hashvalue] == None:
self.slots[hashvalue] = key
self.data[hashvalue] = data
else:
if self.slots[hashvalue] == key:
self.data[hashvalue] = data
else:
nextslot = self.rehash(hashvalue, len(self.slots))
while self.slots[nextslot] != None and self.slots[nextslot] != key:
nextslot = self.rehash(nextslot, len(self.slots))
if self.slots[nextslot] == None:
self.slots[nextslot] = key
self.data[nextslot] =data
else:
self.data[nextslot] = data
def get(self, key):
startslot = self.hashfunction(key, len(self.slots))
data = None
stop = False
found = False
position = startslot
while self.slots[position] != None and not found and not stop:
if self.slots[position] == key:
found = True
data = self.data[position]
else:
position = self.rehash(position, len(self.slots))
if position == startslot:
stop = True
return data
def __getitem__(self, key):
return self.get(key)
def __setitem__(self, key, data):
self.put(key,data)
h = HashTable()
h[54] = "cat"
h[26] = "dog"
h[93] = "lion"
h[17] = "tiger"
h[77] = "bird"
h[31] = "cow"
h[44] = "goat"
h[55] = "pig"
h[20] = "chicken"
print(h.slots)
print(h.data)
h[20] = "duck"
print(h.slots)
print(h.data)
执行结果:
[77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
['bird', 'goat', 'pig', 'chicken', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']
[77, 44, 55, 20, 26, 93, 17, None, None, 31, 54]
['bird', 'goat', 'pig', 'duck', 'dog', 'lion', 'tiger', None, None, 'cow', 'cat']