判断一个字符串是否是另一个字符串的子字符串
学习之前我们先看一下暴力法判断子串的方法
假设两个字符串源串和子串,匹配过程如下:
当匹配到如上图的位置时,源串I位置处字符和子串j处字符不相同,此时,源串和子串的匹配下标都要进行回溯。
源串回溯到开始匹配时的下一位置,即图中的i+1;子串回溯到起始位置,重新开始判断。
代码实现思路如下:暴力法
/*********
* 判断一个字符串是否是另一个字符串的子字符串----暴力法 O
* @param oristring 源字符串
* @param substring 孩子字符串
* @return 源字符串中是否含有子字符串
*/
public static boolean hasSubString(String oristring,String substring)
{
//非空判断
if(oristring==null||substring==null)
{
//有一者为空时 均不视为子字符串
return false;
}
//将字符串转化为字符数组
char[] oriStr=oristring.toCharArray();
char[] subStr=substring.toCharArray();
//循环开始匹配
for(int i=0;i<oriStr.length;i++)//循环匹配源串
{
int temp=i;//遍历源串与子串比较
int subIndex=0;//遍历子串的下标 每次从零开始
while((temp<oriStr.length)&&(subIndex<subStr.length)&&(oriStr[temp]==subStr[subIndex]))
{
//判断下一位是否相同
subIndex++;
temp++;
}
//循环跳出
if(subIndex==subStr.length)//子串遍历完成 匹配成功
{
return true;
}
//否则 源串后移一位 从下一位开始匹配
}
//源串遍历结束仍未找到
return false;
}
另一种暴力法:使用java字符串比较函数实现
/*********
* 判断一个字符串是否是另一个字符串的子字符串----暴力法 O(n^3)
* 实现思路:循环查找源字符串的每一个子串 判断是否与子串相同
* @param oristring 源字符串
* @param substring 孩子字符串
* @return 源字符串中是否含有子字符串
*/
public static boolean hasSubString(String oristring,String substring)
{
//非空判断
if(oristring==null||substring==null)
{
//有一者为空时 均不视为子字符串
return false;
}
//双重循环列举源字符串的所有子串
for(int i=0;i<oristring.length();i++)
{
for(int j=i+1;j<=oristring.length();j++)
{
//偷个懒 调用一下库函数
if(oristring.substring(i, j).equals(substring))
{
return true;
}
}
}
//循环结束没找到
return false;
}
KMP算法实现字符串的匹配
感谢博主分享
思路:
暴力法也称朴素匹配模式算法,根据以上分析我们可以发现,遍历源串的下标在匹配不成功时都会进行回溯,回溯到上次匹配开始位置的下一位重新开始匹配,造成了极大的资源浪费。当子串的所有字符都不相同时,我们甚至不用回溯 i ,直接在匹配结束的位置开始。
那么,是有有一种可以不用回溯 i 的算法呢?接下来我们来理解一下KMP算法
这次,我们不从KMP的理论开始学习,而是直接分析KMP算法中出现的重要结构,知道如何使用这些结构后,反向去理解算法。
上面我们假设了当子串的各个字母都不相同时,我们可以不用回溯 i ,原因在于 i 之前到开始位置之间的字母全是已经匹配了的,而子串又无相同字母,那么子串的首字母和源串中的 i 到开始位置之间的任意字符都不匹配。如下图:
而如果子串中的字母出现了重复呢?为解决这个问题,我们引入了next数组。
KMP算法中next数组的作用是什么?
next数组记录一个字符串中从零到数组下标处的字符的最长的相同前缀和后缀的长度。
不明白???没关系 分解着理解一下
首先,什么是字符串的前缀?
字符串前缀是指从零开始到字符串非终点的任意位置的字符组成的字符串。假设有字符串ababca,那么它的前缀就有{a、ab、aba、abab、ababc}并不包括字符串本身。也就是单个字符是没有前缀的。
然后,什么是字符串的后缀?
字符串前缀是指从字符串终点位置开始到字符串非起点的任意位置的字符组成的字符串。
同样字符串ababc,那么它的后缀就有{babca、abca、bca、ca、a}。同样,单个字符没有后缀。
最后,什么是最长的相同前缀和后缀的长度呢?
还还还是ababc,相同的前缀和后缀有{a};那么公共前后缀的最大长度就是1,因为公共前后缀只有a,它的长度就是1.
ababca
next[i] = 001201 就记录了到从0到下标位置的字符组成的字符串最长的公共前后缀的长度。如next[3]=2,表示字符串abab的***长度为2。
有了这个数组就能不用回溯 i 了吗?
对的!!!来看一下原理 之前我们将ababca视为源串,现在将它作为子串来看,假设有源串abacababca
在上面,我们已经知道next[i]所表达的含义;那么,j=next[j-1]表示什么呢?
当源串 i 位置的字符和子串 j位置的字符不相等时,已经匹配了子串已经匹配了 0~j-1个元素,而next[j-1]就表示子串中已经匹配的部分字符串的公共前后缀最大长度 。如果这些个字符的公共前后缀最大长度不为0,说明有部分的前后缀是相同的,而我们现在要做的就是用 匹配部分的子串前缀 去匹配匹配部分的源串后缀。
注意 j-1 一定要大于0
KMP算法中next数组的求法
使用子串与子串本身进行匹配,其中一个视为源串,另一个视为子串。源串从下标为1开始,子串从下标0开始。初始化置next[0]=0;
如果源串和子串对应位置的字符相等,next[i]=next[i-1]+1;源串和子串同时访问下一节点。
如果源串和子串对应位置字符不相等且子串下标当前不在开始位置,将子串下标置于开始位置,j==0;
如果源串和子串对应位置字符不相等且子串下标当前在开始位置,next[i]先置零,源串下标再后移一位(i++)。
next数组求解实现
/********************
*求解next数组
*求解方式就是拿自己和自己匹配 当然一个从1开始 一个从0开始
*源串从第二位开始i=1 子串从第一位开始j=0
*比如一个字符串ababc 比较方式为
* ababc第一位不匹配 后移,即i++去匹配源串中的后一位
* ababc第一位匹配 同时后移一位 i++,j++
*next数组存储的数据: next[i]:从0~i位置的字符组成的字符串 的 公共前缀和后缀的最大长度
*就是前缀和后缀集合的交集中最长元素的长度
*/
public static int[] getNextArray(String subString)
{
//非空判断
if(subString==null)
{
return null;
}
//获取字符串数组 方便比较两字符是否形同
char[] subStr=subString.toCharArray();
//定义子字符串的next数组 并将所有位置的公共前后缀长度设置为0
int[] next=new int[subStr.length];
//初始化next[0]=0;//只有一个字符时 没有公共的前后缀
//前后两个指针指明需要进行比较的位置
//从原数组第二位开始比较
int preIndex=1,sufIndex=0;//pre不回头
while(preIndex<subStr.length)//没有比较完成时
{
if(subStr[preIndex]==subStr[sufIndex])//两字符相等
{
next[preIndex]=next[preIndex-1]+1;//当前位置公共前后缀长度等于前一位置公共前后缀长度加1
//两个指针均后移
preIndex++;
sufIndex++;
}
else{//当前比较两字符不相等的时候 ababc
if(sufIndex==0)//如果子串回溯到起始位置 ababc
{
//公共前后缀长度为零
next[preIndex]=0;
//源串后移一位 ababc
preIndex++; // ababc
}
else {//子串没有回溯到起始位置
//先将子串回溯
sufIndex=0;
}
}
}
return next;
}
KMP算法实现子串判断
经过上述分析,我们知道了next数组的求法,也知道了next数组如何使用,接下来利用next数组实现字符串子串的判断。
/**************************************
* 判断一个字符串是否是另一个字符串的子字符串----KMP算法
* @param oristring 源字符串
* @param substring 孩子字符串
* @return 源字符串中是否含有子字符串
*/
public static boolean hasSubStringKMP(String oriString,String subString)
{
//非空判断
if(oriString==null||subString==null)
{
//有一者为空时 均不视为子字符串
return false;
}
int[] next=getNextArray(subString);//获取子串的next数组
//将串转化为字符数组处理
char[] oriStr=oriString.toCharArray();
char[] subStr=subString.toCharArray();
int oriIndex=0;//源串遍历下标
int subIndex=0;//子串遍历下标
//循环遍历源串
while((oriIndex<oriStr.length)&&(subIndex<subStr.length))
{
//比较字符相等
if(oriStr[oriIndex]==subStr[subIndex])
{
oriIndex++;
subIndex++;
}
else if(subIndex==0)//两字符不相等 且子串在初始位置(当前字符之前没有和源串匹配的字符)
{
//源串后移一位
oriIndex++;
}
else
{
//如果有已经匹配过的串 则j回到next[j-1]位置
//否则 j=0
subIndex=subIndex-1>0?next[subIndex-1]:0;
}
}
if(subIndex==subStr.length)
{
return true;
}
return false;
}