串的BF算法和KMP算法个人总结
子串(模式串)的定位操作通常称作串的模式匹配
其中包含最初始的BF算法(Brute-Force)即简单匹配算法或者称作朴素的模式匹配算法,利用穷举法的思路
另一种就是改进后的KMP算法,还有对KMP算法的一种优化算法
现在先展示第一种BF算法的代码及解决思路:
#define _CRT_SECURE_NO_WARNINGS 1
#define MAXSIZE 255
#define ERROR 0
#include<stdio.h>
#include<stdlib.h>typedef struct {char ch[MAXSIZE];int length;
}SString;int main()
{int compare(SString S, SString T, int pos);SString S;SString T;//定义主串和模式串(子串),分别为S,Tint i = 1;//i表示主串第一个元素的数组下标//比较串通常从数组下标为1开始,0位置不存储或者通常存放数组的长度//这里为了方便数组S.ch[0]和T.ch[0]不存放任何数据,长度事先定好并存在S.length和T.length中int j = 1;//j表示子串第一个元素的数组下标printf("请输入主串长度\n");scanf("%d%*c", &S.length);//使用%*c吃掉回车,不然scanf读取回车存在缓存区//下一次使用scanf输入char类型的元素会先读取缓存区的换行符(\n),影响多次scanf使用//这里有好几种解决方法,具体请参考《解决scanf读取回车问题》printf("请输入主串元素\n");while (i < S.length+1)//这里数组长度的定义是你输入的总元素的个数,不是所谓的总长度//最开始我们的0下标位置没有存放,所以i从1开始,循环S.length次,所以<S.length+1//这种比较不确定时候掰手指头带几个数字算算~~~~{scanf("%c", &S.ch[i]);//输入前面已经定好个数的主串元素,记住输入时候不要加空格!!!!!i++;}printf("请输入子串长度\n");//这里就不用考虑回车的问题了,%c情况下才会读取,%d无影响scanf("%d%*c", &T.length);//使用%*c吃掉回车,不然scanf读取回车存在缓存区,影响多次scanf使用printf("请输入子串元素\n");while (j < T.length + 1){scanf("%c", &T.ch[j]);j++;}int pos = 1;//pos是比较起点,也就是从主串哪一位置开始和子串比较int s = 0;//s保存比较函数的返回值printf("请输入比较起始点:\n");scanf("%d", &pos);if (pos<1 || pos>S.length)//记住比较最小从1开始,最大不能超过主串总长//因为我们把元素从1开始存放,所以从哪个元素开始位序和下标一样,不用担心不符合问题{exit(ERROR);}else{s = compare(S, T, pos);}if (s != 0)printf("字串从第%d位开始匹配成功",s);elseprintf("未匹配成功");return 0;
}int compare(SString S, SString T, int pos)//BF算法,i每次要回溯
{int i = pos;//i代表起时从哪个位置开始比较int j = 1;//j代子串的元素位置while (i <= S.length&& j <= T.length)//如果i,j分别小于等于各自长度就可以继续比,大于就没得比了~{if (S.ch[i] == T.ch[j])//如果相等{++i;++j;//i和j都++,都准备开始比较各自的下一个元素}else{i = i - j + 2;//i回溯到当前开始比较位置的下一位置//这里理解为(i-j+1)+1,前面括号里面是刚开始比较的i的位置序号,后面加一表示往后移动一位//从后面一位开始,其位置元素作为新的比较起点开始比较j = 1;//j回溯到起始位置,每次匹配失败后子串就从头开始比较}}if (j > T.length) //最后如果比较成功了,子串对应的j达到了最后一位,即T.length//但是要记住,你最后一个元素匹配成功进入的if语句还有一个++i和++j,所以最后j=T.length+1return (i - T.length);//上面说明了,i最后还有个++,最后i值为比完的最后一位的下一位对应的ielsereturn 0;
}
每次比较后,i都要回溯,j也得从第一位开始重新比较,效率其实非常低,于是一种新的算法腾空出现,就是最初的KMP算法。
他的核心思想就是i不回溯,一遍到底,j回溯到合适位置,不是每次从头开始
KMP的主函数和BF的差不多,先展示其匹配函数:
int KMP_cmp(SString S, SString T, int pos)//KMP算法
{int i = pos;int j = 1;int next[MAXSIZE];//定义next数组nextval(T, next);//为next数组赋值while (i <= S.length && j <= T.length){if (j==0||S.ch[i] == T.ch[j])//多增加了j==0的判断{++i;++j;}else{j = next[j];//i不回溯,j回溯到适当位置}}if (j > T.length)return (i - T.length);elsereturn 0;
}
再就是创建不完美的next数组的函数:
void nextval(SString T, int* next)//最原始的KMP算法的next数组
//如果碰到aaaaab这种子串,会被赋值012345,增加了不必要的回溯
{int i, k;i = 1;k = 0;next[1] = 0;while (i < T.length){if (k == 0 || T.ch[i] == T.ch[k]){++i;++k;next[i] = k;}elsek = next[k];}
}
先给大家简单分析一下k和i的含义和关系(改进后的照样继续分析即可):
首先是next数组生成时候我们把if和else抽出来分析:
现在把最完美的KMP算法展示给大家:
(解决了前面重复元素的多次k回溯问题)
#define _CRT_SECURE_NO_WARNINGS 1
#define MAXSIZE 255
#define ERROR 0
#include<stdio.h>
#include<stdlib.h>typedef struct {char ch[MAXSIZE];int length;
}SString;int main()
{int KMP_cmp(SString S, SString T, int pos);SString S;SString T;//定义主串和模式串(子串)int i = 1;int j = 1;printf("请输入主串长度\n");scanf("%d%*c", &S.length);//使用%*c吃掉回车,不然scanf读取回车存在缓存区,影响多次scanf使用printf("请输入主串元素\n");while (i < S.length + 1){scanf("%c", &S.ch[i]);i++;}printf("请输入子串长度\n");//这里就不用考虑回车的问题了,%c情况下才会读取,%d无影响scanf("%d%*c", &T.length);//使用%*c吃掉回车,不然scanf读取回车存在缓存区,影响多次scanf使用printf("请输入子串元素\n");while (j < T.length + 1){scanf("%c", &T.ch[j]);j++;}int pos = 1;int s = 0;printf("请输入比较起始点:\n");scanf("%d", &pos);if (pos<1 || pos>S.length)//记住比较最小从1开始,最大不能超过主串总长//因为我们把元素从1开始存放,所以从哪个元素开始位序和下标一样,不用担心不符合问题{exit(ERROR);}else{s = KMP_cmp(S, T, pos);}if (s != 0)printf("字串从第%d位开始匹配成功", s);elseprintf("未匹配成功");return 0;
}//主函数和BF算法一致,关键在于一个next数组的构建,他存放着j要回溯到的位置void nextval(SString T, int *next)
{int i, k;i = 1;//i表示元素的位序k = 0;//k代表当前i位置元素的最大公共前缀+1//例如abcabd,next值为011123next[1] = 0;//第一个元素的next值人为先定义为0while (i < T.length){if (k == 0 || T.ch[i] == T.ch[k])//如果k==0或者i位置和k位置的元素值相等{++i;++k;if (T.ch[i] != T.ch[k])//如果i位置的值和k位置的值不等,也就是外面if中以k==0条件进来的next[i] = k;//把此时k的值赋给i位置元素的nextelsenext[i] = next[k];//如果相等i位置的next和k位置的next相等}elsek = next[k];//如果既不相等k又不等于0,k就被赋值为k位置的next}
}int KMP_cmp(SString S, SString T, int pos)//KMP算法
{int i = pos;int j = 1;int next[MAXSIZE];//定义next数组nextval(T, next);//为next数组赋值while (i <= S.length && j <= T.length){if (j==0||S.ch[i] == T.ch[j])//多增加了j==0的判断{++i;++j;}else{j = next[j];//i不回溯,j回溯到适当位置}}if (j > T.length)return (i - T.length);elsereturn 0;
}
下面是输入输出结果:
下面展示用数组第一位(下标为0)存储数组长度的相同算法(BF与KMP):
#define MAXSIZE 255
#define ERROR 0
#include<stdio.h>
#include<stdlib.h>typedef struct {char ch[MAXSIZE];
}SString;int main()
{//int compare(SString S, SString T, int pos);int KMP_cmp(SString S, SString T, int pos);SString S;SString T;int i = 1;int j = 1;char x, y;//先把串元素的值赋给x,y再把其赋给数组对应下标位置,这样能方便终止输入printf("请输入主串元素(输入'#'停止,每输入一个元素用enter换行)\n");scanf("%c%*c", &x);for (i = 1; x != '#'; i++){S.ch[i] = x;scanf("%c%*c", &x);}S.ch[0] = i - 1;//数组下标为0的位置存放数组的长度fflush(stdin);//清空缓存区的所有字符,这里因为最后输入#后面接了一个换行,不清除后面子串第一个接收的就是换行符printf("请输入子串元素(输入'#'停止,每输入一个元素用enter换行)\n");scanf("%c%*c", &y);for (j = 1; y != '#'; j++){T.ch[j] = y;scanf("%c%*c", &y);}T.ch[0] = j - 1;int pos = 1;//pos是比较起点,也就是从主串哪一位置开始和子串比较int s = 0;//s保存比较函数的返回值printf("请输入比较起始点:\n");scanf("%d", &pos);if (pos<1 || pos>S.ch[0])//记住比较最小从1开始,最大不能超过主串总长//因为我们把元素从1开始存放,所以从哪个元素开始位序和下标一样,不用担心不符合问题{exit(ERROR);}else{//s = compare(S, T, pos);s = KMP_cmp(S, T, pos);}if (s != 0)printf("字串从第%d位开始匹配成功", s);elseprintf("未匹配成功");return 0;
}//int compare(SString S, SString T, int pos)//BF算法,i每次要回溯
//{
// int i = pos;//i代表起时从哪个位置开始比较
// int j = 1;//j代子串的元素位置
// while (i <= S.ch[0] && j <= T.ch[0])//如果i,j分别小于等于各自长度就可以继续比,大于就没得比了~
// {
// if (S.ch[i] == T.ch[j])//如果相等
// {
// ++i;
// ++j;//i和j都++,都准备开始比较各自的下一个元素
// }
// else
// {
// i = i - j + 2;//i回溯到当前开始比较位置的下一位置
// //这里理解为(i-j+1)+1,前面括号里面是刚开始比较的i的位置序号,后面加一表示往后移动一位
// //从后面一位开始,其位置元素作为新的比较起点开始比较
// j = 1;//j回溯到起始位置,每次匹配失败后子串就从头开始比较
// }
// }
// if (j > T.ch[0]) //最后如果比较成功了,子串对应的j达到了最后一位,即T.length
// //但是要记住,你最后一个元素匹配成功进入的if语句还有一个++i和++j,所以最后j=T.length+1
// return (i - T.ch[0]);//上面说明了,i最后还有个++,最后i值为比完的最后一位的下一位对应的i
// else
// return 0;
//}
//
void nextval(SString T, int *next)
{int i, k;i = 1;//i表示元素的位序k = 0;//k代表当前i位置元素的最大公共前缀+1//例如abcabd,next值为011123next[1] = 0;//第一个元素的next值人为先定义为0while (i < T.ch[0]){if (k == 0 || T.ch[i] == T.ch[k])//如果k==0或者i位置和k位置的元素值相等{++i;++k;if (T.ch[i] != T.ch[k])//如果i位置的值和k位置的值不等,也就是外面if中以k==0条件进来的next[i] = k;//把此时k的值赋给i位置元素的nextelsenext[i] = next[k];//如果相等i位置的next和k位置的next相等}elsek = next[k];//如果既不相等k又不等于0,k就被赋值为k位置的next}
}int KMP_cmp(SString S, SString T, int pos)//KMP算法
{int i = pos;int j = 1;int next[MAXSIZE];//定义next数组nextval(T, next);//为next数组赋值while (i <= S.ch[0] && j <= T.ch[0]){if (j==0||S.ch[i] == T.ch[j])//多增加了j==0的判断{++i;++j;}else{j = next[j];//i不回溯,j回溯到适当位置}}if (j > T.ch[0])return (i - T.ch[0]);elsereturn 0;
}
下面是输入输出结果:
时间复杂度:(m,n分别是主串和子串的长度)
BF算法: O(m*n)
KMP算法 O(m+n)
KMP的思想和next数组创建建议多独立分析,先搞清思想再来用代码实现~~
独立研究next数组的创建过程------为什么需要、为什么这么建立
串的BF算法和KMP算法个人总结相关推荐
- 若S作主串,P作模式串,试分别写出利用BF算法和KMP算法的匹配过程。
目 录 题目: 百度文库-答案: (1) (2) MOOC标准答案: (1) (2) mooc答案-截图: 数据结构(C语言版)-严蔚敏2007 题目: 设字符串S='aabaabaabaac', ...
- BF算法和KMP算法
给定两个字符串S和T,在主串S中查找子串T的过程称为串匹配(string matching,也称模式匹配),T称为模式.这里将介绍处理串匹配问题的两种算法,BF算法和KMP算法. BF算法 (暴力匹配 ...
- Algorithm:C++语言实现之字符串相关算法(字符串的循环左移、字符串的全排列、带有同个字符的全排列、串匹配问题的BF算法和KMP算法)
Algorithm:C++语言实现之字符串相关算法(字符串的循环左移.字符串的全排列.带有同个字符的全排列.串匹配问题的BF算法和KMP算法) 目录 一.字符串的算法 1.字符串的循环左移 2.字符串 ...
- 【算法篇-字符串匹配算法】BF算法和KMP算法
目录 前言 1. BF算法 1.1 画图分析 1.3 BF 算法的时间复杂度 2. KMP 算法 2.1 KMP 算法和 BF 算法 的区别 2.1.1 为什么主串不回退? 2. 2 next 数组 ...
- 数据结构之字符串匹配算法(BF算法和KMP算法)
字符串匹配算法: 就是给定两个串,主串(s)和子串(sub), 查找子串是否在主串里面,如果找到便返回子串在主串中第一个元素的位置下标,否贼返回-1,. 在这里我 们讨论的时候主要用字符串来举例实现. ...
- 模式串匹配的BF算法和KMP算法
KMP是三位大牛:D.E.Knuth.J.H.Morris和V.R.Pratt同时发现的.为了解决模式匹配问题,也即寻找模式串(子串)在主串中第一次出现的位置,若模式串在主串中不存在则返回-1. 简单 ...
- 字符串匹配—BF算法和KMP算法
BF算法 本章重点是是KMP算法,但是由于其较难理解,先从更简单朴素的BF算法开始. 其思路非常简单 也就是,对这样两个字符串(称短的为模式串,长的为主串): 让主串和模式串的每个字符逐个匹配,如果从 ...
- BF算法和KMP算法实现
https://blog.csdn.net/xxibgdrgn_liqian_a_/article/details/80690593
- 数据结构与算法之美笔记——基础篇(下):图、字符串匹配算法(BF 算法和 RK 算法、BM 算法和 KMP 算法 、Trie 树和 AC 自动机)
图 如何存储微博.微信等社交网络中的好友关系?图.实际上,涉及图的算法有很多,也非常复杂,比如图的搜索.最短路径.最小生成树.二分图等等.我们今天聚焦在图存储这一方面,后面会分好几节来依次讲解图相关的 ...
最新文章
- 中山行书百年纪念版字体可以商用吗_干货|免费可商用字体
- 【移动通信】4G LTE帧结构
- GraphQL 学习
- 【django】项目准备
- 虚拟机上的ubuntu安装vmware tools
- 百度地图API开发指南
- Java笔记-spring-rabbitmq中使用@RabbitListener消费(手动确认,获header数据)
- python3 正则 去除 html标签、提取正文内容_Python通过正则表达式去除(过滤)HTML标签,提取文字...
- java 获取文件大小_利用百度AI OCR图片识别,Java实现PDF中的图片转换成文字
- 【简●解】POJ 1845 【Sumdiv】
- 密码学40年|重要历史与人物
- GD32Pack包下载地址
- My97DatePicker事件无效,触发事件calendar.js,datepicker.css等文件无法加载
- C++[POJ1849]铲雪车问题——树形DP求出树的直径
- 爬取20万数据,我们深扒了风口上的「享物说」
- 乐动手环app下载安装_乐动健康下载app_乐动健康下载安装app手环v2.34
- 苹果怎么换行打字_微信新功能上线!安卓苹果都有!网友:再也不怕被刷屏了!!...
- 渗透测试之---xss-labs闯关【1-14关】
- java点打印没反应_兄弟QL打印机SDK - 不打印,没有任何反应(QL-710W)
- el-radio-button 设置默认选中问题