一、问题来源
用C语言编程实现在文本文件Android.log中搜索字符串,搜索“CameraService::connect”与“logicalCameraId: 5, cameraId: 5”,并打印出来包含字符串的那一行内容。
字符串搜索是我们经常会遇到的问题,特别是在log中某些信息定位中起着重要作用。如何在海量的文本数据中,找到我们想要的信息,对搜索算法的速度和精度都有着较高的要求。
二、算法选择
字符串搜索算法有很多种,例如:KMP算法、正则匹配、暴力查找、Rabin-Karp算法、Sunday算法、BF算法等。
各个字符串搜索算法原理详解与实现参考网址:link
本文采用了KMP(Knut Morris Pratt)算法,KMP算法是一种线性时间复杂度的字符串匹配算法,它是对BF(Brute-Force,最基本的字符串匹配算法)的改进。kmp算法主要是减少字符串查找过程中的回退,尽可能减少不用的操作,算法复杂度是O(n+m)。
KMP算法的整体思路是什么样子呢?让我们来看一组例子:
KMP算法和BF算法的“开局”是一样的,同样是把主串和模式串的首位对齐,从左到右对逐个字符进行比较。
第一轮,模式串和主串的第一个等长子串比较,发现前5个字符都是匹配的,第6个字符不匹配,是一个“坏字符”:
这时候,如何有效利用已匹配的前缀 “GTGTG” 呢?
我们可以发现,在前缀“GTGTG”当中,后三个字符“GTG”和前三位字符“GTG”是相同的:
在下一轮的比较时,只有把这两个相同的片段对齐,才有可能出现匹配。这两个字符串片段,分别叫做最长可匹配后缀子串和最长可匹配前缀子串。
第二轮,我们直接把模式串向后移动两位,让两个“GTG”对齐,继续从刚才主串的坏字符A开始进行比较:
显然,主串的字符A仍然是坏字符,这时候的匹配前缀缩短成了GTG:
按照第一轮的思路,我们来重新确定最长可匹配后缀子串和最长可匹配前缀子串:
第三轮,我们再次把模式串向后移动两位,让两个“G”对齐,继续从刚才主串的坏字符A开始进行比较:
以上就是KMP算法的整体思路:在已匹配的前缀当中寻找到最长可匹配后缀子串和最长可匹配前缀子串,在下一轮直接把两者对齐,从而实现模式串的快速移动。
引用文章链接:link
三、程序实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int KMP(char s[],char t[]);
void Getnext(int next[],char t[]);
int main(int argc, char *argv[])
{
//Read the file
FILE *fp = fopen("/root/program/Android.log","r");
if(fp == NULL)
{
printf("Open error!");
return 0;
}
//Read the string that the user wants to look up.
printf("Please enter the number of strings of you want to find: \n");
int x ;
scanf("%d",&x);
char str[x][100];
for (int i = 0; i < x; i ++)
{
printf("Please enter a string: \n");
scanf("%s",&str[i]);
}
//Assigns the read to s and outouts it
char s[1000];
int m = 1; //row
while(fgets(s,sizeof(s),fp))
{
//char t[] = "CameraService::connect";
for (int i = 0; i < x; i ++)
{
int n = 0; //column
n = KMP(s,str[i]);
if (n != -1)
{
printf("%s is in the %d row %d column \n",str[i],m,n);
printf("%s \n",s);
}
}
m++; //row +1
}
fclose(fp); //close file
return 0;
}
/*
KMP算法
关键在于next,回退位置
*/
int KMP(char s[],char t[])
{
int i=0,j=0;
int slen = strlen(s);
int tlen = strlen(t);
int next[tlen]; //next数组长度与字符串t等长?
Getnext(next,t);
while(i < slen && j < tlen)
{
if(j == -1 || s[i] == t[j])
{
i++;
j++;
}
else
{
j = next[j]; // j回退
}
}
if (j == tlen)
return i - j; //匹配成功,返回子串位置
else
return (-1); //匹配失败
}
void Getnext(int next[],char t[])
{
int j = 0,k = -1;
next[0] = -1;
int tlen =strlen(t);
while(j < tlen -1)
{
if (k == -1 || t[j] == t[k])
{
j++;
k++;
if(t[j] == t[k]) //当两个字符相同时,就跳过
next[j] = next[k];
else
next[j] = k;
}
else
{
k = next[k];
}
}
}