正向最大匹配法
分詞目標:
在詞典中進行掃描,儘可能地選擇與詞典中最長單詞匹配的詞作爲目標分詞,然後進行下一次匹配。
算法流程:
假設詞典中最長的單詞爲 5 個(MAX_LENGTH),那麼最大匹配的起始子串字數也爲 5 個
(1)掃描字典,測試讀入的子串是否在字典中
(2)如果存在,則從輸入中刪除掉該子串,重新按照規則取子串,重複(1)
(3)如果不存在於字典中,則從右向左減少子串長度,重複(1)
分詞實例:
比如說輸入 “北京大學生前來應聘”,
- 第一輪:取子串 “北京大學生”,正向取詞,如果匹配失敗,每次去掉匹配字段最後面的一個字
- “北京大學生”,掃描 5 字詞典,沒有匹配,子串長度減 1 變爲“北京大學”
- “北京大學”,掃描 4 字詞典,有匹配,輸出“北京大學”,輸入變爲“生前來應聘”
- 第二輪:取子串“生前來應聘”
- “生前來應聘”,掃描 5 字詞典,沒有匹配,子串長度減 1 變爲“生前來應”
- “生前來應”,掃描 4 字詞典,沒有匹配,子串長度減 1 變爲“生前來”
- “生前來”,掃描 3 字詞典,沒有匹配,子串長度減 1 變爲“生前”
- “生前”,掃描 2 字詞典,有匹配,輸出“生前”,輸入變爲“來應聘””
- 第三輪:取子串“來應聘”
- “來應聘”,掃描 3 字詞典,沒有匹配,子串長度減 1 變爲“來應”
- “來應”,掃描 2 字詞典,沒有匹配,子串長度減 1 變爲“來”
- 顆粒度最小爲 1,直接輸出“來”,輸入變爲“應聘”
- 第四輪:取子串“應聘”
- “應聘”,掃描 2 字詞典,有匹配,輸出“應聘”,輸入變爲“”
- 輸入長度爲0,掃描終止
正向匹配法最終的切分結果爲:”北京大學 / 生前 / 來 / 應聘”
正向匹配法實現代碼如下:
public List<String> leftMax(String str) {
List<String> results = new ArrayList<String>();
String input = str;
while( input.length() > 0 ) {
String subSeq;
// 每次取小於或者等於最大字典長度的子串進行匹配
if( input.length() < MAX_LENGTH)
subSeq = input;
else
subSeq = input.substring(0, MAX_LENGTH);
while( subSeq.length() > 0 ) {
// 如果字典中含有該子串或者子串顆粒度爲1,子串匹配成功
if( dictionary.contains(subSeq) || subSeq.length() == 1) {
results.add(subSeq);
// 輸入中從前向後去掉已經匹配的子串
input = input.substring(subSeq.length());
break; // 退出循環,進行下一次匹配
} else {
// 去掉匹配字段最後面的一個字
subSeq = subSeq.substring(0, subSeq.length() - 1);
}
}
}
return results;
}
逆向最大匹配法
分詞目標:
在詞典中進行掃描,儘可能地選擇與詞典中最長單詞匹配的詞作爲目標分詞,然後進行下一次匹配。
在實踐中,逆向最大匹配算法性能優於正向最大匹配算法。
算法流程:
假設詞典中最長的單詞爲 5 個(MAX_LENGTH),那麼最大匹配的起始子串字數也爲 5 個
(1)掃描字典,測試讀入的子串是否在字典中
(2)如果存在,則從輸入中刪除掉該子串,重新按照規則取子串,重複(1)
(3)如果不存在於字典中,則從左向右減少子串長度,重複(1)
分詞實例:
比如說輸入 “北京大學生前來應聘”,
- 第一輪:取子串 “生前來應聘”,逆向取詞,如果匹配失敗,每次去掉匹配字段最前面的一個字
- “生前來應聘”,掃描 5 字詞典,沒有匹配,字串長度減 1 變爲“前來應聘”
- “前來應聘”,掃描 4 字詞典,沒有匹配,字串長度減 1 變爲“來應聘”
- “來應聘”,掃描 3 字詞典,沒有匹配,字串長度減 1 變爲“應聘”
- “應聘”,掃描 2 字詞典,有匹配,輸出“應聘”,輸入變爲“大學生前來”
- 第二輪:取子串“大學生前來”
- “大學生前來”,掃描 5 字詞典,沒有匹配,字串長度減 1 變爲“學生前來”
- “學生前來”,掃描 4 字詞典,沒有匹配,字串長度減 1 變爲“生前來”
- “生前來”,掃描 3 字詞典,沒有匹配,字串長度減 1 變爲“前來”
- “前來”,掃描 2 字詞典,有匹配,輸出“前來”,輸入變爲“北京大學生”
- 第三輪:取子串“北京大學生”
- “北京大學生”,掃描 5 字詞典,沒有匹配,字串長度減 1 變爲“京大學生”
- “京大學生”,掃描 4 字詞典,沒有匹配,字串長度減 1 變爲“大學生”
- “大學生”,掃描 3 字詞典,有匹配,輸出“大學生”,輸入變爲“北京”
- 第四輪:取子串“北京”
- “北京”,掃描 2 字詞典,有匹配,輸出“北京”,輸入變爲“”
- 輸入長度爲0,掃描終止
逆向匹配法最終的切分結果爲:”北京/ 大學生/ 前來 / 應聘”
逆向匹配法實現如下:
public List<String> rightMax(String str) {
// 採用堆棧處理結果,後進先出
Stack<String> store=new Stack<String>();
List<String> results = new ArrayList<String>();
String input = str;
while( input.length() > 0 ) {
String subSeq;
// 每次取小於或者等於最大字典長度的子串進行匹配
if( input.length() < MAX_LENGTH)
subSeq = input;
else
subSeq = input.substring(input.length() - MAX_LENGTH);
while( subSeq.length() > 0 ) {
// 如果字典中含有該子串或者子串顆粒度爲1,子串匹配成功
if( dictionary.contains(subSeq) || subSeq.length() == 1) {
store.add(subSeq);
// 輸入中從後向前去掉已經匹配的子串
input = input.substring(0, input.length() - subSeq.length());
break;
} else {
// 去掉匹配字段最前面的一個字
subSeq = subSeq.substring(1);
}
}
}
// 輸出結果
int size = store.size();
for( int i = 0; i < size; i ++) {
results.add(store.pop());
}
return results;
}
雙向最大匹配法
分詞目標:
將正向最大匹配算法和逆向最大匹配算法進行比較,從而確定正確的分詞方法。
算法流程:
- 比較正向最大匹配和逆向最大匹配結果
- 如果分詞數量結果不同,那麼取分詞數量較少的那個
- 如果分詞數量結果相同
- 分詞結果相同,可以返回任何一個
- 分詞結果不同,返回單字數比較少的那個
分詞實例:
就上例來看,
正向匹配最終切分結果爲:北京大學 / 生前 / 來 / 應聘,分詞數量爲 4,單字數爲 1
逆向匹配最終切分結果爲:”北京/ 大學生/ 前來 / 應聘,分詞數量爲 4,單字數爲 0
逆向匹配單字數少,因此返回逆向匹配的結果。
雙向最大匹配法實現如下:
public List<String> segment() {
List<String> fmm = this.leftMax();
List<String> bmm = this.rightMax();
// 如果分詞的結果不同,返回長度較小的
if( fmm.size() != bmm.size()) {
if ( fmm.size() > bmm.size())
return bmm;
else
return bmm;
}
// 如果分詞的詞數相同
else {
int fmmSingle = 0, bmmSingle = 0;
boolean isEqual = true;
for( int i = 0; i < bmm.size(); i ++) {
if( !fmm.get(i).equals(bmm.get(i))) {
isEqual = false;
}
if( fmm.get(i).length() == 1)
fmmSingle ++;
if( bmm.get(i).length() == 1)
bmmSingle ++;
}
// 如果正向、逆向匹配結果完全相等,返回任意結果
if ( isEqual ) {
return fmm;
// 否則,返回單字數少的匹配方式
} else if ( fmmSingle > bmmSingle)
return bmm;
else
return fmm;
}
}
載入字典和自定義添加詞
這裏的字典文件採用的是
http://download.csdn.net/download/yuanlulu/2380141
載入字典和自定義添加詞實現如下:
private static Set<String> dictionary;
// 初始化字典,採用 hashset 存儲
public void getDictionary() {
dictionary = new HashSet<String>();
String dicpath = "data/worddict2.txt";
String line = null;
BufferedReader br;
try {
// 按照 gbk 編碼讀入文件
br = new BufferedReader(new InputStreamReader(new FileInputStream(dicpath),"gbk"));
try {
while(((line = br.readLine())!=null)) {
// 按照空格切分,只讀取第二部分
String[] str = line.split("\\s+");
line = str[1];
dictionary.add(line);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (UnsupportedEncodingException | FileNotFoundException e) {
e.printStackTrace();
}
}
// 自定義添加詞彙
public void addWord(String str) {
dictionary.add(str);
}
歧義句測試
可以看到效果還不錯,最大匹配法的效果還是取決於字典的質量。
整體代碼如下:
package mm;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
public class MMSegment {
private String request;
private int MAX_LENGTH = 5;
private static Set<String> dictionary;
public void getDictionary() {
dictionary = new HashSet<String>();
String dicpath = "data/worddict2.txt";
String line = null;
BufferedReader br;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(dicpath),"gbk"));
try {
while(((line = br.readLine())!=null)) {
String[] str = line.split("\\s+");
line = str[1];
dictionary.add(line);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (UnsupportedEncodingException | FileNotFoundException e) {
e.printStackTrace();
}
}
public void addWord(String str) {
dictionary.add(str);
}
public List<String> leftMax() {
List<String> results = new ArrayList<String>();
String input = request;
while( input.length() > 0 ) {
String subSeq;
if( input.length() < MAX_LENGTH)
subSeq = input;
else
subSeq = input.substring(0, MAX_LENGTH);
while( subSeq.length() > 0 ) {
if( dictionary.contains(subSeq) || subSeq.length() == 1) {
results.add(subSeq);
input = input.substring(subSeq.length());
break;
} else {
subSeq = subSeq.substring(0, subSeq.length() - 1);
}
}
}
return results;
}
public List<String> rightMax() {
Stack<String> store=new Stack<String>();
List<String> results = new ArrayList<String>();
String input = request;
while( input.length() > 0 ) {
String subSeq;
if( input.length() < MAX_LENGTH)
subSeq = input;
else
subSeq = input.substring(input.length() - MAX_LENGTH);
while( subSeq.length() > 0 ) {
if( dictionary.contains(subSeq) || subSeq.length() == 1) {
store.add(subSeq);
input = input.substring(0, input.length() - subSeq.length());
break;
} else {
subSeq = subSeq.substring(1);
}
}
}
int size = store.size();
for( int i = 0; i < size; i ++) {
results.add(store.pop());
}
return results;
}
public List<String> segment() {
List<String> fmm = this.leftMax();
List<String> bmm = this.rightMax();
if( fmm.size() != bmm.size()) {
if ( fmm.size() > bmm.size())
return bmm;
else
return fmm;
}
else {
int fmmSingle = 0, bmmSingle = 0;
boolean isEqual = true;
for( int i = 0; i < bmm.size(); i ++) {
if( !fmm.get(i).equals(bmm.get(i))) {
isEqual = false;
}
if( fmm.get(i).length() == 1)
fmmSingle ++;
if( bmm.get(i).length() == 1)
bmmSingle ++;
}
if ( isEqual ) {
return fmm;
} else if ( fmmSingle > bmmSingle)
return bmm;
else
return fmm;
}
}
public void test(String str) {
request = str;
System.out.println(this.segment());
}
public static void main(String[] args) {
MMSegment f = new MMSegment();
f.getDictionary();
f.test("研究生命科學");
f.test("研究生命令本科生");
f.test("我從馬上下來");
f.test("北京大學生喝進口紅酒");
f.test("美軍中將竟公然說");
f.test("阿美首腦會議將討論巴以和平等問題");
f.addWord("巴以和平");
System.out.println("---------------------------");
System.out.println("向字典中添加'巴以和平'後");
f.test("阿美首腦會議將討論巴以和平等問題");
f.test("我不想吃東西");
}
}
參考資料
[1] http://blog.csdn.net/worldwindjp/article/details/18085725
[2] http://blog.csdn.net/hu948162999/article/details/43608107
[3] http://blog.csdn.net/xiaoyeyopulei/article/details/25194021
[4] http://blog.csdn.net/chenlei0630/article/details/40710441