【轻松掌握数据结构与算法】搜索算法

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(log⁡n)

示例代码
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(log⁡log⁡n)

示例代码
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(log⁡n)
  • 插值搜索:O(log⁡log⁡n)(最佳情况)

适用场景

  • 无序线性搜索:适用于未排序的数据。

  • 有序线性搜索:适用于已排序的数据,但数据量较小。

  • 二分搜索:适用于已排序的数据,尤其是数据量较大时。

  • 插值搜索:适用于已排序且数据分布较为均匀的情况。

符号表和哈希

符号表(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(log⁡n)。

示例代码
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}")

总结

本章详细介绍了多种搜索算法,包括线性搜索、二分搜索、插值搜索、符号表和哈希表、字符串搜索算法等。每种算法都有其适用场景和性能特点。通过理解这些算法的原理和实现,可以更好地选择合适的搜索算法来解决实际问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值