字符串算法大整理!你能想到的都能找到

字符串算法大整理!你能想到的都能找到(吧)。

2018.7.16 Chengdu

今天学习了字符串相关的一些算法,种类挺多的,特来整理一波。

字符串哈希(Hash)

简介

哈希( HashHash 所远不能及的。

第一次了解到哈希这一种技术是在吴军 dalaodalao 的借阅!)讲解网络爬虫的数学原理的一章。有兴趣的话可以去读一读。

原理

哈希查找

为什么哈希查找的时间复杂度可以这么低呢?考虑下面一个问题:

设计一个程序,以完成以下输入输出:
第一行,输入两个数字 n,mn,m

对于朴素算法,我们把第二行输入的数字储存在一个数组中。对于每一次询问,我们从左到右遍历整个数组来查询,每次询问的时间复杂度为 O(n)O(n)

对于二分查找算法,我们把第二行输入的数字进行排序。对于每一次询问,我们用二分查找的方法找到这个数字处在的位置,排序的时间复杂度为 O(logn)O(log⁡n)

可是这一题真的有这么复杂吗?我们可以使用一个大小为 105105 数组,就可以得到询问的结果了。而哈希,就是运用了这样的思想:我们多开一个数组,用这些多申请的空间去解决时间上的复杂。这也正是应了那句名言:

以空间换时间。 —-蒋介石

字符串哈希

那么如果我们把题改一下呢?

设计一个程序,以完成以下输入输出:
第一行,输入两个数字 n,mn,m

如果输入的是字符串的话,我们不就不能用 visvis 这一神奇的模板库,那么你一定会毫不犹豫地打出一行这样的代码:

map<string,bool>vis;
 
 
  • 1

是的, mapmap 可以完美的解决这个问题。不过,我们可以试试用自己的力量来完成对字符串的处理:将字符串转换为数字。

怎样转换呢?我们知道,每一个字符都有它对应的 ASCIIASCII 的增大而逐步增大。

接下来给出一个函数 HashHash ,来获取一个字符串的哈希值:

const int P=131;

int Hash(string tmp)
{
    int hash=0;
    for(int i=0;i<tmp.length();i++)
        hash=hash*P+tmp[i];
    return hash;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可是我们又面临了一个问题,这是因为题目中写到:

数据说明:输入的字符串的长度范围在 [1,105][1,105]

字符串的最大长度是 105105 不就爆炸了吗?

所以我们还要定义一个大数 MM 。当然,如果你乐意,取其它的值也是可以的。那么这个函数的运算就变成了:

const int P=131;
const int M=99991;

int Hash(string tmp)
{
    int hash=0;
    for(int i=0;i<tmp.length();i++)
        hash=(hash*P+tmp[i])%M;
    return hash;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这样我们就得到了一个字符串的哈希值,不过问题又出现了:怎样保证两个字符串的哈希各不相同?

这确实是哈希的一个大问题,具体来说有三种解决方法:

  1. 通过增加哈希池大小来降低两个字符串的哈希值冲突的概率。哈希池就是储存字符哈希值的地方,对应上述问题中的 visvis 的方式来达到这一结果,不过这样就会使得哈希计算过于复杂而且难以储存。
  2. 对于每个字符串的哈希值记录原有字符串,在冲突的情况下对原有字符串进行逐次比较。

第二种方法是我的常用方法,不过怎么实现呢?这里我们可以不使用 visvis ,具体来说这样写:

vector<string>v[M];

bool Query(string tmp)
{
    int pos=Hash(tmp);
    for(int i=0;i<v[pos].size();i++)
        if(v[pos][i]==tmp) return true;
    return false;
}
void Add(string tmp)
{
    if(Query(tmp)) return ;
    int pos=Hash(tmp);
    G[pos].push_back(tmp);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这样就达到了我们的目的。这里给出一道字符串哈希的模板题:

Luogu P3370 【模板】字符串哈希

哈希的弊端 & 如何卡哈希

可是还有的人为了图代码书写方便而拒绝使用强大的 vectorvector 来进行储存,这样就会被毒瘤出题人卡,因为会有人思考出卡掉各种字符串哈希的方法,具体可见几道神题:

BZOJ 3097 Hash Killer I
BZOJ 3098 Hash Killer II
BZOJ 3099 Hash Killer III
BZOJ 4917 [Lydsy1706月赛]Hash Killer IV (付费警告!)

在这些题目里,你被要扮演一个毒瘤出题人。因为你的后缀自动机神题被人用字符串哈希水掉了,所以你很不开心,决定用一组自造数据来卡掉他的代码。在这些题目中,给出水题的人的C++代码,要求你输出一组 hackhack 数据。

题目挺有意思,但是我们如何来操作呢?即,我们如何构造出两个相同的字符串,使它们的哈希值相同呢?

先拿第一题做例子。下面是题目大意:

Hash killer 1:
本来的神题:给你一个长度为 NN

自然溢出是什么意思呢?就是不去取 MM 。他的代码如下:

typedef unsigned long long u64;
const int MaxN = 100000;
inline int hash_handle(const char *s, const int &n, const int &l, const int &base)
{
    u64 hash_pow_l = 1;
    for(int i=1;i<=l;i++)
        hash_pow_l *= base;
    int li_n = 0;
    static u64 li[MaxN];
    u64 val = 0;
    for(int i=0;i<l;i++)
        val=val*base+s[i]-'a';
    li[li_n++]=val;
    for(int i=l;i<n;i++)
    {
        val=val*base+s[i]-'a';
        val-=(s[i-l]-'a')*hash_pow_l;
        li[li_n++]=val;
    }
    sort(li,li+li_n);
    li_n=unique(li,li+li_n)-li;
    return li_n;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

需要注意的事,这一代码中的基数 basebase 值)是随机的。那么我们就需要分类讨论:

  • 如果基数 basebase

恭喜你,经过了层层计算,终于卡掉了他的代码!(体会到毒瘤出题人的艰辛)

接下来我们再来看看相对与计算无关的第二题吧。神题还是一模一样,只不过水题的人不打算使用自然溢出了,而是打算用模大质数的方法(这是剽窃我的代码啊喂)。而且喜欢大数字的他打算加大哈希池,令 M=109+7M=109+7 。他的代码是这样的:

typedef unsigned long long u64;
const int MaxN = 100000;

inline int hash_handle(const char *s, const int &n, const int &l, const int &base)
{
    const int Mod=1000000007;
    u64 hash_pow_l=1;
    for(int i=1;i<=l;i++)
        hash_pow_l=(hash_pow_l*base)%Mod;
    int li_n=0;
    static int li[MaxN];
    u64 val=0;
    for(int i=0;i<l;i++)
        val=(val*base+s[i]-'a')%Mod;
    li[li_n++]=val;
    for(int i=l;i<n;i++)
    {
        val=(val*base+s[i]-'a')%Mod;
        val=(val+Mod-((s[i-l]-'a')*hash_pow_l)%Mod)%Mod;
        li[li_n++]=val;
    }
    sort(li,li+li_n);
    li_n =unique(li,li+li_n)-li;
    return li_n;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

这道题的做法也不难,但是要考验你的欧气了,先来了解一个神奇的悖论:

生日悖论_百度百科

那么同样的,如果你用大量的数据去卡他,他不就废了吗?不过如果你足够非,你还是可以WA到飞起的。

KMP算法

简介

三名算法大佬克努特( D.E.KnuthD.E.Knuth 算法。网上对于这种算法有很多的讲解,而讲的最好的非这一篇莫属了。感谢这位大佬详尽的讲解。摘录于此,方便大家学习:

原文地址:v_JULY_v 的博客

1. 引言

KMPKMP 的理解始终不够,故才迟迟没有修改本文。

然近期因开了个算法班,班上专门讲解数据结构、面试、算法,才再次仔细回顾了这个 KMPKMP ,在综合了一些网友的理解、以及算法班的两位讲师朋友曹博、邹博的理解之后,写了

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值