Searching(搜索)
在计算机科学中,搜索算法是用于在数据结构中查找特定元素的算法。本章将详细介绍几种常见的搜索算法,包括线性搜索、二分搜索、插值搜索等,并探讨它们的适用场景和性能特点。
什么是搜索?
搜索算法的目标是在给定的数据结构中找到一个特定的元素。搜索可以分为两类:无序搜索和有序搜索。无序搜索适用于未排序的数据,而有序搜索则适用于已排序的数据。
为什么需要搜索?
搜索算法在许多应用场景中都非常有用,例如在数据库中查找记录、在文件系统中查找文件、在网页中查找特定内容等。高效的搜索算法可以显著提高系统的性能和用户体验。
搜索的类型
无序线性搜索
无序线性搜索是最基本的搜索算法,适用于未排序的数据。它通过遍历数据结构中的每个元素来查找目标元素。
示例代码
def linear_search(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i # 返回目标元素的索引
return -1 # 如果未找到,返回 -1
# 示例输入
arr = [4, 2, 7, 1, 9, 3]
target = 7
# 示例输出
index = linear_search(arr, target)
print(f"Element {target} found at index {index}") # 输出: Element 7 found at index 2
有序线性搜索
有序线性搜索适用于已排序的数据。虽然它仍然需要遍历每个元素,但由于数据已排序,可以在找到目标元素或一个比目标元素大的元素时提前终止搜索。
示例代码
def ordered_linear_search(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i # 返回目标元素的索引
elif arr[i] > target:
return -1 # 如果当前元素大于目标元素,提前终止搜索
return -1 # 如果未找到,返回 -1
# 示例输入
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
target = 5
# 示例输出
index = ordered_linear_search(arr, target)
print(f"Element {target} found at index {index}") # 输出: Element 5 found at index 4
二分搜索
二分搜索是一种高效的搜索算法,适用于已排序的数据。它通过不断将搜索区间一分为二来查找目标元素,时间复杂度为 O(logn)
示例代码
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid # 返回目标元素的索引
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1 # 如果未找到,返回 -1
# 示例输入
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
target = 5
# 示例输出
index = binary_search(arr, target)
print(f"Element {target} found at index {index}") # 输出: Element 5 found at index 4
插值搜索
插值搜索是一种改进的二分搜索,适用于数据分布较为均匀的情况。它通过估计目标元素可能的位置来缩小搜索区间,时间复杂度在最佳情况下可以达到 O(loglogn)
示例代码
def interpolation_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right and target >= arr[left] and target <= arr[right]:
if left == right:
if arr[left] == target:
return left
return -1
pos = left + ((target - arr[left]) * (right - left)) // (arr[right] - arr[left])
if arr[pos] == target:
return pos
elif arr[pos] < target:
left = pos + 1
else:
right = pos - 1
return -1 # 如果未找到,返回 -1
# 示例输入
arr = [10, 20, 30, 40, 50, 60, 70, 80, 90]
target = 50
# 示例输出
index = interpolation_search(arr, target)
print(f"Element {target} found at index {index}") # 输出: Element 50 found at index 4
比较基本的搜索算法
时间复杂度
- 无序线性搜索:O(n)
- 有序线性搜索:O(n)
- 二分搜索:O(logn)
- 插值搜索:O(loglogn)(最佳情况)
适用场景
-
无序线性搜索:适用于未排序的数据。
-
有序线性搜索:适用于已排序的数据,但数据量较小。
-
二分搜索:适用于已排序的数据,尤其是数据量较大时。
-
插值搜索:适用于已排序且数据分布较为均匀的情况。
符号表和哈希
符号表(Symbol Table)是一种数据结构,用于存储键值对。哈希表(Hash Table)是一种实现符号表的高效方法,通过哈希函数将键映射到表中的位置,从而实现快速的插入和查找操作。
哈希表的基本操作
-
插入:将键值对插入哈希表。
-
查找:根据键查找值。
-
删除:根据键删除键值对。
示例代码
class HashTable:
def __init__(self, size=10):
self.size = size
self.table = [[] for _ in range(size)]
def _hash(self, key):
return hash(key) % self.size
def insert(self, key, value):
index = self._hash(key)
for item in self.table[index]:
if item[0] == key:
item[1] = value
return
self.table[index].append([key, value])
def search(self, key):
index = self._hash(key)
for item in self.table[index]:
if item[0] == key:
return item[1]
return None
def delete(self, key):
index = self._hash(key)
for i, item in enumerate(self.table[index]):
if item[0] == key:
del self.table[index][i]
return
# 示例输入
hash_table = HashTable()
hash_table.insert("apple", 5)
hash_table.insert("banana", 7)
hash_table.insert("cherry", 3)
# 示例输出
print(hash_table.search("banana")) # 输出: 7
hash_table.delete("banana")
print(hash_table.search("banana")) # 输出: None
哈希函数
哈希函数用于将键映射到哈希表中的位置。一个好的哈希函数应该能够均匀地分布键,以减少冲突。
示例代码
def simple_hash(key, table_size):
return sum(ord(c) for c in key) % table_size
# 示例输入
key = "apple"
table_size = 10
# 示例输出
print(simple_hash(key, table_size)) # 输出: 5
冲突解决技术
-
链地址法(Separate Chaining):每个哈希表位置存储一个链表,用于处理冲突。
-
开放定址法(Open Addressing):在哈希表中寻找下一个空闲位置来存储冲突的键值对。
示例代码(链地址法)
class HashTable:
def __init__(self, size=10):
self.size = size
self.table = [[] for _ in range(size)]
def _hash(self, key):
return hash(key) % self.size
def insert(self, key, value):
index = self._hash(key)
for item in self.table[index]:
if item[0] == key:
item[1] = value
return
self.table[index].append([key, value])
def search(self, key):
index = self._hash(key)
for item in self.table[index]:
if item[0] == key:
return item[1]
return None
def delete(self, key):
index = self._hash(key)
for i, item in enumerate(self.table[index]):
if item[0] == key:
del self.table[index][i]
return
# 示例输入
hash_table = HashTable()
hash_table.insert("apple", 5)
hash_table.insert("banana", 7)
hash_table.insert("cherry", 3)
# 示例输出
print(hash_table.search("banana")) # 输出: 7
hash_table.delete("banana")
print(hash_table.search("banana")) # 输出: None
示例代码(开放定址法)
class HashTable:
def __init__(self, size=10):
self.size = size
self.table = [None] * size
def _hash(self, key):
return hash(key) % self.size
def _rehash(self, old_hash):
return (old_hash + 1) % self.size
def insert(self, key, value):
index = self._hash(key)
while self.table[index] is not None and self.table[index][0] != key:
index = self._rehash(index)
self.table[index] = (key, value)
def search(self, key):
index = self._hash(key)
start_index = index
while self.table[index] is not None:
if self.table[index][0] == key:
return self.table[index][1]
index = self._rehash(index)
if index == start_index:
break
return None
def delete(self, key):
index = self._hash(key)
start_index = index
while self.table[index] is not None:
if self.table[index][0] == key:
self.table[index] = None
return
index = self._rehash(index)
if index == start_index:
break
# 示例输入
hash_table = HashTable()
hash_table.insert("apple", 5)
hash_table.insert("banana", 7)
hash_table.insert("cherry", 3)
# 示例输出
print(hash_table.search("banana")) # 输出: 7
hash_table.delete("banana")
print(hash_table.search("banana")) # 输出: None
哈希表的性能
哈希表的性能取决于哈希函数的质量和冲突解决策略。在理想情况下,哈希表的插入、查找和删除操作的时间复杂度可以达到 O(1)
字符串搜索算法
字符串搜索算法用于在文本中查找特定的模式。常见的字符串搜索算法包括暴力搜索、Rabin-Karp 算法、有限自动机搜索、KMP 算法和 Boyer-Moore 算法。
暴力搜索
暴力搜索是最基本的字符串搜索算法,通过逐个比较文本和模式中的字符来查找模式。
示例代码
def brute_force_search(text, pattern):
n = len(text)
m = len(pattern)
for i in range(n - m + 1):
j = 0
while j < m and text[i + j] == pattern[j]:
j += 1
if j == m:
return i # 返回模式的起始索引
return -1 # 如果未找到,返回 -1
# 示例输入
text = "abcabcabc"
pattern = "abc"
# 示例输出
index = brute_force_search(text, pattern)
print(f"Pattern found at index {index}") # 输出: Pattern found at index 0
Rabin-Karp 算法
Rabin-Karp 算法通过使用哈希函数来加速模式匹配过程。它在文本中滑动窗口,计算每个窗口的哈希值,并与模式的哈希值进行比较。
示例代码
def rabin_karp_search(text, pattern, prime, d):
n = len(text)
m = len(pattern)
p = 0 # 模式的哈希值
t = 0 # 文本的哈希值
h = 1 # h = d^(m-1) % prime
for i in range(m - 1):
h = (h * d) % prime
for i in range(m):
p = (d * p + ord(pattern[i])) % prime
t = (d * t + ord(text[i])) % prime
for i in range(n - m + 1):
if p == t:
for j in range(m):
if text[i + j] != pattern[j]:
break
else:
return i # 返回模式的起始索引
if i < n - m:
t = (d * (t - ord(text[i]) * h) + ord(text[i + m])) % prime
if t < 0:
t += prime
return -1 # 如果未找到,返回 -1
# 示例输入
text = "abcabcabc"
pattern = "abc"
prime = 101
d = 256
# 示例输出
index = rabin_karp_search(text, pattern, prime, d)
print(f"Pattern found at index {index}") # 输出: Pattern found at index 0
有限自动机搜索
有限自动机搜索通过构建一个确定性有限自动机(DFA)来加速模式匹配过程。DFA 根据当前状态和输入字符决定下一个状态。
示例代码
def compute_transition_function(pattern, alphabet):
m = len(pattern)
tf = [[0] * (len(alphabet) + 1) for _ in range(m + 1)]
for state in range(m + 1):
for x in alphabet:
k = min(m + 1, state + 2)
k -= 1
while k > 0 and not (pattern[:k] == pattern[state - k + 1:state] + x):
k -= 1
tf[state][ord(x)] = k
return tf
def finite_automaton_search(text, pattern):
alphabet = set(text)
tf = compute_transition_function(pattern, alphabet)
n = len(text)
m = len(pattern)
state = 0
for i in range(n):
state = tf[state][ord(text[i])]
if state == m:
return i - m + 1 # 返回模式的起始索引
return -1 # 如果未找到,返回 -1
# 示例输入
text = "abcabcabc"
pattern = "abc"
# 示例输出
index = finite_automaton_search(text, pattern)
print(f"Pattern found at index {index}") # 输出: Pattern found at index 0
KMP 算法
KMP 算法通过预处理模式来加速模式匹配过程。它使用部分匹配表(Prefix Function)来避免不必要的比较。
示例代码
def compute_lps_array(pattern):
m = len(pattern)
lps = [0] * m
length = 0
i = 1
while i < m:
if pattern[i] == pattern[length]:
length += 1
lps[i] = length
i += 1
else:
if length != 0:
length = lps[length - 1]
else:
lps[i] = 0
i += 1
return lps
def kmp_search(text, pattern):
n = len(text)
m = len(pattern)
lps = compute_lps_array(pattern)
i = 0 # 文本的索引
j = 0 # 模式的索引
while i < n:
if pattern[j] == text[i]:
i += 1
j += 1
if j == m:
return i - j # 返回模式的起始索引
elif i < n and pattern[j] != text[i]:
if j != 0:
j = lps[j - 1]
else:
i += 1
return -1 # 如果未找到,返回 -1
# 示例输入
text = "abcabcabc"
pattern = "abc"
# 示例输出
index = kmp_search(text, pattern)
print(f"Pattern found at index {index}") # 输出: Pattern found at index 0
Boyer-Moore 算法
Boyer-Moore 算法通过从右向左比较字符来加速模式匹配过程。它使用坏字符规则和好后缀规则来跳过不必要的比较。
示例代码
def bad_character_heuristic(pattern, m):
bad_char = [-1] * 256
for i in range(m):
bad_char[ord(pattern[i])] = i
return bad_char
def boyer_moore_search(text, pattern):
n = len(text)
m = len(pattern)
bad_char = bad_character_heuristic(pattern, m)
s = 0 # s 是模式在文本中的位置
while s <= n - m:
j = m - 1
while j >= 0 and pattern[j] == text[s + j]:
j -= 1
if j < 0:
return s # 返回模式的起始索引
s += max(1, j - bad_char[ord(text[s + j])])
return -1 # 如果未找到,返回 -1
# 示例输入
text = "abcabcabc"
pattern = "abc"
# 示例输出
index = boyer_moore_search(text, pattern)
print(f"Pattern found at index {index}") # 输出: Pattern found at index 0
搜索算法:问题与解答
问题 1:在未排序的数组中查找特定元素
解答
使用无序线性搜索算法,遍历数组中的每个元素,直到找到目标元素。
示例代码
def linear_search(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i # 返回目标元素的索引
return -1 # 如果未找到,返回 -1
# 示例输入
arr = [4, 2, 7, 1, 9, 3]
target = 7
# 示例输出
index = linear_search(arr, target)
print(f"Element {target} found at index {index}") # 输出: Element 7 found at index 2
问题 2:在已排序的数组中查找特定元素
解答
使用二分搜索算法,通过不断将搜索区间一分为二来查找目标元素,时间复杂度为 O(logn)。
示例代码
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid # 返回目标元素的索引
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1 # 如果未找到,返回 -1
# 示例输入
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
target = 5
# 示例输出
index = binary_search(arr, target)
print(f"Element {target} found at index {index}") # 输出: Element 5 found at index 4
问题 3:在文本中查找特定模式
解答
使用 KMP 算法,通过预处理模式来加速模式匹配过程。KMP 算法使用部分匹配表(Prefix Function)来避免不必要的比较。
示例代码
def compute_lps_array(pattern):
m = len(pattern)
lps = [0] * m
length = 0
i = 1
while i < m:
if pattern[i] == pattern[length]:
length += 1
lps[i] = length
i += 1
else:
if length != 0:
length = lps[length - 1]
else:
lps[i] = 0
i += 1
return lps
def kmp_search(text, pattern):
n = len(text)
m = len(pattern)
lps = compute_lps_array(pattern)
i = 0 # 文本的索引
j = 0 # 模式的索引
while i < n:
if pattern[j] == text[i]:
i += 1
j += 1
if j == m:
return i - j # 返回模式的起始索引
elif i < n and pattern[j] != text[i]:
if j != 0:
j = lps[j - 1]
else:
i += 1
return -1 # 如果未找到,返回 -1
# 示例输入
text = "abcabcabc"
pattern = "abc"
# 示例输出
index = kmp_search(text, pattern)
print(f"Pattern found at index {index}") # 输出: Pattern found at index 0
问题 4:在哈希表中查找特定键
解答
使用哈希表的搜索方法,通过哈希函数将键映射到表中的位置,然后查找该位置的链表或开放定址法中的下一个位置。
示例代码
class HashTable:
def __init__(self, size=10):
self.size = size
self.table = [[] for _ in range(size)]
def _hash(self, key):
return hash(key) % self.size
def insert(self, key, value):
index = self._hash(key)
for item in self.table[index]:
if item[0] == key:
item[1] = value
return
self.table[index].append([key, value])
def search(self, key):
index = self._hash(key)
for item in self.table[index]:
if item[0] == key:
return item[1]
return None
# 示例输入
hash_table = HashTable()
hash_table.insert("apple", 5)
hash_table.insert("banana", 7)
hash_table.insert("cherry", 3)
# 示例输出
print(hash_table.search("banana")) # 输出: 7
问题 5:在字符串中查找多个模式
解答
使用 Aho-Corasick 算法,通过构建一个模式匹配机来同时查找多个模式。Aho-Corasick 算法结合了 KMP 算法和 Trie 树的优点,能够高效地处理多个模式的匹配问题。
示例代码
class TrieNode:
def __init__(self):
self.children = {}
self.is_end_of_word = False
self.fail = None # 失败指针
class AhoCorasick:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end_of_word = True
def build_fail_pointers(self):
queue = []
node = self.root
for child in node.children.values():
child.fail = self.root
queue.append(child)
while queue:
rnode = queue.pop(0)
for (key, child) in rnode.children.items():
queue.append(child)
fail_node = rnode.fail
while fail_node and key not in fail_node.children:
fail_node = fail_node.fail
child.fail = fail_node.children[key] if fail_node else self.root
child.is_end_of_word = child.is_end_of_word or child.fail.is_end_of_word
def search(self, text):
node = self.root
results = []
for i in range(len(text)):
while node and text[i] not in node.children:
node = node.fail
if not node:
node = self.root
continue
node = node.children[text[i]]
temp = node
while temp:
if temp.is_end_of_word:
results.append((i - len(self.get_word(temp)) + 1, self.get_word(temp)))
temp = temp.fail
return results
def get_word(self, node):
result = []
while node and node != self.root:
result.append(node.char)
node = node.parent
return ''.join(reversed(result))
# 示例输入
patterns = ["he", "she", "hers", "his"]
text = "ahishers"
# 示例输出
aco = AhoCorasick()
for pattern in patterns:
aco.insert(pattern)
aco.build_fail_pointers()
results = aco.search(text)
for start, word in results:
print(f"Pattern '{word}' found at index {start}")
总结
本章详细介绍了多种搜索算法,包括线性搜索、二分搜索、插值搜索、符号表和哈希表、字符串搜索算法等。每种算法都有其适用场景和性能特点。通过理解这些算法的原理和实现,可以更好地选择合适的搜索算法来解决实际问题。