字符串哈希算法详解:原理、实现与应用

news2025/4/18 2:06:17

字符串哈希是一种高效处理字符串匹配和比较的技术,它通过将字符串映射为一个唯一的数值(哈希值),从而在O(1)时间内完成子串的比较。本文将结合代码实现,详细讲解前缀哈希法的工作原理,并通过流程图逐步解析其执行过程。


. 字符串哈希的核心思想

字符串哈希的核心目标是:

  • 将任意长度的字符串转换为固定大小的哈希值

  • 支持快速比较两个子串是否相同(时间复杂度O(1))。

  • 适用于大规模文本处理(如DNA序列比对、 plagiarism检测等)。

1.1 哈希函数设计

常用的字符串哈希方法是多项式滚动哈希(Polynomial Rolling Hash),其公式为:

H(S)=(S[0]×Pn−1+S[1]×Pn−2+⋯+S[n−1]×P0)mod  MH(S)=(S[0]×Pn−1+S[1]×Pn−2+⋯+S[n−1]×P0)modM

其中:

  • PP 是一个质数(通常取131或13331)。

  • MM 是一个大数(如 264264,代码中用 unsigned long long 自然溢出替代取模)。


2. 代码实现解析

以下是基于前缀哈希的字符串匹配代码(支持快速比较任意两个子串):

2.1 变量定义

#include <stdio.h>
#include <string.h>

#define N 100010
#define P 131  // 经验值,减少冲突

typedef unsigned long long Ull;

Ull h[N];  // h[i]存储前i个字符的哈希值
Ull p[N];  // p[i]存储P的i次幂

2.2 初始化哈希数组

int main() {
    int n, m;
    char str[N];
    scanf("%d%d", &n, &m);
    scanf("%s", str + 1);  // 从str[1]开始存储

    p[0] = 1;
    for (int i = 1; i <= n; i++) {
        p[i] = p[i - 1] * P;           // 计算P^i
        h[i] = h[i - 1] * P + str[i];  // 计算前i个字符的哈希值
    }
    // ...后续处理查询
}
初始化过程详解
  1. p[i] = P^i:预计算幂次,用于后续子串哈希计算。

  2. h[i] = h[i-1] * P + str[i]:递推计算前缀哈希值。

示例(假设 str = "ABC"P=131):

  • h[0] = 0

  • h[1] = h[0]*131 + 'A' = 65

  • h[2] = h[1]*131 + 'B' = 65*131 + 66 = 8581

  • h[3] = h[2]*131 + 'C' = 8581*131 + 67 = 1123988


2.3 子串哈希计算

Ull get(int l, int r) {
    return h[r] - h[l - 1] * p[r - l + 1];
}

公式解析

H(S[l..r])=H(r)−H(l−1)×Pr−l+1H(S[l..r])=H(r)−H(l−1)×Pr−l+1

  • h[r]:前 r 个字符的哈希值。

  • h[l-1] * p[r-l+1]:前 l-1 个字符的哈希值左移到对齐位置。

示例(计算 "BC" 在 "ABC" 中的哈希值):

  • get(2, 3) = h[3] - h[1]*p[2] = 1123988 - 65*17161 = 1123988 - 1115465 = 8523


2.4 查询处理

while (m--) {
    int l1, r1, l2, r2;
    scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
    if (get(l1, r1) == get(l2, r2))
        printf("Yes\n");
    else
        printf("No\n");
}

:比较两个子串 str[l1..r1] 和 str[l2..r2] 是否相同。


3. 流程图详解

以下是代码执行的流程图:

graph TD
    A[开始] --> B[输入n,m和字符串str]
    B --> C[初始化p[0]=1, h[0]=0]
    C --> D[循环i=1到n]
    D --> E[计算p[i] = p[i-1] * P]
    E --> F[计算h[i] = h[i-1] * P + str[i]]
    F --> D
    D --> G[输入查询次数m]
    G --> H[循环m次]
    H --> I[输入l1,r1,l2,r2]
    I --> J[计算Hash1=get(l1,r1)]
    I --> K[计算Hash2=get(l2,r2)]
    J --> L{Hash1 == Hash2?}
    K --> L
    L --> |Yes| M[输出"Yes"]
    L --> |No| N[输出"No"]
    M --> H
    N --> H
    H --> O[结束]

4. 关键问题解答

4.1 为什么选择P=131?

  • 131是一个经验值,满足:

    • 足够大以减少冲突。

    • 是质数,保证哈希分布均匀。

  • 其他常见选择:13331、99991等。

4.2 如何处理哈希冲突?

  • 理论上,unsigned long long 自然溢出相当于模 264264,冲突概率极低。

  • 如果需绝对准确,可双哈希(用两个不同的P计算)。

4.3 时间复杂度分析

操作时间复杂度
初始化哈希数组O(n)
单次子串比较O(1)
m次查询O(m)

5. 应用场景

  1. 快速子串匹配(如Rabin-Karp算法)。

  2. 最长回文子串(结合二分搜索)。

  3. 文本去重(比较哈希值而非原始字符串)。


6. 完整代码

#include <stdio.h>
#include <string.h>

#define N 100010
#define P 131

typedef unsigned long long Ull;

Ull h[N], p[N];

Ull get(int l, int r) {
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main() {
    int n, m;
    char str[N];
    scanf("%d%d", &n, &m);
    scanf("%s", str + 1);

    p[0] = 1;
    for (int i = 1; i <= n; i++) {
        p[i] = p[i - 1] * P;
        h[i] = h[i - 1] * P + str[i];
    }

    while (m--) {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        if (get(l1, r1) == get(l2, r2))
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}

  • 字符串哈希通过前缀哈希+幂次预处理实现O(1)子串比较。

  • P的选择自然溢出是关键优化点。

  • 适用于需要频繁比较子串的场景,比直接暴力匹配高效得多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2331188.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue2(webpack)集成electron和 electron 打包

前言 之前发过一篇vue集成electron的文章&#xff0c;但是用vue3vite实现的&#xff0c;在vue2webpack工程可能不适用&#xff0c;所以这篇文章就主要介绍vue2webpack集成electron方法 创建项目 vue create vue-electron-demo目录架构 vue-electron-demo/ ├── src/ …

C++内存管理优化实战:提升应用性能与效率

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开发技术&#xff0c;能熟练应用常用数据库SQL server,Oracle…

redis数据迁移之通过redis-dump镜像

这里写目录标题 一、redis-dump 镜像打包1.1 安装windows docker1.2 idea项目创建1.3 idea镜像打包 二、redis数据迁移2.1 数据导出2.2 数据导入 一、redis-dump 镜像打包 没有找到可用的redis-dump镜像&#xff0c;需要自己打包一下&#xff0c;这里我是在idea直接打包的 1.…

redis导入成功,缺不显示数据

SpringBootTest class SecurityApplicationTests {AutowiredStringRedisTemplate template; //添加这句代码&#xff0c;自动装载&#xff0c;即可解决文章三处代码报错Testvoid contextLoads() {String compact Jwts.builder().signWith(Jwts.SIG.HS512.key().build()).subj…

从表格到序列:Swift 如何优雅地解 LeetCode 251 展开二维向量

文章目录 摘要描述题解答案题解代码分析示例测试及结果时间复杂度空间复杂度总结 摘要 在这篇文章中&#xff0c;我们将深入探讨 LeetCode 第 251 题——“展开二维向量”的问题。通过 Swift 语言&#xff0c;我们不仅会提供可运行的示例代码&#xff0c;还会结合实际场景进行…

小型园区网实验

划分VLAN SW3 [sw3]vlan batch 2 3 20 30 [sw3]interface GigabitEthernet 0/0/1 [sw3-GigabitEthernet0/0/1]port link-type access [sw3-GigabitEthernet0/0/1]port default vlan 2 [sw3-GigabitEthernet0/0/1]int g0/0/2 [sw3-GigabitEthernet0/0/2]port link-type acces…

c# 数据结构 链表篇 有关单链表的一切

本人能力有限,本文仅作学习交流与参考,如有不足还请斧正 目录 0.单链表好处 0.5.单链表分类 1.无虚拟头节点情况 图示: 代码: 头插/尾插 删除 搜索 遍历全部 测试代码: 全部代码 2.有尾指针情况 尾插 全部代码 3.有虚拟头节点情况 全部代码 4.循环单链表 几个…

VS Code连接服务器编写Python文件

1、下载 Visual Studio Code 2、打开扩展&#xff08;ctrl shift x ) 3、搜索 Remote - SSH&#xff0c;安装 4、F1 或者 点金左下角 5、选择&#xff1a;Remote-SSH: Connect to Host……&#xff0c;回车 6、第一次用的时候&#xff0c;VS Code 会提示添加 SSH 主机。输…

Gitea的安装和配置以及应用

Gitea的安装和配置以及应用 一、安装 1、创建数据库和数据库账户&#xff08;pg&#xff09; su – postgres -c "psql" CREATE ROLE gitea WITH LOGIN PASSWORD gitea; CREATE DATABASE giteadb WITH OWNER gitea TEMPLATE template0 ENCODING UTF8 LC_COLLATE …

$_GET变量

$_GET 是一个超级全局变量&#xff0c;在 PHP 中用于收集通过 URL 查询字符串传递的参数。它是一个关联数组&#xff0c;包含了所有通过 HTTP GET 方法发送到当前脚本的变量。 预定义的 $_GET 变量用于收集来自 method"get" 的表单中的值。 从带有 GET 方法的表单发…

TBE(TVM的扩展)

算子 张量 一个张量只有一种数据类型 在内存中只能线性存储&#xff0c;最终形成一个长的一维数组 晟腾AI的数据格式 AIPP是对我们常见的数据格式转化成AI core支持的数据格式 广播机制 TVM TBE的第一种开发方式&#xff1a;DSL TBE的第二种开发方式&#xff1a;TVM TBE的第…

【Function Calling与Tool Calling】深度解析大模型智能中枢的架构革命

目录 一、范式转移&#xff1a;从对话引擎到智能中枢 二、核心技术解析 2.1 Function Calling技术栈 2.2 Tool Calling实现模式 三、企业级应用架构设计 3.1 智能工单系统案例 3.2 性能优化策略 四、安全与治理框架 4.1 权限控制矩阵 4.2 审计追踪设计 五、开发者实…

知识表示方法之六:过程表示法(Procedural Representation)

在人工智能的发展史中&#xff0c;关于知识的表示方法曾存在两种不同的观点。一种观点认为知识主要是陈述性的&#xff0c;其表示方法应着重将其静态特性&#xff0c;即事物的属性以及事物间的关系表示出来&#xff0c;称以这种观点表示知识的方法为陈述式或说明式表示法&#…

sql-labs靶场 less-2

文章目录 sqli-labs靶场less 2 联合注入 sqli-labs靶场 每道题都从以下模板讲解&#xff0c;并且每个步骤都有图片&#xff0c;清晰明了&#xff0c;便于复盘。 sql注入的基本步骤 注入点注入类型 字符型&#xff1a;判断闭合方式 &#xff08;‘、"、’、“”&#xf…

git clone(复制)下载

1、复制 下载地址 2、打开网页&#xff0c;点击 克隆/下载按扭 3、按提示复制命令行到终端 4、VS里打开终端&#xff0c;并粘贴以下命令 5、 下载完毕 6、复制文件夹到你选定的位置 7、用VSCODE打开文件夹&#xff0c;开始你接下来的工作

Android设置adjustResize时无法生效 解决办法

删除Activity类下执行全屏的一行参数。 将图中这段Activity类中执行命令给删除就解决了。 注意关闭后状态栏和导航栏的透明度就无法自动处理了&#xff0c;需要到values和values-night下的themes.xml手动设置状态栏背景颜色。 <item name"android:statusBarColor"…

按键长按代码

这些代码都存放在定时器中断中。中断为100ms中断一次。 数据判断&#xff0c;看的懂就看吧

优选算法第八讲:链表

优选算法第八讲&#xff1a;链表 1.链表常用操作和技巧总结2.两数相加3.两两交换链表中的节点4.重排链表5.合并k个升序链表6.k个一组翻转链表 1.链表常用操作和技巧总结 2.两数相加 3.两两交换链表中的节点 4.重排链表 5.合并k个升序链表 6.k个一组翻转链表

4S店汽车维修保养管理系统 (源码+lw+部署文档+讲解),源码可白嫖!

摘要 二十一世纪我们的社会进入了信息时代&#xff0c;信息管理系统的建立&#xff0c;大大提高了人们信息化水平。传统的管理方式已经与当今4S店汽车维修保养管理系统的业务需求不相适应,也与4S店汽车维修保养管理系统化建设的发展趋势不相适应。本文针对这一需求设计并实现了…

【NLP 面经 8】

目录 一、文本生成任务 模型架构方面 训练数据方面 生成策略方面 二、命名实体识别任务NER 模型架构方面 特征工程方面 训练优化方面 三、情感分析任务 模型架构方面 训练数据方面 超参数调整方面 四、计算余弦相似度并添加符合条件结果 提示&#xff1a; 思路与算法 任由深渊的…