C语言:自然语言处理-中文分词器(基于字符串匹配)

中文分词

中文分词作为自然语言处理的分支,也是中文人机自然语言交互的基础模块,这里充分利用计算机的特点进行分析,与之英文不同的是,中文分词没有词的界限,因此在进行中文自然语言处理时,通常需要先进行分词,分词效果将直接影响词性,句法树等模块的效果,当然分词只是一个工具,场景不同,要求也不同。在人机自然语言交互中,成熟的中文分词算法能够达到更好的自然语言处理效果,帮助计算机理解复杂的中文语言。

基本信息


作用

中文分词是文本挖掘的基础,对于输入的一段中文,成功的进行中文分词,可以达到电脑自动识别语句含义的效果。
中文分词技术属于自然语言处理技术范畴,对于一句话,人可以通过自己的知识来明白哪些是词,哪些不是词,但如何让计算机也能理解?其处理过程就是分词算法。

影响

中文分词对于搜索引擎来说,最重要的并不是找到所有结果,因为在上百亿的网页中找到所有结果没有太多的意义,没有人能看得完,最重要的是把最相关的结果排在最前面,这也称为相关度排序。中文分词的准确与否,常常直接影响到对搜索结果的相关度排序。从定性分析来说,搜索引擎的分词算法不同,词库的不同都会影响页面的返回结果


摘录《百度百科》

算法分类

现有的分词算法中有三大类:

 1. 基于字符串匹配的分词方法
 2. 基于理解的分词方法和基于统计的分词方法。
 3. 按照是否与词性标注过程相结合,又可以分为单纯分词方法和分词与标注相结合的一体化方法。

这里我采用的是字符串匹配法
字符匹配又叫机械分词法,通过截取部分字符串与字典中进行比对,若某个字符串在字典中比对成功,则匹配成功。根据扫描方法不同可分为正向匹配、逆向匹配、双向匹配

  1. 正向最大匹配(由左到右的方向):
  2. 逆向最大匹配(由右到左的方向):
  3. 双向最大匹配(进行由左到右、由右到左两次扫描):

一般做最长匹配,但是我这里的词库里最大单词长度为4,所以我就截取的长度为4个单元。
这里一个注意点

英文字母:
字节数 : 1;编码:GB2312
字节数 : 1;编码:GBK
字节数 : 1;编码:GB18030
字节数 : 1;编码:ISO-8859-1(latin-1)
字节数 : 1;编码:UTF-8
字节数 : 4;编码:UTF-16
字节数 : 2;编码:UTF-16BE
字节数 : 2;编码:UTF-16LE

中文汉字:
字节数 : 2;编码:GB2312
字节数 : 2;编码:GBK
字节数 : 2;编码:GB18030
字节数 : 1;编码:ISO-8859-1(latin-1)
字节数 : 3;编码:UTF-8
字节数 : 4;编码:UTF-16
字节数 : 2;编码:UTF-16BE
字节数 : 2;编码:UTF-16LE

因此:我们在输入的时候注意不同环境下的中文编码问题!
对于中文分词,首先要解决的第一个问题,字符串匹配问题

strcmp()简介

一、函数原型:int strcmp(const char *s1,const char *s2);
二、函数参数:
s1->指向第一个字符串
s2->指向第二个字符串
三、函数结果
如果两个字符串相等则返回0,不等则返回-1,1。

在这里我用的是string输入字符串,则需要运用compare函数
这里我们做一个样例:

#include<iostream>
#include<string> 
using namespace std;
int main(){
	int flag;
   	string FirstStr = " ",SecondStr = " ";//First, a null value is attached
   	cin >> FirstStr >> SecondStr;
   	if(flag = SecondStr.compare(FirstStr))//compare this string are true or false.
   		cout<<flag<<endl;
   	else
   		cout<<flag<<endl;
   	return 0;
}

有了上面的样例,我们开始算法分析

正向最长匹配

正向最长匹配:依次从左到右将带分词的字符与字典进行匹配,如果匹配上则划分出一个词语。
例如:“研究生命科学起源”,在词典中存在[“研究生”],[“研究”],两个词汇那么我们需要遵从最长原则,截取“研究生”此单词,由此可见,最大匹配出的词必须保证下一个扫描不是词表中的词或词的前缀才可以结束。这里我们怎么做呢?我们可以截取一段长度进行机械查询。

int forward(char ch[],string sentence){
	//正向情况 
	clock_t start,end;
	int S = 0;
	int i;
	char Words[1000][1000] = {""};
	string reward = sentence;
	struct words words;
	struct words *p;
	struct words head;
	words.next = NULL;
   	loadwords(ch,&words);
   	p = words.next; 
	string tmp = "";
	while(sentence.length()){
		int flag = 1;
		if(sentence.length()>8)
			tmp = sentence.substr(0, 8);//赋值4个字
		else
			tmp = sentence.substr(0, sentence.length());//只读一个字 
		while(tmp.length()){
			p = words.next;
			while(p != NULL){
				const char *Ch = tmp.data();
				if(strcmp(p->word,Ch)==0){
					strcat(Words[S],Ch);
					strcat(Words[S++],"/");
					flag = 0;
					break;
				}
				p = p->next;
			}
			if(!flag)break;
			tmp = tmp.substr(0 ,tmp.length() - 2);//往前推一个 
		}
		sentence = sentence.substr(tmp.length(),sentence.length());
	}
	end = clock();
	cout<<"正向查询:"; 
	int max;
	for(i=0;i<S;i++){
		if(max<strlen(Words[i]))max = strlen(Words[i]);
		cout<<Words[i];
	}
这里做一个简易分析
输入:研究生命起源
第一次查询研究生命剩余:起源
第二次查询研究生剩余:命起源
第三次查询命起源剩余:null
第四次查询命起剩余:源
第五次查询剩余:起源
第六次查询起源剩余:null
第七次查询null剩余:null(结束)
得到结果研究生/命/起源

当然,中文分词具有歧义,单纯一个正向查询是远远不够的,我们还需要逆向查询,逆向插叙与正向查询查询顺序相反。

	int T = 0;
	char NewWords[1000][1000] = {""};
	tmp = "";
	while(reward.length()){
		int flag = 1;
		if(reward.length()>8)
			tmp = reward.substr(reward.length()-8, 8);//赋值4个字
		else
			tmp = reward.substr(0, reward.length());//剩下
		while(tmp.length()){
			p = words.next;
			while(p != NULL){
				const char *Ch = tmp.data();
				if(strcmp(p->word,Ch)==0){
					strcat(NewWords[T],Ch);
					strcat(NewWords[T++],"/");
					flag = 0;
					break;
				}
				p = p->next;
			}
			if(!flag)break;
			tmp = tmp.substr(2,tmp.length());//往前推一个 
		}
		reward = reward.substr(0,reward.length()-tmp.length());
	}
	end = clock();
	cout<<"逆向查询:"; 
	int Max = 0;
	for(i=T-1;i>=0;i--){
		if(Max<strlen(NewWords[i]))Max = strlen(NewWords[i]);
		printf("%s",NewWords[i]);
	}

同理:我们采用同一句话(研究生命起源)进行分析

研究生命起源
第一次查询生命起源剩余:生命
第二次查询命起源剩余:生命起
第三次查询起源剩余:研究生命
第四次查询研究生命剩余:null
第五次查询究生命剩余:生
第六次查询生命剩余:研究
第七次查询研究剩余:null
第八次查询null剩余:null(结束)
得到结果研究/生命/起源
那么存在歧义如何解决呢?有第一句正确了,但下一句又出错了,可谓拆东墙补西墙。另一些人提出综合两种规则,期待它们取长补短,称为双向最长匹配。 这是一种融合两种匹配方法的复杂规则集,流程如下:
同时执行正向和逆向最长匹配,若两者的词数不同,则返回词数更少的那一个。
否则,返回两者中单字更少的那一个。当单字数也相同时,优先返回逆向最长匹配的结果。

//双向

	if(S>T){//空格比 
		for(int i=0;i<S;i++)
			 cout<<Words[i];
	} 
	else if(S<T){
		for(int i=T-1;i>=0;i--)
			cout<<NewWords[i];
	}
	else if(S==T){
		if(max>Max){
			for(int i=0;i<S;i++)
				cout<<Words[i];
		}
		else{
			for(int i=T-1;i>=0;i--)
				cout<<NewWords[i];
		}
	}

项目资源:自然语言:中文分词

结论

本文研究了关于中文分词的算法,而中文分词作为后续信息检索、机器翻译、语音识别等等广泛应用于各个领域当中。

备注

上述内容存在着缺陷与不足,恳请提出建议进行改正。

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不知名的MasaNvi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值