在《字符串匹配算法研究(一)》一文中主要介绍了多串匹配问题和解决单串匹配问题的KMP算法,接下来我们可以利用其思想解决多串匹配问题。 不过在此之前需要引入单词查找树的概念。
单词查找树是一棵无限延伸的26叉树,每个结点的26个通向子结点的边分别被命名为a,b,...,z;(之前曾提到过,将原文和模式串假设成仅由26个小写英文字母组成)。这样从根结点到任一非根结点p路径上的字母均可组成一字符串Sp,且任一字符串均可在单词树中找到唯一一条从根结点出发,与之对应的路径。
对于模式串单词树的建立我们一般先建立一棵空树,再将模式串按照单词查找树的方式一个个插入单词树中。不过光有模式串单词树并不能解决多串匹配问题。因此我们还需要引入单词前缀树的概念。
单词前缀树首先是一棵单词查找树,但其每一非根结点都有一个前缀指针。对于树中结点p的前缀指针prefix定义如下:p->prefix=n|n in tree and Sn=Sp.subString(min{k},Sp.length),其中整数k∈[1,Sp.length)。
前缀指针的建立方法可仿照kmp算法中前缀函数的建立方法:根据父结点的前缀指针找到当前结点的前缀指针。下面给出用单词前缀树实现多串匹配算法的Java代码:
package
algorithm;

import
java.util.Iterator;
import
java.util.LinkedList;
import
java.util.List;


/** */
/**
* 单词前缀树类
* 包含用单词前缀树解决多串匹配问题的算法
* @author liuxy
*
*/

public
class
CharPrefixTree
...
{
//该结点的26个子结点
private CharPrefixTree[] children = new CharPrefixTree[26];
//用一List动态存储子结点以便在遍历时减少循环次数
private List childrenList = new LinkedList();
//该结点的父指针
private CharPrefixTree father = null;
//该结点的前缀指针
private CharPrefixTree prefix = null;
//标记该结点是否为模式串的终点,若是则将模式串的引用赋给它
private LinkedList Okey = null;
//父结点到该结点边上的字符
private char value;

/** *//**
* 得到字符串s中第i个字母在孩子数组中的对应下标
*/

private static int getIndex(String s, int i)...{
return s.charAt(i) - 'a';
}

/** *//**
* 得到该结点字符对应孩子数组的下标
* @return
*/

private int getIndex()...{
return value - 'a';
}

/** *//**
* 给一课单词树添加前缀指针
* 算法:采用广度优先算法遍历树中结点并调用setPrefix设置其前缀指针,
* 保证给当前结点设置前缀指针时比它深度小的结点的前缀指针都已设置完毕
*/

private void generatePrefix()...{
LinkedList l = new LinkedList();
l.addLast(this);

while(l.size() > 0)...{
CharPrefixTree p = (CharPrefixTree)l.removeFirst();
Iterator iter = p.childrenList.iterator();

while(iter.hasNext())...{
CharPrefixTree child = (CharPrefixTree)iter.next();
child.setPrefix();
l.addLast(child);
}
}
}

/** *//**
* 设置当前结点的前缀指针
* 算法:假设当前结点为p
* q<-p->father->prefix
* index<-此时q的父亲到q边上字母对应的下标
* While q!=Root and q->children[index]=null Do
* q<-q.prefix
* If q->children[index]!=null Then p->prefix=q->children[index]
*
*/

private void setPrefix()...{
CharPrefixTree q = this.father.prefix;
int index = getIndex();
if(q == null)return;

while(q.father != null && q.children[index] == null)...{
q = q.prefix;
}

if(q.children[index] != null)...{
this.prefix = q.children[index];

if(this.prefix.Okey != null && this.prefix.Okey.size() > 0)...{
if(this.Okey == null)this.Okey = new LinkedList();
this.Okey.addAll(this.prefix.Okey);
}
}
}

/** *//**
* 在单词树中插入单词
* @param s:需要插入的单词
*/

private void insertString(String s)...{
CharPrefixTree p = this;

for(int i = 0; i < s.length(); ++i)...{
int index = getIndex(s,i);

if(p.children[index] == null)...{
p.children[index] = new CharPrefixTree();
p.children[index].father = p;
p.children[index].value = s.charAt(i);
p.children[index].prefix = this;
p.childrenList.add(p.children[index]);
}
p = p.children[index];
}
if(p.Okey == null)p.Okey = new LinkedList();
p.Okey.addLast(s);
System.out.println(p.Okey);
}

/** *//**
* 在单词树中插入单词数组
* @param patterns:需插入的单词数组
*/

private void insertString(String[] patterns)...{
for(int i = 0; i < patterns.length; ++i)
this.insertString(patterns[i]);
}

/** *//**
* 多串匹配主算法
* 算法:
* create CharPrefixTree
* q<-root
* For i<-0 to n-1 Do
* index<-text[i]-'a';
* while q!=root and q->children[index] Do
* q<-q->prefix
* If q->children[index]!=null Then q<-q->children[index];
* If q->Okey Then Display Matching Message
* End For
*
* 构造单词前缀树总时间复杂度为O(Sum{Li|Li=patterns[i].length});
* 多串匹配算法时间复杂度为O(text.length)
* @param patterns:模式串
* @param text:正文
*/

public static void mpMatching(String[] patterns, String text)...{
CharPrefixTree q = new CharPrefixTree();
q.insertString(patterns);
q.generatePrefix();

for(int i = 0; i < text.length(); ++i)...{
int index = getIndex(text,i);
while(q.father != null && q.children[index] == null)
q = q.prefix;
if(q.children[index] != null)q = q.children[index];

if(q.Okey != null && q.Okey.size() > 0)...{
Iterator iter = q.Okey.iterator();

while(iter.hasNext())...{
String pattern = (String)iter.next();
System.out.println("pattern:" + pattern + " position:" + (i - pattern.length() + 1));
}
}
}
}

public static void main(String args[])...{
String text = "zababaabc";

String[] patterns = ...{"b","aba","babaa","abc"};
mpMatching(patterns,text);
}
}