字符串匹配 - BF和RK算法

字符串匹配有很多种算法,今天在这里先介绍两种比较简单的算法:BF 和 RK 算法。之后我们会学习其它更复杂的匹配算法。

BF(Brute Force)算法

BF算法中文叫做暴力匹配算法,也是最朴素的匹配算法。它的设计思想非常简单,易懂,当然相应的,它的性能并不高。

主串 和 模式串:在进行算法介绍前,先定义这两个概念。例如:要在 A 中寻找 B ,那么 A 就是主串,B 就是模式串。

对于 BF 算法来说,它的思想很容易概括:从头在主串中依次寻找模式串。举个例子你会很清楚:

暴力匹配-BF算法.jpg

性能分析

时间复杂度:极端情况下,主串为 “aaa...aaa”,模式串是“aaaaab”。每次比对都要进行到最后一位才能确定是否匹配成功。假设主串有 n 个字符,模式串有 m 个字符,则要比对 n-m+1 次,所以,最坏情况为 O(n*m)。
尽管复杂度很高,但是这种极端情况很难遇到,反而由于 BF 算法思想简单,易实现,易维护等,成为了非常常用的字符串匹配算法。

RK算法

RK算法的思路是这样的:对主串中的 n-m+1 个子串分别求哈希值,然后逐个与模式串的哈希值比较。如果相等,就说明匹配(暂时不考虑哈希冲突,后面会讲),如果不相等,就说明不匹配。由于哈希值是数字,所以哈希的比较非常快,效率也会提升:
RK匹配.jpg

但是在这里有一个问题:在计算子串的哈希值的时候,我们需要遍历子串中的每个字符,如果我们循规蹈矩地遍历字符,尽管比对效率提高了,但是构造哈希值的过程却非常耗时。这就导致算法的整体效率并不高。
有什么方法可以提高计算哈希值的效率呢?

高效的哈希值的计算方法

首先,我们确定哈希值的计算方法,在这里,我们假设字符串只有 a~z 这 26 个小写字母,我们就用 二十六进制 来表示一个字符串。这样我们会有如下的计算方式:

RK-哈希计算.jpg

随后,我们注意一下在子串中两个相邻子串的计算方式:
RK-字符串哈希.jpg

你会发现,它们之间的计算有如下规律:
RK-哈希关系.jpg

这样,我们就不必每次都计算子串的哈希值了,而是可以根据上一个子串计算得到。这样,我们减少了对内存的访问次数,同时与模式串的比较也非常高效。

在这里还有一个小技巧,你可以将二十六进制每一位的值预先计算好,这样可以减轻计算机的计算负担:
RK-哈希计算暂存.jpg

用散列冲突减少计算负担

按照上面的逻辑,字符串的散列值不会产生散列冲突。但是这也带来了一个问题,如果模式串的长度非常长,比如达到 100 位,那计算出来的数值就会非常大,所需要的散列表的空间也难以接受。
这里,我们就需要改变哈希函数或者对哈希值进行取模了,而这样势必会产生散列冲突。这就需要我们对性能和应用场景进行综合考虑了。

性能分析

时间复杂度:程序分为两个部分:第一部分是根据主串构建子串的哈希值,这需要遍历字符串,时间复杂度为O(n)。第二部分是使用子串的哈希值和模式串进行比对,这里需要遍历 n-m+1 个子串的哈希值,所以,这一部分的时间复杂度也是O(n)。
综上,RK算法的整体时间复杂度是O(n)。

小结

BF 算法是最简单、粗暴的字符串匹配算法,它拿模式串依次与主串中的子串进行匹配。时间复杂度较高。但是在实际的软件开发中,由于这种算法的实现简单,所以在处理小规模的字符串匹配的时候很好用。

RK 算法是借助哈希算法对 BF 算法进行改造,即分别求子串的哈希值,然后利用哈希值进行比对,以减少比较时间。既然 RK 算法使用到了哈希算法,就要考虑哈希函数的设计问题和对散列冲突的处理。

感悟

之前我们提到过,用优势弥补不足,对计算机来说,优势是计算快速,不足是内存访问缓慢。学习了今天的匹配算法,尤其是了解了在 RK 算法中对哈希的应用,你对这句话是否有了更深的感悟?


以上就是本节的全部内容

注:本文章的主要内容来自我对极客时间app的《数据结构与算法之美》专栏的总结,我大量引用了其中的代码和截图,如果想要了解具体内容,可以前往极客时间

一个代码实例

我曾经写过一个通讯录软件,其中有一个查找信息的功能就使用到了字符串查找,当时不知道 BF 算法,但是使用的思想和 BF 完全一致,下面就将段代码放在下面,可以拿来参考。

#include<stdio.h>
#include<iostream>
#pragma warning(disable:4996)
using namespace std;


typedef struct person           //设置基本结构
{
    char name[20];
    char sex[10];
    char address[50];
    char number[15];
    struct person *next;
}person,*zz;

void create_for_head(zz &head);                 //声明需要的所有的函数
void delete_person(zz want, zz q);
void change_person(zz p);
void add_person(zz &head);
int search_person(char cx[20], zz who);
void print_list(zz head);
void print_person(zz temp);
void search_list(zz head);
void delete_list(zz head);
void change_list(zz head);




void create_for_head(zz &head)              //初始化这个通讯录
{
    head =(zz)malloc(sizeof(person));       
    head->next = NULL;

    strcpy(head->name, "姓名");           //为这个通讯录事先存储一些信息
    strcpy(head->sex, "性别");
    strcpy(head->address, "地址");
    strcpy(head->number, "号码");

    zz p,q;
    p = head;
    q = (zz)malloc(sizeof(person));
    q->next = NULL;
    strcpy(q->name, "张三");
    strcpy(q->sex, "女");
    strcpy(q->address, "北京");
    strcpy(q->number, "12345678901");
    p->next = q;
    p = p->next;

    q = (zz)malloc(sizeof(person));
    q->next = NULL;
    strcpy(q->name, "李四");
    strcpy(q->sex, "男");
    strcpy(q->address, "辽宁");
    strcpy(q->number, "9876543210");
    p->next = q;
    p = p->next;

    q = (zz)malloc(sizeof(person));
    q->next = NULL;
    strcpy(q->name, "谭同学");
    strcpy(q->sex, "男");
    strcpy(q->address, "新疆");
    strcpy(q->number, "13384466772");
    p->next = q;
    p = p->next;

    q = (zz)malloc(sizeof(person));
    q->next = NULL;
    strcpy(q->name, "杨老师");
    strcpy(q->sex, "男");
    strcpy(q->address, "吉林");
    strcpy(q->number, "16875465198");
    p->next = q;
    p = p->next;

    q = (zz)malloc(sizeof(person));
    q->next = NULL;
    strcpy(q->name, "吕同学");
    strcpy(q->sex, "女");
    strcpy(q->address, "天津");
    strcpy(q->number, "79987413642");
    p->next = q;
    p = p->next;
}

void add_person(zz &head)           //为通讯录添加一个人员信息
{
    zz p, q;
    q = head;
    for (; q->next != NULL;)
    {
        q = q->next;
    }
    p = (zz)malloc(sizeof(person));
    p->next = NULL;
    cout << "请输入人员的通讯信息:姓名、性别、地址、号码" << endl;
    cin >> p->name >> p->sex >> p->address >> p->number;
    q->next = p;
    system("cls");
    cout << "操作已完成" << endl;
}

void print_list(zz head)                //输出这个通讯录的信息
{
    zz i;
    i = head;
    for (; i; i = i->next)
    {
        cout << i->name << "\t" << i->sex << "\t" << i->address << "\t" << i->number << endl;
    }
}

void print_person(zz temp)              //输出一个person的信息
{
    if (temp!= NULL)
    {
        cout << temp->name << "\t" << temp->sex << "\t" << temp->address << "\t" << temp->number << endl;
    }
}

void search_list(zz head)               //执行查询操作
{
    zz p,q;
    cout << "输入部分信息进行查找:" << endl;
    char search[20];
    cin >> search;
    p = head->next;
    q = head;
    int x = 0;
    while (p)
    {
        if (search_person(search, p))
        {
            if (x == 0)cout << "姓名" << "\t" << "性别" << "\t" << "地址" << "\t" << "号码" << "\t" << endl;
            print_person(p);
            x++;
        }
        q = q->next;
        p = p->next;
    }
    if (x == 0)
    {
        system("cls");
        cout << "没有查询到任何与之相关的信息" << endl<<endl;
    }
}

int search_person(char cx[20], zz who)      //用于查找一段信息是否属于某个person
{
    int i, m, n;
    m = strlen(cx);             //查询号码
    n = strlen(who->number);
    int p = 0;
    for (i = 0; i <= n; i++)
    {
        if (cx[p] == who->number[i])
        {
            p++;
            if (p == m) return 1;
        }
        else p = 0;
    }

    n = strlen(who->name);          //查询姓名
    p = 0;
    for (i = 0; i <= n; i++)
    {
        if (cx[p] == who->name[i])
        {
            p++;
            if (p == m) return 1;
        }
        else p = 0;
    }

    n = strlen(who->address);       //查询地址
    p = 0;
    for (i = 0; i <= n; i++)
    {
        if (cx[p] == who->address[i])
        {
            p++;
            if (p == m) return 1;
        }
        else p = 0;
    }

    n = strlen(who->sex);       //性别
    p = 0;
    for (i = 0; i <= n; i++)
    {
        if (cx[p] == who->sex[i])
        {
            p++;
            if (p == m) return 1;
        }
        else p = 0;
    }
    return 0;
}

void delete_person(zz want,zz q)        //删除一个person
{
    zz temp;
    temp = want;
    want = temp->next;
    q->next = temp->next;
    want = q->next;
    free(temp);
    cout << "已删除" << endl;
}

void delete_list(zz head)           //从通讯录中执行删除操作
{
    zz p=head->next, q=head;
    char name[20];
    cout << "请输入要删除的人的姓名" << endl;
    cin >> name;
    int pd=1;
    while (p)
    {
        if (!strcmp(p->name, name))
        {
            delete_person(p, q);
            break;
        }
        q = q->next;
        p = p->next;
    }
    if (p == NULL)cout << "查找不到该人信息" << endl;
}

void change_person(zz p)            //修改一个person的信息
{
    cout << "请输入这个人的新信息" << endl;
    cin >> p->name >> p->sex >> p->address >> p->number;
}

void change_list(zz head)       //从通讯录中执行修改操作
{
    zz p = head;
    char name[20];
    cout << "你想要修改谁的信息?" << endl;
    cin >> name;
    while (p)
    {
        if (!strcmp(p->name, name))
        {
            change_person(p);
            cout << "修改完成" << endl;
            break;
        }
        p = p->next;
    }
    if (p == NULL)cout << "查找不到你要修改的人" << endl;
}

int main()
{
    zz head;
    create_for_head(head);
    int choose;
    while (1)
    {
        cout << endl;
        cout << "\t|-----------------------------------|" << endl;
        cout << "\t|                                   |" << endl;
        cout << "\t|              菜单:               |" << endl;
        cout << "\t|                                   |" << endl;
        cout << "\t|         1-添加通讯录              |" << endl;
        cout << "\t|         2-查看所有联系人          |" << endl;
        cout << "\t|         3-通讯录查询              |" << endl;
        cout << "\t|         4-删除联系人              |" << endl;
        cout << "\t|         5-修改联系信息            |" << endl;
        cout << "\t|                                   |" << endl;
        cout << "\t|         0-退出                    |" << endl;
        cout << "\t|                                   |" << endl;
        cout << "\t|-----------------------------------|" << endl;
        cin >> choose;
        switch (choose)
        {
        case 1:add_person(head); continue;
        case 2:system("cls"); print_list(head);  continue;
        case 3:system("cls"); search_list(head); continue;
        case 4:system("cls"); delete_list(head); continue;
        case 5:system("cls"); change_list(head); continue;
        case 0:return 0;
        }
    }
    system("pause");
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容