这两种分词方法都是机械分词方法,按照一定的策略将待分析的汉字串与机器词典中的词条进行匹配,若在词典中找到某个字符串,则匹配成功。按照扫描方向的不同,串匹配分词方法可以分为正向匹配和逆向匹配。按照不同长度优先匹配的情况,可以分为最大匹配和最小匹配。由于汉语单字成词的特点,正向最小匹配和逆向最小匹配一般很少用。一般说来,逆向匹配的切分精度略高于正向匹配,遇到的歧义现象也较少。统计结果表明,单纯使用正向最大匹配的错误率为1/169,单纯使用逆向最大匹配的错误率为1/245。但这种精度还远远不能满足实际的需要。实际使用的分词系统,都是把机械分词作为一种初分手段,还需通过利用各种其它的语言信息来进一步提高切分的准确率。
例:今天来了许多新同事
1.正向最大匹配算法,规定最大匹配长度为5
今天来了许
今天来了
今天来
今天====》得到一个词–今天
来了许多新
来了许多
来了许
来了
来====》得到一个词–来
了许多新同
了许多新
了许多
了许
了====》得到一个词–了
许多新同事
许多新同
许多新
许多====》得到一个词–许多
新同事
新同
新====》得到一个词–新
同事====》得到一个词–同事
最后正向最大匹配的结果是:/今天/来/了/许多/新/同事/
2.逆向最大匹配算法,规定最大匹配长度为5
许多新同事
多新同事
新同事
同事====》得到一个词–同事
来了许多新
了许多新
许多新
多新
新====》得到一个词–新
天来了许多
来了许多
了许多
许多====》得到一个词–许多
今天来了
天来了
来了
了====》得到一个词–了
今天来
天来
来====》得到一个词–来
今天====》得到一个词–今天
最后反向最大匹配的结果是: /今天/来/了/许多/新/同事/
正向最大匹配和反向最大匹配的结果并不一定相同。我一个人吃饭这句话, 按照上述正向最大匹配的结果是:/我/一个/人/吃饭/;按照上述逆向最大匹配的结果是:/我/一/个人/吃饭/。这次两种方式的结果就不一致了。
借助于C++STL的map和BM算法的思想,我简单实现了这两种算法。
#include<map> #include<string> #include<iomanip> #include<iostream> using namespace std; int main() { string str; map<int,string> dict; int maxlength=0,temp=0,i,j,size; cin>>size; for(i=0;i<size;i++) { temp=0; cin>>str; if(str.length()>maxlength) maxlength=str.length(); //记录词典中字符串的最大长度 for(j=0;j<str.length();j++) { temp+=str[j]; temp=temp*10; } dict[temp]=str; //制作key-value键值对 } i=0,j=0; cin>>str; int pos=-1; map<int,string>::iterator map_it; //从前向后匹配 while(i<str.length()) { temp=0; for(j=i;j<str.length();j++) { temp+=str[j]; temp=temp*10; map_it=dict.find(temp); if(map_it!=dict.end()) pos=j; //如果匹配成功记录匹配的位置 if(map_it!=dict.end()&&j==str.length()-1) { cout<<str.substr(i,j)<<endl; i=str.length(); break; } //如果已经到末尾了就不需要记录位置直接输出即可 if(map_it==dict.end()&&(j-i>=maxlength-1||j==str.length()-1)) { if(pos==-1) //如果没有匹配成功进行单字切分向后移动一个字继续匹配 { cout<<str.substr(i,2)<<endl; i+=2; break; } else //如果匹配成功输出并移动到匹配成功后的下一个位置继续匹配 { cout<<str.substr(i,pos-i+1)<<endl; i=pos+1; pos=-1; break; } } } } }
#include<map> #include<string> #include<iomanip> #include<iostream> using namespace std; int main() { string str; map<int,string> dict; int maxlength=0,temp=0,i,j,size; cin>>size; for(i=0;i<size;i++) { temp=0; cin>>str; if(str.length()>maxlength) maxlength=str.length(); //记录词典中字符串的最大长度 for(j=str.length()-1;j>=0;j--) { temp+=str[j]; temp=temp*10; } dict[temp]=str; //制作key-value键值对 } cin>>str; int pos=-1; i=j=str.length()-1; map<int,string>::iterator map_it; //从后向前匹配 while(i>=0) { temp=0; for(j=i;j>=0;j--) { temp+=str[j]; temp=temp*10; map_it=dict.find(temp); if(map_it!=dict.end()) pos=j; //如果匹配成功记录匹配的位置 if(map_it!=dict.end()&&j==0) { cout<<str.substr(0,i-j+1)<<endl; i=-1; break; } //如果已经到末尾了就不需要记录位置直接输出即可 if(map_it==dict.end()&&(i-j>=maxlength-1||j==0)) { if(pos==-1) //如果没有匹配成功进行单字切分向后移动一个字继续匹配 { cout<<str.substr(i-1,2)<<endl; i-=2; break; } else //如果匹配成功输出并移动到匹配成功后的下一个位置继续匹配 { cout<<str.substr(pos,i-pos+1)<<endl; i=pos-1; pos=-1; break; } } } } }