惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
大猫的无限游戏
大猫的无限游戏
B
Blog
阮一峰的网络日志
阮一峰的网络日志
IT之家
IT之家
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
Jina AI
Jina AI
博客园 - 聂微东
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
L
LangChain Blog
M
MIT News - Artificial intelligence
Blog — PlanetScale
Blog — PlanetScale
腾讯CDC
酷 壳 – CoolShell
酷 壳 – CoolShell
Y
Y Combinator Blog
F
Fortinet All Blogs
H
Help Net Security
B
Blog RSS Feed
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Apple Machine Learning Research
Apple Machine Learning Research
S
SegmentFault 最新的问题

博客园 - 来自海边的一片云

Binary Tree Maximum Path Sum 解题注意 CodingTMD’s Reading List De Bruijn 序列生成 Word Ladder I ,II 解题思路 suduko及8皇后问题及相关问题的解题思路 leetcode Word Break II 解题思路 内存管理 Permutations leetcode Clone Graph leetcode 开弓没有回头箭 combination sum leetcode Combinations leetcode 组合问题 word break leetcode LRU cache Leetcode 重新试着写blog SQL injection Fuzz testing XML库的解析效率 Init()
Search for a string in an infinite stream of input string.
来自海边的一片云 · 2014-02-15 · via 博客园 - 来自海边的一片云

问题,read()从stream中读出一个字符。

然后在stream 中找到含有目标字符串的位置。

这个题目是个典型的KMP算法。

KMP算法的关键在于如果build Next数组

讲KMP算法讲的比较好的网站 

http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

其实用平实的逻辑思考也能想明白这个算法究竟是怎么来。

string S1 abcd......

string S2 Ai.....An

1. 当拿S2和S1去匹配的时候,当在S2的第K位置发生不匹配的时候,最最naive的方法就是直接跳回到第二个字符再比。注意至少S2中,0~k-1位置上的内容和前面是匹配的。

2. 当字符中出现重复的时候,特别是后缀和前缀有匹配的时候, 比如在ABCDABD ‘D’出现不匹配的时候,但是在不匹配的位置之前都是匹配的。

     那么以其字符串移到'B'CDAB B处进行匹配

     还不如直接看看发生错配处前面的后缀,与字符串的前缀的重合长度,如果重合长度为1,那就直接从第二位比起吧。

3. 那就对要搜索的字符串,先假设在各个位置发生不匹配的时候最长匹配的字符串长度记到表格里。next

4. 那下次发生不匹配的时候直接滑动到那个位置就可以了。

拿下面这个例子来说,在不匹配的时候 next[B]的位置是2, 那就直接从第三个字符开始比就行了。

这个就是整个KMP算法的思路和一般暴力搜索的区别。而且这个思路可以扩展到2维,比如图像匹配这种问题。

那如何来建这个表呢?

根据前面的描述,最笨的办法就是,直接到一个位置去试前缀和后缀的长度,然后去检验。。。。

但是呢,这个可以用一维动态规划来做,这也是为啥,这个next这么难搞了。

next[0]= 0

递推式

next[j] =next[j-1]+1 if (A[next[j-1] == A[j]) 

or next[j]=0 (if(A[next[j-1] != A[j] ) 1(if(A[0] == A[j])

void buildTable(char *x, int length, int* next) {
    int i, j;
    next[0] = 0;
    for (int i = 1, j = 0; i<length; i++)
    {
        while (j>0 && x[i] != x[j])
        {
            j = next[j - 1];
        }

        if (x[i] == x[j])
        {
            j++;
        }

        next[i] = j;
    }
}

改成这样,就容易理解多了

void buildtable(char *x, int length, int* next)
{
    next[0] = 0;
    for (int j = 1; j < length; j++)
    {

        if (x[next[j - 1]] == x[j])
        {
            next[j] = next[j - 1] + 1;
        }
        else
        {
            if (x[0] == x[j])
            {
                next[j] = 1;
            }
            else
            next[j] = 0;
        }
    }
}

这个从stream里面读要比直接比两个字符串更加可恶,因为你要考虑好啥时候该读,在什么地方读。。

一种是一直在首字符串不匹配的情况下,那就需要读,

一种是前面一次比较是匹配的情况下,那就需要读。其他的好理解了。

int find(char* str)
{
    int len = strlen(str);
    int counter = 0;
    char ch;
    if (str == NULL) return -1;

    int *next = (int*)malloc(strlen(str));
    buildTable(str, strlen(str), next);
    bool found = false;
    bool stillmatch = true;
    int s_index = 0;
    while (!found)
    {
        if (stillmatch ||s_index == 0 ) { 
            ch = read();
            counter++;
        }
       if (str[s_index] == ch) {//read char match 
           stillmatch = true;   //need read from stream
           s_index++;           // read compare next char
           if (s_index == len){ //end of search string 
               found = true;
               break;
           }           
       }
       else //not match
       {
           stillmatch = false;
           if (s_index > 0) //we already found some matches
           {
               s_index = next[s_index - 1]; //backtrack
           }           
       }
    }

    if (next != NULL) free(next);

    if (found) {
        return counter - len;
    }
    else {
        return -1;
    }
}

另外一种实现,很是巧妙但是可读性。。。。

public static int[] next;

public static boolean kmp(String str, String dest) {
  // i stands for index of str string, j stands for index in dest string.
  // At the beginning of each loop process, j is the new position of dest
  // taht should be compared.
  for (int i = 0, j = 0; i < str.length(); i++) {
      while (j > 0 && str.charAt(i) != dest.charAt(j))
          // 遇到不匹配的情况,就回退到nextval 标定的值
          j = next[j - 1];
      if (str.charAt(i) == dest.charAt(j))
          j++;
//匹配,继续往前搜
if (j == dest.length()) return true; } return false; } public static int[] kmpNext(String str) { int[] next = new int[str.length()]; next[0] = 0; // i stands for index of string, j is temporary for particail match // values computing, at the beginning of each loop process, j is the // particial match value of former character . for (int i = 1, j = 0; i < str.length(); ++i) { while (j > 0 && str.charAt(i) != str.charAt(j)) j = next[j - 1];// j will be recomputed in the recursion. Take // care that next[j-1] is the particial match // value of the first j characters substirng. if (str.charAt(i) == str.charAt(j)) // If not in this case, j must // meets end, equals to zero. ++j; next[i] = j; } return next; }

总结:

KMP算法

1. 了解暴力解法怎么解,无论是内存随意读,还是从流读。

2. 理解为啥需要建这个next表,这个next查找表的每个位置的值究竟啥意义。这个思路如何拓展到二维。

3. 如果建这个表。一维动态规划

4. 如果从流读,什么条件的情况下需要读。