哈希算法题目:Rabin-Karp 算法在二维矩阵中查找模式
字数 2317 2025-11-09 15:43:26

哈希算法题目:Rabin-Karp 算法在二维矩阵中查找模式

题目描述
给定一个大小为 \(m \times n\) 的二维矩阵(文本矩阵)和一个大小为 \(p \times q\) 的二维模式矩阵,要求使用 Rabin-Karp 算法高效地查找模式矩阵在文本矩阵中出现的所有位置。文本矩阵和模式矩阵中的元素可以是字符或整数。


解题过程循序渐进讲解

步骤1:理解核心思想——滚动哈希的扩展

  • Rabin-Karp 算法的核心是通过哈希值快速比较数据段。在一维字符串中,我们通过滑动窗口计算滚动哈希;在二维问题中,需要将滚动哈希扩展到二维窗口。
  • 基本思路
    1. 计算模式矩阵的哈希值 \(H_{\text{pattern}}\)
    2. 计算文本矩阵中每个可能的 \(p \times q\) 子矩阵的哈希值。
    3. 若子矩阵的哈希值与 \(H_{\text{pattern}}\) 匹配,再逐元素验证(避免哈希冲突)。
    4. 利用滚动哈希优化:在文本矩阵中滑动窗口时,仅需增量更新哈希值,而非重新计算整个子矩阵的哈希。

步骤2:设计二维哈希函数

  • 假设矩阵元素为整数(字符可转换为 ASCII 值)。
  • 选择两个基数 \(B_1\)\(B_2\)(通常为质数),分别用于行方向和列方向的哈希计算。
  • 对于一个 \(p \times q\) 的子矩阵,其哈希值计算公式为:

\[ H = \sum_{i=0}^{p-1} \sum_{j=0}^{q-1} \text{matrix}[i][j] \cdot B_1^{p-1-i} \cdot B_2^{q-1-j} \]

此公式将二维矩阵视为一个 \(p \times q\) 的“数字”,其中每行类似一个高基数的数字位。


步骤3:预处理文本矩阵的哈希值

  • 为了支持滚动更新,需预先计算文本矩阵的“前缀哈希数组”。
  • 定义二维前缀哈希数组 \(\text{prefix}[i][j]\) 表示从 \((0,0)\)\((i-1, j-1)\) 子矩阵的哈希值(需调整索引边界)。
  • 实际实现中,可直接计算每个可能的 \(p \times q\) 窗口的哈希值,但通过以下步骤优化:

步骤4:列方向的滚动哈希(固定行方向)

  1. 第一步:对文本矩阵的每一行,计算长度为 \(q\) 的滚动哈希序列
    • 使用一维 Rabin-Karp 方法,以基数 \(B_2\) 计算每行中所有长度为 \(q\) 的窗口的哈希值,结果存于二维数组 \(\text{rowHash}[i][j]\)(表示第 \(i\) 行从列 \(j\) 开始的长度为 \(q\) 的窗口哈希值)。
    • 例如,对第 \(i\) 行:

\[ \text{rowHash}[i][j] = \sum_{k=0}^{q-1} \text{matrix}[i][j+k] \cdot B_2^{q-1-k} \]

 滑动时通过减去最高位、乘以 $ B_2 $、加上新元素来更新哈希。
  1. 第二步:在列方向滚动计算 \(p \times q\) 子矩阵哈希
    • \(\text{rowHash}[i][j]\) 视为一维数组中的元素,再以基数 \(B_1\) 在列方向计算长度为 \(p\) 的窗口的哈希值。
    • 对于固定的列起始位置 \(j\),计算从行 \(i\) 开始的 \(p\) 行对应的 \(\text{rowHash}[i][j]\) 的哈希值:

\[ H_{\text{sub}} = \sum_{k=0}^{p-1} \text{rowHash}[i+k][j] \cdot B_1^{p-1-k} \]

  • 滑动窗口向下移动一行时:

\[ H_{\text{new}} = (H_{\text{old}} - \text{rowHash}[i][j] \cdot B_1^{p-1}) \cdot B_1 + \text{rowHash}[i+p][j] \]


步骤5:算法流程

  1. 计算模式矩阵的哈希值 \(H_{\text{pattern}}\)(使用相同的 \(B_1, B_2\))。
  2. 对文本矩阵的每一行,计算所有长度为 \(q\) 的窗口的滚动哈希值 \(\text{rowHash}[i][j]\)
  3. 对于每个列起始位置 \(j\)\(0 \leq j \leq n-q\)):
    • 初始化一个列方向的滚动哈希,计算前 \(p\) 行的 \(\text{rowHash}[i][j]\) 的哈希值。
    • 从第 \(p-1\) 行开始向下滑动窗口,每次比较当前哈希与 \(H_{\text{pattern}}\)。若匹配,则逐元素验证子矩阵。
    • 更新滚动哈希并移动到下一行。

步骤6:处理哈希冲突与边界条件

  • 由于哈希值可能冲突,当哈希匹配时必须逐元素验证子矩阵。
  • 注意模运算:实际计算中需对一个大质数(如 \(10^9+7\))取模,避免溢出。
  • 时间复杂度:预处理 \(O(mn)\),滑动窗口比较 \(O((m-p+1)(n-q+1))\),最坏情况下(哈希冲突多)为 \(O(mnpq)\),但平均效率高。

总结
通过将一维滚动哈希扩展到行列两个方向,Rabin-Karp 算法能高效解决二维模式匹配问题。关键点在于分离行列方向的哈希计算,并利用滚动更新避免重复计算。

哈希算法题目:Rabin-Karp 算法在二维矩阵中查找模式 题目描述 给定一个大小为 \( m \times n \) 的二维矩阵(文本矩阵)和一个大小为 \( p \times q \) 的二维模式矩阵,要求使用 Rabin-Karp 算法高效地查找模式矩阵在文本矩阵中出现的所有位置。文本矩阵和模式矩阵中的元素可以是字符或整数。 解题过程循序渐进讲解 步骤1:理解核心思想——滚动哈希的扩展 Rabin-Karp 算法的核心是通过哈希值快速比较数据段。在一维字符串中,我们通过滑动窗口计算滚动哈希;在二维问题中,需要将滚动哈希扩展到二维窗口。 基本思路 : 计算模式矩阵的哈希值 \( H_ {\text{pattern}} \)。 计算文本矩阵中每个可能的 \( p \times q \) 子矩阵的哈希值。 若子矩阵的哈希值与 \( H_ {\text{pattern}} \) 匹配,再逐元素验证(避免哈希冲突)。 利用滚动哈希优化:在文本矩阵中滑动窗口时,仅需增量更新哈希值,而非重新计算整个子矩阵的哈希。 步骤2:设计二维哈希函数 假设矩阵元素为整数(字符可转换为 ASCII 值)。 选择两个基数 \( B_ 1 \) 和 \( B_ 2 \)(通常为质数),分别用于行方向和列方向的哈希计算。 对于一个 \( p \times q \) 的子矩阵,其哈希值计算公式为: \[ H = \sum_ {i=0}^{p-1} \sum_ {j=0}^{q-1} \text{matrix}[ i][ j] \cdot B_ 1^{p-1-i} \cdot B_ 2^{q-1-j} \] 此公式将二维矩阵视为一个 \( p \times q \) 的“数字”,其中每行类似一个高基数的数字位。 步骤3:预处理文本矩阵的哈希值 为了支持滚动更新,需预先计算文本矩阵的“前缀哈希数组”。 定义二维前缀哈希数组 \( \text{prefix}[ i][ j ] \) 表示从 \( (0,0) \) 到 \( (i-1, j-1) \) 子矩阵的哈希值(需调整索引边界)。 实际实现中,可直接计算每个可能的 \( p \times q \) 窗口的哈希值,但通过以下步骤优化: 步骤4:列方向的滚动哈希(固定行方向) 第一步:对文本矩阵的每一行,计算长度为 \( q \) 的滚动哈希序列 使用一维 Rabin-Karp 方法,以基数 \( B_ 2 \) 计算每行中所有长度为 \( q \) 的窗口的哈希值,结果存于二维数组 \( \text{rowHash}[ i][ j ] \)(表示第 \( i \) 行从列 \( j \) 开始的长度为 \( q \) 的窗口哈希值)。 例如,对第 \( i \) 行: \[ \text{rowHash}[ i][ j] = \sum_ {k=0}^{q-1} \text{matrix}[ i][ j+k] \cdot B_ 2^{q-1-k} \] 滑动时通过减去最高位、乘以 \( B_ 2 \)、加上新元素来更新哈希。 第二步:在列方向滚动计算 \( p \times q \) 子矩阵哈希 将 \( \text{rowHash}[ i][ j] \) 视为一维数组中的元素,再以基数 \( B_ 1 \) 在列方向计算长度为 \( p \) 的窗口的哈希值。 对于固定的列起始位置 \( j \),计算从行 \( i \) 开始的 \( p \) 行对应的 \( \text{rowHash}[ i][ j ] \) 的哈希值: \[ H_ {\text{sub}} = \sum_ {k=0}^{p-1} \text{rowHash}[ i+k][ j] \cdot B_ 1^{p-1-k} \] 滑动窗口向下移动一行时: \[ H_ {\text{new}} = (H_ {\text{old}} - \text{rowHash}[ i][ j] \cdot B_ 1^{p-1}) \cdot B_ 1 + \text{rowHash}[ i+p][ j ] \] 步骤5:算法流程 计算模式矩阵的哈希值 \( H_ {\text{pattern}} \)(使用相同的 \( B_ 1, B_ 2 \))。 对文本矩阵的每一行,计算所有长度为 \( q \) 的窗口的滚动哈希值 \( \text{rowHash}[ i][ j ] \)。 对于每个列起始位置 \( j \)(\( 0 \leq j \leq n-q \)): 初始化一个列方向的滚动哈希,计算前 \( p \) 行的 \( \text{rowHash}[ i][ j ] \) 的哈希值。 从第 \( p-1 \) 行开始向下滑动窗口,每次比较当前哈希与 \( H_ {\text{pattern}} \)。若匹配,则逐元素验证子矩阵。 更新滚动哈希并移动到下一行。 步骤6:处理哈希冲突与边界条件 由于哈希值可能冲突,当哈希匹配时必须逐元素验证子矩阵。 注意模运算:实际计算中需对一个大质数(如 \( 10^9+7 \))取模,避免溢出。 时间复杂度:预处理 \( O(mn) \),滑动窗口比较 \( O((m-p+1)(n-q+1)) \),最坏情况下(哈希冲突多)为 \( O(mnpq) \),但平均效率高。 总结 通过将一维滚动哈希扩展到行列两个方向,Rabin-Karp 算法能高效解决二维模式匹配问题。关键点在于分离行列方向的哈希计算,并利用滚动更新避免重复计算。