中文分词-最大匹配算法

正向最大匹配法算法如下所示:

逆向匹配法思想与正向一样,只是从右向左切分,这里举一个例子:

输入例句:S1=”计算语言学课程有意思” ;

定义:最大词长MaxLen = 5;S2= ” “;分隔符 = “/”;

假设存在词表:…,计算语言学,课程,意思,…;

最大逆向匹配分词算法过程如下:

  1. S2=””;S1不为空,从S1右边取出候选子串W=”课程有意思”;

  2. 查词表,W不在词表中,将W最左边一个字去掉,得到W=”程有意思”;

  3. 查词表,W不在词表中,将W最左边一个字去掉,得到W=”有意思”;

  4. 查词表,W不在词表中,将W最左边一个字去掉,得到W=”意思”

  5. 查词表,“意思”在词表中,将W加入到S2中,S2=” 意思/”,并将W从S1中去掉,此时S1=”计算语言学课程有”;

  6. S1不为空,于是从S1左边取出候选子串W=”言学课程有”;

  7. 查词表,W不在词表中,将W最左边一个字去掉,得到W=”学课程有”;

  8. 查词表,W不在词表中,将W最左边一个字去掉,得到W=”课程有”;

  9. 查词表,W不在词表中,将W最左边一个字去掉,得到W=”程有”;

  10. 查词表,W不在词表中,将W最左边一个字去掉,得到W=”有”,这W是单字,将W加入到S2中,S2=“ /有 /意思”,并将W从S1中去掉,此时S1=”计算语言学课程”;

  11. S1不为空,于是从S1左边取出候选子串W=”语言学课程”;

  12. 查词表,W不在词表中,将W最左边一个字去掉,得到W=”言学课程”;

  13. 查词表,W不在词表中,将W最左边一个字去掉,得到W=”学课程”;

  14. 查词表,W不在词表中,将W最左边一个字去掉,得到W=”课程”;

  15. 查词表,“意思”在词表中,将W加入到S2中,S2=“ 课程/ 有/ 意思/”,并将W从S1中去掉,此时S1=”计算语言学”;

  16. S1不为空,于是从S1左边取出候选子串W=”计算语言学”;

  17. 查词表,“计算语言学”在词表中,将W加入到S2中,S2=“计算语言学/ 课程/ 有/ 意思/”,并将W从S1中去掉,此时S1=””;

  18. S1为空,输出S2作为分词结果,分词过程结束。

相应程序示例:

准备文件:建立一个词表文件wordlexicon,格式如下

  • 计算语言学
  • 课程
  • 意思

输入文件:test,格式如下

  • 计算语言学课程有意思

编译后执行如下:SegWord.exe test,输出分词结果文件:SegmentResult.txt

源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// Dictionary.h
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <hash_map>
using namespace std;
using namespace stdext;
class CDictionary
{
public:
CDictionary(); //将词典文件读入并构造为一个哈希词典
~CDictionary();
int FindWord(string w); //在哈希词典中查找词
private:
string strtmp; //读取词典的每一行
string word; //保存每个词
hash_map<string, int> wordhash; // 用于读取词典后的哈希
hash_map<string, int >::iterator worditer; //
typedef pair<string, int> sipair;
};
//将词典文件读入并构造为一个哈希词典
CDictionary::CDictionary()
{
ifstream infile(“wordlexicon”); // 打开词典
if (!infile.is_open()) // 打开词典失败则退出程序
{
cerr << "Unable to open input file: " << "wordlexicon"
<< " -- bailing out!" << endl;
exit(-1);
}
while (getline(infile, strtmp, '\\n')) // 读入词典的每一行并将其添加入哈希中
{
istringstream istr(strtmp);
istr >> word; //读入每行第一个词
wordhash.insert(sipair(word, 1)); //插入到哈希中
}
}
CDictionary::~CDictionary()
{
}
//在哈希词典中查找词,若找到,则返回,否则返回
int CDictionary::FindWord(string w)
{
if (wordhash.find(w) != wordhash.end())
{
return 1;
}
else
{
return 0;
}
}

主程序main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include “Dictionary.h”
# define MaxWordLength 10 // 最大词长为个字节(即个汉字)
# define Separator “/ ” // 词界标记
CDictionary WordDic; //初始化一个词典
//对字符串用最大匹配法(正向或逆向)处理
string SegmentSentence(string s1)
{
string s2 = “”; //用s2存放分词结果
while(!s1.empty())
{
int len =(int) s1.length(); // 取输入串长度
if (len > MaxWordLength) // 如果输入串长度大于最大词长
{
len = MaxWordLength; // 只在最大词长范围内进行处理
}
//string w = s1.substr(0, len); // (正向用)将输入串左边等于最大词长长度串取出作为候选词
string w = s1.substr(s1.length() – len, len); //逆向用
int n = WordDic.FindWord(w); // 在词典中查找相应的词
while(len > 2 && n == 0) // 如果不是词
{
len -= 2; // 从候选词右边减掉一个汉字,将剩下的部分作为候选词
//w = w.substr(0, len); //正向用
w = s1.substr(s1.length() – len, len); //逆向用
n = WordDic.FindWord(w);
}
//s2 += w + Separator; // (正向用)将匹配得到的词连同词界标记加到输出串末尾
w = w + Separator; // (逆向用)
s2 = w + s2 ; // (逆向用)
//s1 = s1.substr(w.length(), s1.length()); //(正向用)从s1-w处开始
s1 = s1.substr(0, s1.length() – len); // (逆向用)
}
return s2;
}
//对句子进行最大匹配法处理,包含对特殊字符的处理
string SegmentSentenceMM (string s1)
{
string s2 = “”; //用s2存放分词结果
int i;
int dd;
while(!s1.empty() )
{
unsigned char ch = (unsigned char)s1[0];
if (ch < 128) // 处理西文字符
{
i = 1;
dd = (int)s1.length();
while (i < dd && ((unsigned char)s1[i] < 128) && (s1[i] != 10) && (s1[i] != 13)) // s1[i]不能是换行符或回车符
{
i++;
}
if ((ch != 32) && (ch != 10) && (ch != 13)) // 如果不是西文空格或换行或回车符
{
s2 += s1.substr(0,i) + Separator;
}
else
{
//if (ch == 10 || ch == 13) // 如果是换行或回车符,将它拷贝给s2输出
if (ch == 10 || ch == 13 || ch == 32) //谢谢读者mces89的指正
{
s2 += s1.substr(0, i);
}
}
s1 = s1.substr(i,dd);
continue;
}
else
{
if (ch < 176) // 中文标点等非汉字字符
{
i = 0;
dd = (int)s1.length();
while(i < dd && ((unsigned char)s1[i] < 176) && ((unsigned char)s1[i] >= 161)
&& (!((unsigned char)s1[i] == 161 && ((unsigned char)s1[i+1] >= 162 && (unsigned char)s1[i+1] <= 168)))
&& (!((unsigned char)s1[i] == 161 && ((unsigned char)s1[i+1] >= 171 && (unsigned char)s1[i+1] <= 191)))
&& (!((unsigned char)s1[i] == 163 && ((unsigned char)s1[i+1] == 172 || (unsigned char)s1[i+1] == 161)
|| (unsigned char)s1[i+1] == 168 || (unsigned char)s1[i+1] == 169 || (unsigned char)s1[i+1] == 186
|| (unsigned char)s1[i+1] == 187 || (unsigned char)s1[i+1] == 191)))
{
i = i + 2; // 假定没有半个汉字
}
if (i == 0)
{
i = i + 2;
}
if (!(ch == 161 && (unsigned char)s1[1] == 161)) // 不处理中文空格
{
s2+=s1.substr(0, i) + Separator; // 其他的非汉字双字节字符可能连续输出
}
s1 = s1.substr(i, dd);
continue;
}
}
// 以下处理汉字串
i = 2;
dd = (int)s1.length();
while(i < dd && (unsigned char)s1[i] >= 176)
{
i += 2;
}
s2 += SegmentSentence(s1.substr(0, i));
s1 = s1.substr(i,dd);
}
return s2;
}
int main(int argc, char *argv[])
{
string strtmp; //用于保存从语料库中读入的每一行
string line; //用于输出每一行的结果
ifstream infile(argv[1]); // 打开输入文件
if (!infile.is_open()) // 打开输入文件失败则退出程序
{
cerr << "Unable to open input file: " << argv[1]
<< " -- bailing out!" << endl;
exit(-1);
}
ofstream outfile1("SegmentResult.txt"); //确定输出文件
if (!outfile1.is_open())
{
cerr << "Unable to open file:SegmentResult.txt"
<< "--bailing out!" << endl;
exit(-1);
}
while (getline(infile, strtmp, 'n')) //读入语料库中的每一行并用最大匹配法处理
{
line = strtmp;
line = SegmentSentenceMM(line); // 调用分词函数进行分词处理
outfile1 << line << endl; // 将分词结果写入目标文件
}
return 0;
}

补充说明:如果使用正向匹配法,请将源代码中的相关注释 “//“互换。