【问题标题】:Adapting Boyer-Moore Implementation调整 Boyer-Moore 实施
【发布时间】:2012-10-03 06:10:06
【问题描述】:

我正在尝试调整 Boyer-Moore c(++) Wikipedia implementation 以获取字符串中模式的所有匹配项。实际上,Wikipedia 实现返回第一个匹配项。主要代码如下:

char* boyer_moore (uint8_t *string, uint32_t stringlen, uint8_t *pat, uint32_t patlen) {
    int i;
    int delta1[ALPHABET_LEN];
    int *delta2 = malloc(patlen * sizeof(int));
    make_delta1(delta1, pat, patlen);
    make_delta2(delta2, pat, patlen);

    i = patlen-1;
    while (i < stringlen) {
        int j = patlen-1;
        while (j >= 0 && (string[i] == pat[j])) {
            --i;
            --j;
        }
        if (j < 0) {
            free(delta2);
            return (string + i+1);
        }

        i += max(delta1[string[i]], delta2[j]);
    }
    free(delta2);
    return NULL;
}

我试图修改if (j &lt; 0) 之后的块以将索引添加到数组/向量并让外循环继续,但它似乎没有工作。在测试修改后的代码时,我仍然只得到一个匹配项。也许这个实现并不是为了返回所有匹配项而设计的,而且它需要不止一些快速更改才能做到这一点?我不太了解算法本身,所以我不确定如何使这项工作。如果有人能指出我正确的方向,我将不胜感激。

注意:函数 make_delta1 和 make_delta2 在源代码的前面定义(查看维基百科页面),而 max() 函数调用实际上也是在源代码前面定义的宏。

【问题讨论】:

    标签: c++ c algorithm implementation boyer-moore


    【解决方案1】:

    Boyer-Moore 的算法利用了这样一个事实,即在较长的字符串中搜索“HELLO WORLD”时,您在给定位置找到的字母会限制在该位置周围可以找到的内容,如果要在总而言之,有点像海战游戏:如果您在距离边界的四个单元格处发现公海,则无需测试剩余的四个单元格,以防有 5 个单元格的航母隐藏在那里;不可能。

    如果您在第十一个位置找到例如“D”,它可能是 HELLO WORLD 的最后一个字母;但是如果你发现一个'Q','Q'不在HELLO WORLD中的任何地方,这意味着搜索的字符串不能在前11个字符中的任何地方,你可以完全避免在那里搜索。另一方面,“L”可能意味着 HELLO WORLD 存在,从位置 11-3(HELLO WORLD 的第三个字母是 L)、11-4 或 11-10 开始。

    在搜索时,您可以使用两个 delta 数组来跟踪这些可能性。

    所以当你找到一个模式时,你应该这样做,

    if (j < 0)
    {
        // Found a pattern from position i+1 to i+1+patlen
        // Add vector or whatever is needed; check we don't overflow it.
        if (index_size+1 >= index_counter)
        {
            index[index_counter] = 0;
            return index_size;
        }
        index[index_counter++] = i+1;
    
        // Reinitialize j to restart search
        j = patlen-1;
    
        // Reinitialize i to start at i+1+patlen
        i += patlen +1; // (not completely sure of that +1)
    
        // Do not free delta2
        // free(delta2);
    
        // Continue loop without altering i again
        continue;
    }
    i += max(delta1[string[i]], delta2[j]);
    }
    free(delta2);
    index[index_counter] = 0;
    return index_counter;
    

    这应该返回一个以零结尾的索引列表,前提是您将 size_t *indexes 之类的内容传递给函数。

    然后该函数将返回 0(未找到)、index_size(匹配太多)或 1 和 index_size-1 之间的匹配数。

    这允许例如添加额外的匹配项,而不必重复整个搜索已找到的 (index_size-1) 子字符串;您将num_indexes 增加new_num,realloc indexes 数组,然后将偏移量old_index_size-1 处的新数组传递给函数,new_num 作为新大小,以及从索引处匹配的偏移量开始的干草堆字符串@ 987654327@ 加上 一个不是,正如我在之前的修订中所写,加上针线的长度;见评论)。

    这种方法也会报告重叠匹配,例如在 banana 中搜索 ana 会找到 b*ana*na 和 ban*ana*.

    更新

    我测试了上面的,它似乎工作。我通过添加这两个包含来修改 Wikipedia 代码以防止 gcc 抱怨

    #include <stdio.h>
    #include <string.h>
    

    然后我修改了if (j &lt; 0) 以简单地输出它找到的内容

        if (j < 0) {
                printf("Found %s at offset %d: %s\n", pat, i+1, string+i+1);
                //free(delta2);
                // return (string + i+1);
                i += patlen + 1;
                j = patlen - 1;
                continue;
        }
    

    最后我用这个测试了

    int main(void)
    {
        char *s = "This is a string in which I am going to look for a string I will string along";
        char *p = "string";
        boyer_moore(s, strlen(s), p, strlen(p));
        return 0;
    }
    

    如预期的那样得到了:

    Found string at offset 10: string in which I am going to look for a string I will string along
    Found string at offset 51: string I will string along
    Found string at offset 65: string along
    

    如果字符串包含两个重叠的序列,则找到两个:

    char *s = "This is an andean andeandean andean trouble";
    char *p = "andean";
    
    Found andean at offset 11: andean andeandean andean trouble
    Found andean at offset 18: andeandean andean trouble
    Found andean at offset 22: andean andean trouble
    Found andean at offset 29: andean trouble
    

    为避免重叠匹配,最快的方法是不存储重叠。它可以在函数中完成,但这意味着重新初始化第一个增量向量并更新字符串指针;我们还需要将第二个i 索引存储为i2,以防止保存的索引变得非单调。这不值得。更好:

        if (j < 0) {
            // We have found a patlen match at i+1
            // Is it an overlap?
            if (index && (indexes[index] + patlen < i+1))
            {
                // Yes, it is. So we don't store it.
    
    
                // We could store the last of several overlaps
                // It's not exactly trivial, though:
                // searching 'anana' in 'Bananananana'
                // finds FOUR matches, and the fourth is NOT overlapped
                // with the first. So in case of overlap, if we want to keep
                // the LAST of the bunch, we must save info somewhere else,
                // say last_conflicting_overlap, and check twice.
                // Then again, the third match (which is the last to overlap
                // with the first) would overlap with the fourth.
    
                // So the "return as many non overlapping matches as possible"
                // is actually accomplished by doing NOTHING in this branch of the IF.
            }
            else
            {
                // Not an overlap, so store it.
                indexes[++index] = i+1;
                if (index == max_indexes) // Too many matches already found?
                    break; // Stop searching and return found so far
            }
            // Adapt i and j to keep searching
            i += patlen + 1;
            j = patlen - 1;
            continue;
        }
    

    【讨论】:

    • 感谢您发布此内容,我将尝试将其添加到代码中,看看效果如何。
    • 我添加了您编写的代码,但它似乎仍然停留在单个匹配项上。明天我得再去看看。我在想也许需要对i 变量进行某种对齐?我仍然没有完全掌握算法,但我可以看到可能需要对表格进行调整或其他什么。
    • 我已经验证了算法并对维基百科代码进行了简单的修改(现在将其添加到我的答案中)
    • 然而,有一个不一致的地方。如果找到太多匹配并且我的索引变体返回,则必须从最后一个匹配的偏移量plus one 重新开始搜索,而不是像我写的那样plus the length of the needle string。否则,如果该匹配被重叠,则不会检测到该重叠;不同的索引大小会产生略有不同的结果。
    • 我确实让它工作了,不知道我之前做错了什么,但它现在肯定工作了。只是为了澄清一下,如果我确实使用了原来的代码,那会防止重叠匹配吗?还是仅在溢出的情况下?
    猜你喜欢
    • 2014-06-29
    • 2011-06-21
    • 1970-01-01
    • 1970-01-01
    • 2016-04-13
    • 1970-01-01
    • 1970-01-01
    • 2020-07-17
    • 1970-01-01
    相关资源
    最近更新 更多