您的位置:首页 > 其它

KMP算法的具体实现

2016-03-29 21:27 411 查看
我打算重新开始学习数据结构C语言版。今天看到的KMP算法。硬是把我绕进去了。主要是书上给的代码短小精悍,硬是看不懂怎么回事。

KMP算法的主要思想是即是利用部分匹配的思想,当匹配不同的时候,两个指针不用回溯到初始位置,对于主串中的指针就是不用回溯到开始匹配模式串的位置,对于模式串的指针就是不用回到模式串的头位置。主串中的指针无需回溯,而模式串中的指针则是根据相应next函数值,回溯到某个位置即可,这样算法时间复杂度就可以降低为O(n+m)。

核心部分就是求出模式串中的next函数值。

下例中数组均以 1 开始。

对于next[i] = j 来说,则说明 模式串中的前 ( j - 1 )个字符 和 从( i - 1)开始往前( j - 1 )个字符相匹配。定义next[1] = 0;此时

1. 若 t[i] = t[j] ,则说明next[ i + 1] = next[ i ] + 1。前 j 个字符都匹配。

2. 若 t[i] != t[j] ,则说明此时 对于第 ( i + 1) 个字符(t[i+1])来说,前面的 第 j 个字符匹配不成功。

此时应该缩小匹配长度,即令 j = next [ j ],如果此时相等的话,此时next [ i + 1] = next [ j ] + 1。如果不相等就进入循环。如果到最后都不相等则令next [ i + 1 ] = 1。(即回到模式串的头位置)

//-----本书中的字符串数组的第一个为数组长度 如下 T的长度为 T[0];
//-----以下为类C语言的算法描述
void get_next(SString T,int next[]){
i =1; next[1] = 0; j = 0;
while(i < T(0)){
if(j == 0 || T[i] == T[j]){++i;++j; next[i] = j;}
else j = next[j];
}


实在不知所云。于是我要先自己实现一遍,即便代码很丑…,再细细琢磨才行。

//此处是C语言实现的。代码可以运行。
// int nt 为模式串的长度,char t[] 为模式串。

void get_next(char t[],int nt,int next[]){
int i = 1;//计数器:为当前匹配字母位置。( 0 开头的数组 )
next[0] = -1;
next[1] = 0;
int temp = next[i];
for(;i < nt;){
printf("next[% d]= %d\n",i,next[i]);
if(t[i] == t[next[i]]){
next[i+1] = next[i] + 1;
i++;
temp = next[i];
printf("t[j]=t[k]:直接加一:t[i] + 1\n");

}else if(t[i] == t[temp]){

if( temp == 0){

printf("t[j] = t[0]:当和第一个字母匹配时,从第二个开始匹配:0\n");
next[i + 1] = 1;
}
else{
printf("t[j] = t[next..]:当和某个t[temp]匹配时:next[temp]+1\n");
next[i+1] = next[temp] + 1;
}
i++;
temp = next[i];
}else if(temp < 0){

printf("temp < 0 :当无法匹配时,从第一个开始匹配:0 \n",temp);
next[i+1] = 0;
i++;
temp = next[i];
}else {
printf("进入循环,使得temp=next[temp]\n");

temp = next[temp];

}

}


再来理解一下上面伟大的短代码:

void get_next(SString T,int next[]){
i = 1;//------for i = 1 to Tlength(T(0)-1),计算每个next[i]的值
next[1] = 0;
j = 0;//j用来控制循环,j 始终等于 next[j],当符合条件时候,就 ++i ++j,然后赋值给next[i+1]。
while(i < T[0]){
if(j == 0 || T[i] == T[j]){
++i; ++j; next[i] = j;
//上面一行即等价于 next[i + 1] = j + 1;i++;
//也许会更好理解一点。
}
else j = next[j];
}


下面我再把第一次写的C代码改善一下:

//这里C语言实现,因为在算法中next[1]规定为0。在这里我可以规定 使得 next[0] = -1
//使其字符数组和next[]的计数都可以从0开始。免去一些麻烦。
void get_next(char t[],int nt ,int next[]){
int i = 0,j = -1;
next[0] = -1;
while( i < nt - 1){
if(j == -1 || t[i] == t[j]){
i++;
j++;
next[i] = j;
}
else j = next[j];
}
}


例题:(来自牛客网)

对于两棵彼此独立的二叉树A和B,请编写一个高效算法,检查A中是否存在一棵子树与B树的拓扑结构完全相同。

给定两棵二叉树的头结点A和B,请返回一个bool值,代表A中是否存在一棵同构于B的子树。

思路:即对两个二叉树进行深度优先序列化,然后用KMP算法进行比较是否含有子串。

import java.util.*;

/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}*/
public class IdenticalTree {
public boolean chkIdentical(TreeNode A, TreeNode B) {
// write code here
String stra = toSerializale(A);

String strb = toSerializale(B);

char[] sa = stra.toCharArray();
char[] sb = strb.toCharArray();

return kmp(sa,sb) != -1;
}
public static String toSerializale(TreeNode head){
StringBuilder str = new StringBuilder();
if(head == null) return "#!";

str.append(head.val);
str.append(toSerializale(head.left));
str.append(toSerializale(head.right));

return str.toString();
}
public static int kmp(char[] a,char[] b){

int[] next = new int[b.length];
getNext(b,next);

int i = 0,j = 0;
while(i < a.length && j < b.length){
if(j == -1 || a[i] == b[j]) {i++;j++;}
else j = next[j];
}

if(j >= b.length) return i - b.length;
return -1;
}
public static void getNext(char[] a,int[] next){
int i = 0;
int j = -1;
next[0] = -1;

while(i < a.length - 1){
if(j == -1 || a[i] == a[j]) { i++; j++; next[i] = j;}
else j = next[j];
}

}
}


其实java里面很多算法都已经实现了,所以上述完全可以不用自己写kmp算法 ,而是直接用indexOf即可。如下:

public boolean chkIdentical(TreeNode A, TreeNode B) {
// write code here
String stra = toSerializale(A);

String strb = toSerializale(B);

return stra.indexOf(strb) != -1;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: