双向最大匹配算法——基于词典规则的中文分词(Java实现)

article/2025/9/23 0:12:12

目录

前言

一、中文分词理论描述

二、算法描述

1、正向最大匹配算法

2、反向最大匹配算法

3、双剑合璧

三、案例描述

四、JAVA实现完整代码

五、组装UI

六、总结


前言

中文分词所需要的词典放在公众号,关注文章末尾的公众号,回复“字典”获取!

这篇将使用Java实现基于规则的中文分词算法,一个中文词典将实现准确率高达85%的分词结果。使用经典算法:正向最大匹配和反向最大匹配算法,然后双剑合璧,双向最大匹配。

一、中文分词理论描述

根据相关资料,中文分词概念的理论描述,我总结如下:

中文分词是将一个汉字序列切分成一个一个单独的词,将连续的字序列按照一定的规范重新组合成词序列的过程,把字与字连在一起的汉语句子分成若干个相互独立、完整、正确的单词,词是最小的、能独立活动的、有意义的语言成分。

中文分词应用广泛,是文本挖掘的基础,在中文文本处理中意义重大,对于输入的一段中文,成功的进行中文分词,可以达到电脑自动识别语句含义的效果。目前,常用流行的以及分次效果好的工具库包括:jieba、HanLP、LTP、FudanNLP等。

我们知道,调用工具方便容易,但是如果自己实现写一个算法实现,那不是更加有成就感^_^。

接下来将一步步介绍最容易理解,最简单,效果还很不错的中文分词算法,据说准确率能达到85%!!

二、算法描述

1、正向最大匹配算法

所谓正向,就是从文本串左边正向扫描,取出子串与词典进行匹配。

算法我分为两步来理解:

假设初始化取最大匹配长度为MaxLen,当前位置pos=0,处理结果result=””,每次取词str,取词长度len,待处理串segstr。

  1. len=MaxLen,取字符串0到len的子串,查找词典,若匹配到则赋值str,加到result,在保证pos+len<=segstr.length()情况下,pos=pos+len,向后匹配,直到字符串扫描完成,结束算法。
  2. 若词典未找到,若len>1,减小匹配长度同时len=MaxLen-1,执行步骤(1),否则,取出剩余子串,执行步骤(1)。

算法代码如下:

    public void MM(String str, int len, int frompos) {if (frompos + 1 > str.length())return;String curstr = "";//此处可以设置断点int llen = str.length() - frompos;if (llen <= len)//句末边界处理curstr = str.substring(frompos, frompos + llen);//substring获取的子串是下标frompos~frompos+llen-1elsecurstr = str.substring(frompos, frompos + len);if (dict.containsKey(curstr)) {result = result + curstr + "/ ";Len = MaxLen;indexpos = frompos + len;MM(str, Len, indexpos);} else {if (Len > 1) {Len = Len - 1;} else {result = result + str + "/ ";frompos = frompos + 1;Len = MaxLen;}MM(str, Len, frompos);}}

 从算法代码看出,很容易理解,细节部分在于边界处理。

测试一下,我输入文本,"我爱自然语言处理,赞赏评论收藏我的文章是我的动力!赶紧关注!"

分词结果:

2、反向最大匹配算法

反向,则与正向相反,从文本串末向左进行扫描。

假设初始化取最大匹配长度为MaxLen,当前位置pos为字符串尾部,处理结果result=””,每次取词str,取词长度len,待处理串segstr。

  1. len=MaxLen,取字符串pos-len到pos的子串,查找词典,若匹配到则赋值str,加到result,同时pos=pos-len,保证pos-len>=0,向前移动匹配,直到字符串扫描完成,结束算法。
  2. 若词典未找到,若len>1,减小匹配长度同时len=MaxLen-1,执行步骤(1),否则,取出剩余子串,执行步骤(1)。

算法逻辑类似,取相反方向处理。

public void RMM(String str, int len, int frompos) {if (frompos < 0)return;String curstr = "";//此处可以设置断点if (frompos - len + 1 >= 0)//句末边界处理curstr = str.substring(frompos - len + 1, frompos + 1);//substring获取的子串是下标frompos~frompos+llen-1elsecurstr = str.substring(0, frompos + 1);//到达句首if (dict.containsKey(curstr)) {RmmResult = curstr + "/ " + RmmResult;Len = MaxLen;indexpos = frompos - len;RMM(str, Len, indexpos);} else {if (Len > 1) {Len = Len - 1;} else {RmmResult = RmmResult + str + "/ ";frompos = frompos - 1;Len = MaxLen;}RMM(str, Len, frompos);}}

 同样,细节部分在于边界处理,其他基本相同。

3、双剑合璧

这里所说的是正向与反向结合,实现双向最大匹配。

双向最大匹配算法,基于正向、反向最大匹配,对分词结果进一步处理,比较两个结果,做的工作就是遵循某些原则和经验,筛选出两者中更确切地分词结果。原则如下:

  • 多数情况下,反向最大匹配效果更好,若分词结果相同,则返回RMM结果;
  • 遵循切分最少词原则,更大匹配词为更好地分词结果,比较之后返回最少词的切分结果;
  • 根据切分后词长度的大小,选择词长度大者为最终结果。

具体也需要看开始给定的最大匹配长度为多少。以下代码只实现了原则(1)、(2)。

    public String BMM() throws IOException {String Mr = MM_Seg();String RMr = RMM_Seg();if (Mr.equals(RMr)) {return "双向匹配相同,结果为:" + Mr;} else {List<String> MStr;List<String> RStr;MStr = Arrays.asList(Mr.trim().split("/"));RStr = Arrays.asList(RMr.trim().split("/"));if (MStr.size() >= RStr.size()) {//多数情况下,反向匹配正确率更高return "双向匹配不同,最佳结果为:" + RMr;} elsereturn "双向匹配不同,最佳结果为:" + Mr;}}

另外,这与使用的词典大小有关,是否包含常用词。

三、案例描述

如果上面还不能完全理解,下面的例子可以更好的理解算法执行过程。

正向最大匹配算法:

取MaxLen=3,SegStr=”对外经济技术合作与交流不断扩大”,maxNum=3,len=3,result=””,pos=0,curstr=””.

第一次,curstr=”对外经”,查找词典,未找到,将len-1,得到curstr=”对外”,此时匹配到词典,将结果加入result=”对外/ ”.pos=pos+len.

第二次,curstr=”经济技”,查找词典,未找到,将len-1,得到curstr=”经济”,此时匹配到词典,将结果加入result=”对外/ 经济/ ”.pos=pos+len.

以此类推...

最后一次,边界,pos=13,因为只剩下”扩大”两个字,所以取出全部,查找词典并匹配到,将结果加入result=”对外/ 经济/ 技术/ 合作/ 与/ 交流/ 不断/ 扩大/ ”.此时pos+1>SegStr.length(),结束算法。 


反向最大匹配算法:

取MaxLen=3,SegStr=”对外经济技术合作与交流不断扩大”,maxNum=3,len=3,result=””,pos=14,curstr=””.

第一次,curstr=”断扩大”,查找词典,未找到,将len-1,得到curstr=”扩大”,此时匹配到词典,将结果加入result=”扩大/ ”.pos=pos-len.

第二次,MaxLen=3,curstr=”流不断”,查找词典,未找到,将len-1,得到curstr=”不断”,此时匹配到词典,将结果加入result=”不断/ 扩大/ ”.pos=pos-len.

以此类推...

最后一次,边界,pos=1,因为只剩下”对外”两个字,所以取出全部,查找词典并匹配到,将结果加入result=”对外/ 经济/ 技术/ 合作/ 与/ 交流/ 不断/ 扩大/ ”.此时pos-1<0,结束算法。

四、JAVA实现完整代码

 除了分词算法实现,还需要读入词典,对词典进行预处理,具体

package ex1;import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;public class seg {String result;String RmmResult;String segstring;int MaxLen;int Len;int indexpos;Map<String, String> dict; public seg(String inputstr, int maxlen) {//构造函数segstring = inputstr;MaxLen = maxlen;Len = MaxLen;indexpos = 0;result = "";RmmResult = "";dict = new HashMap<String, String>();}public void ReadDic() throws FileNotFoundException, IOException {BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("chineseDic.txt"), "GBK"));String line = null;while ((line = br.readLine()) != null) {String[] words = line.trim().split(",");//词典包含词性标注,需要将词与标注分开,放入列表String word = words[0];String cx = words[1];dict.put(word, cx);}br.close();}public String MM_Seg() throws IOException {//正向最大匹配算法try {ReadDic();//读入字典MM(segstring, MaxLen, 0);//正向最大分词return result;} catch (IOException e) {return "Read Error!";}}public void MM(String str, int len, int frompos) {if (frompos + 1 > str.length())return;String curstr = "";//此处可以设置断点int llen = str.length() - frompos;if (llen <= len)//句末边界处理curstr = str.substring(frompos, frompos + llen);//substring获取的子串是下标frompos~frompos+llen-1elsecurstr = str.substring(frompos, frompos + len);if (dict.containsKey(curstr)) {result = result + curstr + "/ ";Len = MaxLen;indexpos = frompos + len;MM(str, Len, indexpos);} else {if (Len > 1) {Len = Len - 1;} else {result = result + str + "/ ";frompos = frompos + 1;Len = MaxLen;}MM(str, Len, frompos);}}public String RMM_Seg() throws IOException {//正向最大匹配算法try {ReadDic();//读入字典RMM(segstring, MaxLen, segstring.length() - 1);//正向最大分词return RmmResult;} catch (IOException e) {return "Read Error!";}}public void RMM(String str, int len, int frompos) {if (frompos < 0)return;String curstr = "";//此处可以设置断点if (frompos - len + 1 >= 0)//句末边界处理curstr = str.substring(frompos - len + 1, frompos + 1);//substring获取的子串是下标frompos~frompos+llen-1elsecurstr = str.substring(0, frompos + 1);//到达句首if (dict.containsKey(curstr)) {RmmResult = curstr + "/ " + RmmResult;Len = MaxLen;indexpos = frompos - len;RMM(str, Len, indexpos);} else {if (Len > 1) {Len = Len - 1;} else {RmmResult = RmmResult + str + "/ ";frompos = frompos - 1;Len = MaxLen;}RMM(str, Len, frompos);}}public String BMM() throws IOException {String Mr = MM_Seg();String RMr = RMM_Seg();if (Mr.equals(RMr)) {return "双向匹配相同,结果为:" + Mr;} else {List<String> MStr;List<String> RStr;MStr = Arrays.asList(Mr.trim().split("/"));RStr = Arrays.asList(RMr.trim().split("/"));if (MStr.size() >= RStr.size()) {//多数情况下,反向匹配正确率更高return "双向匹配不同,最佳结果为:" + RMr;} elsereturn "双向匹配不同,最佳结果为:" + Mr;}}public String getResult() {return result;}public static void main(String[] args) throws IOException, Exception {seg s = new seg("我爱自然语言处理,赞赏评论收藏我的文章是我的动力!赶紧关注!", 3);
//        String result = s.MM_Seg();String result = s.RMM_Seg();System.out.println(result);}
}

五、组装UI

我是用的开发软件为是IDEA,一个方便之处可以拖动组件组装UI界面。也可以自行写JavaFX实现简单布局。

这是简单页面的设计:

UI界面可以有更好的用户体验,通过UI界面的元素调用方法,减少每次测试运行算法脚本的繁琐。

实验演示:

每次可以观察不同最大匹配长度分词后的结果。

"年中"词语解析:

在词典中,是这样的,可以发现满足最大匹配。

双向最大匹配算法,结果提示:

六、总结

这篇介绍了使用Java实现基于规则的中文分词算法,使用经典算法:正向最大匹配和反向最大匹配算法,然后双剑合璧,双向最大匹配。最后设计简单UI界面,实现稍微高效的中文分词处理,结果返回。

  1. 双向最大匹配算法原则,希望句子最长词保留完整、最短词数量最少、单字词问题,目前只解决了句子切分最少词问题。
  2. 正向反向匹配算法可以进一步优化结构,提高执行效率,目前平均耗时20ms。
  3. UI界面增加输入输出提示语,方便用户使用,在正确的区域输入内容。
  4. 将最大匹配长度设置为可输入,实现每次可以观察不同MaxLen得到的切分结果。
  5. 双向最大匹配按钮点击之后,返回结果同时返回MM和RMM结果是否一样的提示,方便查看。

中文分词所需要的词典放在公众号,关注文章末尾的公众号,回复“字典”获取!


我的CSDN博客: 双向最大匹配算法——基于词典规则的中文分词(Java实现)_陆海潘江小C的博客-CSDN博客_双向匹配算法


http://chatgpt.dhexx.cn/article/OLF16hHQ.shtml

相关文章

中文分词技术及应用

中文分词技术及应用中文分词算法有5大类&#xff1a; 1、 基于词典的方法 2、基于统计的方法 3、基于规则的方法 4、基于字标注的方法 5、基于人工智能的技术&#xff08;基于理解&#xff09;的方法 中文分词目前有4个瓶颈&#xff1a; 1、分词歧义 2、未登陆词识别 3、分词粒…

【NLP】为什么中文分词比英文分词更难?有哪些常用算法?(附代码)

导读&#xff1a;人类文明的重要标志之一是语言文字的诞生。数千年来&#xff0c;几乎人类所有知识的传播都是以语言和文字作为媒介。 自然语言处理是使用计算机科学与人工智能技术分析和理解人类语言的一门学科。在人工智能的诸多范畴中&#xff0c;自然语言的理解以其复杂性、…

正向最大匹配中文分词算法

中文分词一直都是中文自然语言处理领域的基础研究。目前&#xff0c;网络上流行的很多中文分词软件都可以在付出较少的代价的同时&#xff0c;具备较高的正确率。而且不少中文分词软件支持Lucene扩展。但不管实现如何&#xff0c;目前而言的分词系统绝大多数都是基于中文词典的…

NLP|中文分词技术及应用

摘要:中文分词是中文信息处理的重要基础,本文详细阐述了目前主要的几种中文分词算法的技术原理 、中文分词目前的瓶颈和评价准则,以及中文分词的具体应用。 中文分词指将一个汉字序列切分成一个个单独的词。现有的中文分词算法有五大类:基于词典的方法,基于统计的方法,基…

入门科普:一文看懂NLP和中文分词算法(附代码举例)

导读&#xff1a;在人类社会中&#xff0c;语言扮演着重要的角色&#xff0c;语言是人类区别于其他动物的根本标志&#xff0c;没有语言&#xff0c;人类的思维无从谈起&#xff0c;沟通交流更是无源之水。 所谓“自然”乃是寓意自然进化形成&#xff0c;是为了区分一些人造语言…

中文分词算法—— 基于词典的方法

1、基于词典的方法&#xff08;字符串匹配&#xff0c;机械分词方法&#xff09; 定义:按照一定策略将待分析的汉字串与一个“大机器词典”中的词条进行匹配&#xff0c;若在词典中找到某个字符串&#xff0c;则匹配成功。 按照扫描方向的不同&#xff1a;正向匹配和逆向匹配…

【NLP】中文分词:原理及分词算法

一、中文分词 词是最小的能够独立活动的有意义的语言成分&#xff0c;英文单词之间是以空格作为自然分界符的&#xff0c;而汉语是以字为基本的书写单位&#xff0c;词语之间没有明显的区分标记&#xff0c;因此&#xff0c;中文词语分析是中文信息处理的基础与关键。 Lucene中…

常见分词算法综述

常见分词算法综述 文章目录 常见分词算法综述一、基于词典的分词1. 最大匹配分词算法2. 最短路径分词算法&#xff1a;2.1基于dijkstra算法求最短路径&#xff1a;2.2N-dijkstra算法求最短路径&#xff1a;2.3. 基于n-gram model的分词算法&#xff1a; 二、基于字的分词算法生…

中文分词原理及分词工具介绍

转自&#xff1a;https://blog.csdn.net/flysky1991/article/details/73948971 本文首先介绍下中文分词的基本原理&#xff0c;然后介绍下国内比较流行的中文分词工具&#xff0c;如jieba、SnowNLP、THULAC、NLPIR&#xff0c;上述分词工具都已经在github上开源&#xff0c;后…

中文分词常见方法

中文分词是中文文本处理的一个基础步骤&#xff0c;也是中文人机自然语言交互的基础模块。不同于英文的是&#xff0c;中文句子中没有词的界限&#xff0c;因此在进行中文自然语言处理时&#xff0c;通常需要先进行分词&#xff0c;分词效果将直接影响词性、句法树等模块的效果…

自然语言处理之中文分词技术与算法

1 正向最大匹配法 1.1 正向最大匹配&#xff08;Maximum Match Method, MM法&#xff09;的基本思想&#xff1a; 假定分词词典中的最长词有i个汉字字符&#xff0c;则用被处理文档的当前字串中的前i个字作为匹配字段&#xff0c;查找字典。若字典中存在这样的一个i字词&#…

列举:中文分词算法你知道几种?

列举&#xff1a;中文分词算法你知道几种&#xff1f; 摘要&#xff1a;看似普通的一句话&#xff0c;甚至几个词&#xff0c;在机器眼里都要经过好几道“程序”。这个过程主要靠中文分词算法&#xff0c;这个算法分为三大类&#xff1a;机械分词算法、基于n元语法的分词算法、…

(转)Linux下管道的原理

7.1.1 Linux管道的实现机制 在Linux中&#xff0c;管道是一种使用非常频繁的通信机制。从本质上说&#xff0c;管道也是一种文件&#xff0c;但它又和一般的文件有所不同&#xff0c;管道可以克服使用文件进行通信的两个问题&#xff0c;具体表现为&#xff1a; 限制管…

Linux之进程间通信——管道

文章目录 前言一、进程间通信1.概念2.目的3.进程间通信分类 二、管道1.管道介绍2.管道分类1.匿名管道pipi创建管道文件&#xff0c;打开读写端fork子进程关闭父进程的写入端&#xff0c;关闭子进程的读取端读写特征管道特征 2.命名管道mkfifo创建管道文件删除管道文件通信 三、…

Linux系统中的管道通信

目录 管道如何通信 管道的访问控制机制&#xff1a; 匿名管道 匿名管道数据传输的原理 如何使用&#xff08;代码案例&#xff09; 用C/C代码编译实现父子进程间通信案例 &#xff1a; 思路 实现 命名管道 为什么要有命名管道 回归进程间通信的本质 匿名管道的短板…

linux 管道 (单管道与双管道)

管道的局限性&#xff1a; ①数据不能进程自己写&#xff0c;自己读。 ②管道中数据不可反复读取。-旦读走, 管道中不再存在。 ③采用半双工通信方式&#xff0c;数据只能在单方向上流动。 ④只能在有公共祖先的进程间使用管道 单通道将小写字母改为大写例程&#xff1a; #in…

Linux 管道文件

管道分为无名管道和有名管道两种管道&#xff0c;管道文件是建立在内存之上可以同时被两个进程访问的文件。 先来说说有名管道&#xff1a; mkfifo函数创建有名管道&#xff0c;属于系统调用。 在linux操作系统中为实现下述功能&#xff0c; 先创建一个有名管道文件fifo。 …

【Linux】Linux 管道命令Cut、sort、wc、uniq、tee、tr【一】

目录 &#x1f40b;Cut— 根据条件 从命令结果中 提取 对应内容 &#x1f40b;sort—可针对文本文件的内容&#xff0c;以行为单位来排序。 &#x1f40b;wc命令— 显示/统计 指定文件 字节数, 单词数, 行数 信息. &#x1f40b; uniq— 用于检查及删除文本文件中重复出现的…

Linux管道命令(pipe)全

目录 选取命令&#xff1a;cut、grep 传送门 排序命令&#xff1a;sort、wc、uniq 传送门 双向重定向&#xff1a;tee 字符转换命令&#xff1a;tr、col、join、paste、expand 传送门 划分命令&#xff1a;split 传送门 参数代换&#xff1a;xargs 传送门 关于减号…

Linux中管道命令的用法

原文地址&#xff1a;http://blog.csdn.net/wirelessqa/article/details/8968381 一. 管道命令 管道命令操作符是&#xff1a;”|”,它只能处理经由前面一个指令传出的正确输出信息&#xff0c;对错误信息信息没有直接处理能力。然后&#xff0c;传递给下一个命令&#xff0c;…