中文分词一直都是中文自然语言处理领域的基础研究。目前,网络上流行的很多中文分词软件都可以在付出较少的代价的同时,具备较高的正确率。而且不少中文分词软件支持Lucene扩展。但不管实现如何,目前而言的分词系统绝大多数都是基于中文词典的匹配算法。
在这里我想介绍一下中文分词的一个最基础算法:最大匹配算法 (Maximum Matching,以下简称MM算法) 。MM算法有两种:一种正向最大匹配,一种逆向最大匹配。
● 算法思想
正向最大匹配算法:从左到右将待分词文本中的几个连续字符与词表匹配,如果匹配上,则切分出一个词。但这里有一个问题:要做到最大匹配,并不是第一次匹配到就可以切分的 。我们来举个例子:
待分词文本: content[]={"中","华","民","族","从","此","站","起","来","了","。"}
词表: dict[]={"中华", "中华民族" , "从此","站起来"}
(1) 从content[1]开始,当扫描到content[2]的时候,发现"中华"已经在词表dict[]中了。但还不能切分出来,因为我们不知道后面的词语能不能组成更长的词(最大匹配)。
(2) 继续扫描content[3],发现"中华民"并不是dict[]中的词。但是我们还不能确定是否前面找到的"中华"已经是最大的词了。因为"中华民"是dict[2]的前缀。
(3) 扫描content[4],发现"中华民族"是dict[]中的词。继续扫描下去:
(4) 当扫描content[5]的时候,发现"中华民族从"并不是词表中的词,也不是词的前缀。因此可以切分出前面最大的词——"中华民族"。
由此可见,最大匹配出的词必须保证下一个扫描不是词表中的词或词的前缀才可以结束。
● 算法实现
使用递归
import java.util.LinkedList;
import java.util.Vector;
import org.junit.Test;
/**
* 正向最大匹配算法:从左到右将待分词文本中的几个连续字符与词表匹配,如果匹配上,则切分出一个词。但这里有一个问题:要做到最大匹配,
* 并不是第一次匹配到就可以切分的 。我们来举个例子:
*
* @author Micropoint
*
*/
public class Main {
/**
* 待分词文本: content[]={"中","华","民","族","从","此","站","起","来","了","。"} 词表:
* dict[]={"中华", "中华民族" , "从此","站起来"}
*
* @param args
*/
public static void main(String[] args) {
String sentence = "中华民族从此站起来了。";
String dict[] = { "中华", "中华民族", "从此", "站起来" };
Vector<String> words = splitToWords(sentence, dict);
System.out.println(words);
}
/**
* 根据词典,将句子划分为词语
*
* @param sentence
* @param dict
* @return
*/
private static Vector<String> splitToWords(String sentence, String[] dict) {
Vector<String> words = new Vector<String>();
// 词
LinkedList<String> originWords = new LinkedList<String>();
for (int i = 0, len = sentence.length(); i < len; i++) {
originWords.addLast(sentence.substring(i, i + 1));
}
// System.out.println(originWords);
StringBuilder sb = null;
String temp = null;
int oldCode = 0;
while (!originWords.isEmpty()) {
// System.out.println(words);
if (sb == null) {
sb = new StringBuilder();
}
if (sb.length() == 0) {
sb.append(originWords.removeFirst());
}
if (sb.length() > 0) {
temp = sb.toString();
int code = IsWords(temp, dict);
// 加入old Code
switch (code) {
case 0:
// 直接加入
if (oldCode == 3) {
// 去掉一个
int len = temp.length();
words.add(temp.substring(0, len - 1));
sb = new StringBuilder();
originWords.addFirst(temp.substring(len - 1));
} else {
// 直接加入
words.add(temp);
sb = new StringBuilder();
}
break;
case 1:
// 直接加入
words.add(temp);
sb = new StringBuilder();
break;
case 2:
// 加入一个词
sb.append(originWords.removeFirst());
break;
case 3:
// 加入一个词
sb.append(originWords.removeFirst());
break;
}
oldCode = code;
}
}
return words;
}
/**
*
* @param str
* @param dict
* @return code 0代表不在字典中 1 代表与字典某个匹配 2 代表是字典中某个的头 3 代表代表与字典中某个匹配并且是某个的头
*/
private static int IsWords(String str, String[] dict) {
int code1 = 0;
int code2 = 0;
for (String d : dict) {
if (d.equals(str)) {
code1 = 1;
}
if (d.startsWith(str) && (str.length() < d.length())) {
code2 = 2;
}
}
return code1 + code2;
}
}