算法
对于中文分词技术的实现,有许多算法可以完成,目前大致可以把算法分为三大类:
基于字符串匹配的分词方法;
基于理解的分词方法;
基于统计的分词方法。
其中,基于字符串匹配的分词方法是把中文句子按照一定的策略将待分析的汉字串与已知且足够大的中文词典库进行比对,从而达到分词效果。而我们通常使用最多的分词策略,大致有三类,正向最大匹配法,逆向最大匹配法和最少切分法。
基于理解的分词方法是指让计算机模拟人对句子的理解进行分词。基于统计的分词方法是指找出上下文中出现较多的汉字组合,将这些组合视为词汇,代入到原文中进行分词。
在这里,我们就使用字符串匹配的分词方法,利用逆向最大匹配的策略,对中文句子进行简单的分词。
算法思路
逆向最大匹配法大致思想为:将整个字符串作为一个“词组”带入到词典中进行比对,若不成功,删除第一个字符,继续进行如此操作,直到成功或者只剩下最后一个字,再把结果放入一个字符串的数组中,最后删除原句中的结果,继续上面的操作。下面我将用一个例子解释这个操作:
原句:今晚月亮真漂亮啊
词典:“今晚”,“月亮”,“漂亮”
第一次代入:今晚月亮真漂亮啊(在词典中没有该词汇,删除首字符继续比对)
删除首字符:晚月亮真漂亮啊(在词典中没有该词汇,删除首字符继续比对)
删除首字符:月亮真漂亮啊(在词典中没有该词汇,删除首字符继续比对)
删除首字符:亮真漂亮啊(在词典中没有该词汇,删除首字符继续比对)
…
删除首字符:啊(在词典中没有该词汇,只剩下一个字,放入结果数组,并删除位于句尾的最后这个字,进行第二次代入)
第二次代入:今晚月亮真漂亮(在词典中没有该词汇,删除首字符继续比对)
…
删除首字符:漂亮(在词典中找到词汇“漂亮”,放入结果数组,并删除位于句尾的结果“漂亮”,进行第三次代入)
…
算法实现
评价
逆向最大匹配法的思想使得这个策略十分容易实现,具有非常明显的简单易懂的特点,实现代码也不算太长。但是,逆向最大匹配算法对于一些比较特殊的句子,分词准确率可能会降低。例如下面这个句子:
爱迪生发明了很多东西
如果你的词典足够大,你会发现,按照逆向最大匹配的方法,计算机会将“明了”看做一个中文词汇分隔出来,继续向下走,计算机将分出词汇“生发”,这样一来,就会造成 “爱迪 生发 明了” 的错误。
没关系!我们还有正向最大匹配法,我们可以用正向最大匹配法,对这个句子进行分词,结果就对了。但是正向最大匹配法也会出现bug,怎么办呢,我们可以将正向和逆向结合,这样就是另外一种分词策略:双向最大匹配法,大大降低了出现bug的概率。
结语
任何算法都有它的优劣性,我们在使用算法时,不仅要使用算法,更要去考虑怎么样去优化算法,使算法更加贴合自己的需求。
package com.rshare.utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
/**
* www.wouuz.com
* @author jspanjsp
*
*/
public class WordTest {
public static void main(String args[]){
String path = "C:\Users\jspanjsp\Desktop\word1.txt";
WordTest wordTest = new WordTest();
List<String> words = wordTest.getWordDictionary(path); //分词词典
List<String> wordList = wordTest.getWordList("查询粉刺字典分词地址",words); //查询语句分词
StringBuilder sb1 = new StringBuilder();
for (String string : wordList) {
sb1.append(string).append(",");
}
System.out.println("查询关键字:"+sb1.toString());
}
//判断当前查询标题包含的关键词密度
public int getkeywordDensity(List<String> words,List<String> title) {
int num = 0;
for (String string : title) {
if(words.contains(string)) {
num += 1;
}
}
return num;
}
//获取词典
public List<String> getWordDictionary(String wordPath){
List<String> wordList = new ArrayList<String>(); //词典
BufferedReader br = null;
try {
File file = new File(wordPath);
br = new BufferedReader(new InputStreamReader(new FileInputStream(file),"utf-8"));
String temp = null;
while((temp=br.readLine())!=null) {
if(temp.contains(";")) {
String[] word = temp.split(";");
for (int i = 0; i < word.length; i++) {
wordList.add(word[i]);
}
}else {
wordList.add(temp);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return wordList;
}
public List<String> getWordList(String a,List<String> wordList){
List<String> splitWords = new ArrayList<String>();
if(StringUtils.isBlank(a)) {
return splitWords;
}
//String[] cs = {"你好","请问","什么","名字","springboot","java","mysql","CentOS","编译","Python3","Python2","句子"};//词典
a = a.toLowerCase();
String[] cs2 = new String[100]; //结果数组
int jud=0;//找到匹配字符串与否的标志
int j=0;
String temp=null;//初始化临时字符串
for(;a.length()>0;)
{
for(int i = 0;i<a.length();i++)
{
temp = a.substring(i);//每次截取掉首个字符
if(isin(wordList,temp) == true)//如果目标字符串在数组中
{
cs2[j] = temp;
jud = 1;
int number = temp.length();
a = a.substring(0,a.length()-number);
}
}
if(jud == 0)//没有找到匹配字符串
{
cs2[j] = a.substring(a.length()-1,a.length());//将最后一个元素放在cs2里面
a = a.substring(0, a.length()-1);//截掉最后一个元素继续循环。
}
jud = 0;
j++;
}
for(;j >= 0;j--){
if(cs2[j] != null) {
if(cs2[j].length()>=2) {
//System.out.print(cs2[j]+" ");
splitWords.add(cs2[j]);
}
}
}
return splitWords;
}
/*
* 下面为判断字符串是否在词典中的函数方法
*/
static public boolean isin(List<String> wordList,String temp)//判断目标字符串是否在对比字符串数组中
{
int i;
for(i = 0;i<wordList.size();)
{
if(temp.equals(wordList.get(i)))
i = wordList.size()+1;
else
i++;
}
if(i == wordList.size()+1)
return true;
else
return false;
}
}
word1.txt文件内容:
天气;句子;判断;域名;html5;字符串;计算;添加;冰箱;mysql 存储过程;面试题;拆分;逐渐;选择;算法;重启;利用;邮件;vip;ram;破解;解决;窗口;一个;操作;+;java;retries;登录;工具;科学上网;wing;=;省略号;特殊;表单;刺字;查询;字典