您的位置:首页 > 职场人生

面试经典--字符串匹配问题

2013-05-06 21:45 417 查看
字符串匹配问题描述:设有两个字符串A和B,A字符串长度为N,B字符串长度为M,现在要确定B字符串是否出现在A字符串中,如果是,返回位置,如果不是返回结果为false,在这里,我们成字符串B为模式字符串,A为主串。

最近经常会被问到字符串匹配的问题,实际上这些问题在网上都有很多资源,我觉得没有必要自己写一个,但是看了之后可能过段时间又不是很清楚了,因此在这里做个记录,记录那些让我感觉讲得比较清楚的主页。

1. KMP算法

KMP算法是O(M+N)的算法,KMP算法的思想很简单,那就是要利用之间比较的结果,其中有贪心算法和回溯算法的影子。此算法需要对模式字符串B进行预处理,得到一个长度与B相同的Partial数组(The Partial Match Table

讲得比较明白浅显易懂的是:阮一峰大神 只是一个简单的介绍
讲得比较简洁清晰的是:matrix67大神  讲得很好,用C++实现了一下这个算法:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
#include <iostream>
#include <vector>
#include <string>
#include <iterator>
using namespace std;

/*预处理的过程有人称之为求next数组,有人称之为求partial table
需要注意的是,next数组的长度不能是本身的长度,也就是说"aa"的next数组长度为1而不是2
不要问为什么,这个是由KMP算法的需求决定的
*/
vector<int>  preprocessing(string & sub)
{

vector<int> ret;
if (sub.empty())
{
return ret;
}
for(int i=0;i<sub.length();++i)
{
ret.push_back(0);  //初始有两个目的:1 分配空间 2 .初始化P[0]=0
}
for(int forward=1;forward<sub.length();++forward)
{
int backward=forward-1;
while(backward>=0)
{
if(sub[forward]==sub[ret[backward]])
{
ret[forward]=ret[backward]+1;break;
}
else
{
backward=ret[backward]-1;
}
}
if(backward<0){ret[forward]=0;}
}

return ret;
}

bool isSubString(string & master ,string &sub,int &index)
{
if(master.length()<sub.length())
{
index=-1;
return false;
}
vector<int> P=preprocessing(sub);
vector<int>::iterator int_iter;
for(int_iter=P.begin();int_iter!=P.end();++int_iter)
{
cout<<*int_iter;
}
cout<<endl;
int i=0;
int j=0;
for(;i<master.length();)
{
cout<<j<<" "<<i<<endl;
if(master[i]==sub[j])
{
j++;i++;
if(sub.length()==j){ index=i-sub.length();return true;}
}
else
{
j=P[j-1];
}
}

index=-1;
return false;
}

int main()
{
string A="abababaababacbaa";
string B="ababacbad";
int loc=0;

cout<<"B "<< (isSubString(A,B,loc)?"is":"is not ") <<"a subtring of A";

return 1;
}


讲得看来比较系统的是:v_JULY_v ,这一篇我没来得及看,但是感觉排版稍微有些混乱



2. BM算法


通常情况下,BM算法要比KMP算法快3到5倍(好吧,这是别人总结的),这个在模式串越长的时候相对于KMP的优势越明显,维基百科上说,它的效率来自于这样的事实:对于每一次失败的匹配尝试,算法都能够使用这些信息来排除尽可能多的无法匹配的位置。由于每一次算法都给予好字符规则和坏字符规则确定最大的移位距离,所以虽然Boyer-Moore算法的执行时间同样线性依赖于被搜索字符串的大小,但是通常仅为其它算法的一小部分。但是目前为止我还无法描述算法复杂性。原始的BM算法最坏情况下的时间复杂度是O(MN),改进的BM算法改进的一般是预处理求好后缀规则和坏字符规则的方法。BM算法还有很多值得研究的地方,现在只是做个大概的总结,要研究透彻得看一看下面的第三个链接。

同样讲得比较浅显易懂的是:阮一峰大神 只是一个简单的介绍

当然:v_JULY_v 也是有的

南柯一喵--字符串匹配BM算法学习心得  这个里面附带了一份BM代码,用KMP做预处理



3.brute force


蛮力算法,适合比较小规模的字符串匹配



4.Rabin-Karp


RK方法的设计思想也是利用前面已经获得的信息,该算法的思想是,通过对模式字符串进行hash运算,同时对源字符串取长度跟模式字符串相等的子字符串也进行hash运算,最后比较hash值来确定模式字符串是否和源字符串的子串匹配,并获得其匹配起始位置。问题的关键就在于hash算法的设计,这个链接里面是个比较易懂的描述。



 

以上四种方法基本上概括了主流的字符串匹配算法,借用Robert Sedgewick 的Algorithm 一书中的一幅图总结一下,





 

扩展:后缀树/后缀数组


老实说,后缀树v_JULY_v也讲过,不过个人觉得字符串处理过程总后缀数组才是个神器,而且也能够解决字符串匹配的问题,时间复杂度为O(M+logN)



老实说,这都不太适合作为一篇博文发出,但是我今天看了的这些东西也不能够浪费,权且给下次看留点资源吧,等过段时间闲下来,我想写个字符串处理的专题,加油!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: