Java实现的双向匹配分词算法示例

本文实例讲述了Java实现的双向匹配分词算法。分享给大家供大家参考,具体如下:

目前比较流行的几大分词算法有:基于字符串匹配的分词方法、基于理解的分词方法和基于统计的分词方法。本文采用的是基于字符串匹配法。

正向最大匹配分词:

该算法是基于分词词典实现,从字符串左侧进行分割匹配,如果词典存在则返回分割出来的词语并将该词从之前的字符串中切除,循环进行切割直到字符串大小为0。

例如:str = "我们都是西北农林科技大学信息工程学院的学生。",(假设我们定义最大切割长度max = 8,也就是八个汉字)

1.定义分词长度len = max,从左边取出len个字符word = "我们都是西北农林",并在字典中匹配word;
2.字典中没有该词,那么去掉最后一个字符赋值给word,len值减1;
3.重复步骤2,直到在字典中找到该词或是len = 1,退出循环;
4.从str中切除掉已分割的词语,重复进行1、2、3步骤,直到str被分解完。

逆向最大匹配分词:

和正向分词算法一样,只是从字符串的右边开始切分词语,直到字符串长度为0,。这里不再赘述。

双向匹配分词:

该方法是在正向分词和逆向分词的基础上,对于分割有歧义的语句进行歧义分词。提出“贪吃蛇算法”实现。要进行分词的字符串,就是食物。有2个贪吃蛇,一个从左向右吃;另一个从右向左吃。只要左右分词结果有歧义,2条蛇就咬下一口。贪吃蛇吃下去的字符串,就变成分词。 如果左右分词一直有歧义,两条蛇就一直吃。两条蛇吃到字符串中间交汇时,就肯定不会有歧义了。这时候,左边贪吃蛇肚子里的分词,中间没有歧义的分词,右边贪吃蛇肚子里的分词,3个一拼,就是最终的分词结果。

本文是对整段话进行分词,首先将这段话根据标点符号断句,然后将每句话再进行分词输出。

package cn.nwsuaf.spilt;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
/**
 * 基于正逆双向最大匹配分词算法实现
 * @author 刘永浪
 *
 */
public class WordSpilt {
  /**
   * 存储分词词典
   */
  private Map<String, Integer> map = new HashMap<String, Integer>();
  /**
   * 最大切词长度,即为五个汉字
   */
  private int MAX_LENGTH = 5;
  /**
   * 构造方法中读取分词词典,并存储到map中
   *
   * @throws IOException
   */
  public WordSpilt() throws IOException {
    BufferedReader br = new BufferedReader(new FileReader("src/dict.txt"));
    String line = null;
    while ((line = br.readLine()) != null) {
      line = line.trim();
      map.put(line, 0);
    }
    br.close();
  }
  /**
   * 设置最大切词长度
   *
   * @param max
   *      最大切词长度
   */
  public void setMaxLength(int max) {
    this.MAX_LENGTH = max;
  }
  /**
   * 获取当前最大切词长度,默认为5(5个汉字)
   *
   * @return 当前最大切词长度
   */
  public int getMaxLength() {
    return this.MAX_LENGTH;
  }
  /**
   * 最大匹配分词算法
   *
   * @param spiltStr
   *      待切分的字符串
   * @param leftToRight
   *      切分方向,true为从左向右,false为从右向左
   * @return 切分的字符串
   */
  public List<String> spilt(String spiltStr, boolean leftToRight) {
    // 如果带切分字符串为空则返回空
    if (spiltStr.isEmpty())
      return null;
    // 储存正向匹配分割字符串
    List<String> leftWords = new ArrayList<String>();
    // 储存负向匹配分割字符串
    List<String> rightWords = new ArrayList<String>();
    // 用于取切分的字串
    String word = null;
    // 取词的长度,初始化设置为最大值
    int wordLength = MAX_LENGTH;
    // 分词操作中处于字串当前位置
    int position = 0;
    // 已经处理字符串的长度
    int length = 0;
    // 去掉字符串中多余的空格
    spiltStr = spiltStr.trim().replaceAll("\\s+", "");
    // 当待切分字符串没有被切分完时循环切分
    while (length < spiltStr.length()) {
      // 如果还没切分的字符串长度小于最大值,让取词词长等于该词本身长度
      if (spiltStr.length() - length < MAX_LENGTH)
        wordLength = spiltStr.length() - length;
      // 否则取默认值
      else
        wordLength = MAX_LENGTH;
      // 如果是正向最大匹配,从spiltStr的position处开始切割
      if (leftToRight) {
        position = length;
        word = spiltStr.substring(position, position + wordLength);
      }
      // 如果是逆向最大匹配,从spiltStr末尾开始切割
      else {
        position = spiltStr.length() - length;
        word = spiltStr.substring(position - wordLength, position);
      }
      // 从当前位置开始切割指定长度的字符串
      // word = spiltStr.substring(position, position + wordLength);
      // 如果分词词典里面没有切割出来的字符串,舍去一个字符
      while (!map.containsKey(word)) {
        // 如果是单字,退出循环
        if (word.length() == 1) {
          // 如果是字母或是数字要将连续的字母或者数字分在一起
          if (word.matches("[a-zA-z0-9]")) {
            // 如果是正向匹配直接循环将后续连续字符加起来
            if (leftToRight) {
              for (int i = spiltStr.indexOf(word, position) + 1; i < spiltStr
                  .length(); i++) {
                if ((spiltStr.charAt(i) >= '0' && spiltStr
                    .charAt(i) <= '9')
                    || (spiltStr.charAt(i) >= 'A' && spiltStr
                        .charAt(i) <= 'Z')
                    || (spiltStr.charAt(i) >= 'a' && spiltStr
                        .charAt(i) <= 'z')) {
                  word += spiltStr.charAt(i);
                } else
                  break;
              }
            } else {
              // 如果是逆向匹配,从当前位置之前的连续数字、字母字符加起来并翻转
              for (int i = spiltStr.indexOf(word, position - 1) - 1; i >= 0; i--) {
                if ((spiltStr.charAt(i) >= '0' && spiltStr
                    .charAt(i) <= '9')
                    || (spiltStr.charAt(i) >= 'A' && spiltStr
                        .charAt(i) <= 'Z')
                    || (spiltStr.charAt(i) >= 'a' && spiltStr
                        .charAt(i) <= 'z')) {
                  word += spiltStr.charAt(i);
                  if (i == 0) {
                    StringBuffer sb = new StringBuffer(word);
                    word = sb.reverse().toString();
                  }
                } else {
                  // 翻转操作
                  StringBuffer sb = new StringBuffer(word);
                  word = sb.reverse().toString();
                  break;
                }
              }
            }
          }
          break;
        }
        // 如果是正向最大匹配,舍去最后一个字符
        if (leftToRight)
          word = word.substring(0, word.length() - 1);
        // 否则舍去第一个字符
        else
          word = word.substring(1);
      }
      // 将切分出来的字符串存到指定的表中
      if (leftToRight)
        leftWords.add(word);
      else
        rightWords.add(word);
      // 已处理字符串增加
      length += word.length();
    }
    // 如果是逆向最大匹配,要把表中的字符串调整为正向
    if (!leftToRight) {
      for (int i = rightWords.size() - 1; i >= 0; i--) {
        leftWords.add(rightWords.get(i));
      }
    }
    // 返回切分结果
    return leftWords;
  }
  /**
   * 判断两个集合是否相等
   *
   * @param list1
   *      集合1
   * @param list2
   *      集合2
   * @return 如果相等则返回true,否则为false
   */
  public boolean isEqual(List<String> list1, List<String> list2) {
    if (list1.isEmpty() && list2.isEmpty())
      return false;
    if (list1.size() != list2.size())
      return false;
    for (int i = 0; i < list1.size(); i++) {
      if (!list1.get(i).equals(list2.get(i)))
        return false;
    }
    return true;
  }
  /**
   * 判别分词歧义函数
   *
   * @param inputStr
   *      待切分字符串
   * @return 分词结果
   */
  public List<String> resultWord(String inputStr) {
    // 分词结果
    List<String> result = new ArrayList<String>();
    // “左贪吃蛇”分词结果
    List<String> resultLeft = new ArrayList<String>();
    // “中贪吃蛇”(分歧部分)分词结果
    List<String> resultMiddle = new ArrayList<String>();
    // “右贪吃蛇”分词结果
    List<String> resultRight = new ArrayList<String>();
    // 正向最大匹配分词结果
    List<String> left = new ArrayList<String>();
    // 逆向最大匹配分词结果
    List<String> right = new ArrayList<String>();
    left = spilt(inputStr, true);
    /*System.out.println("正向分词结果:");
    for (String string : left) {
      System.out.print(string + "/");
    }
    System.out.println("\n逆向分词结果:");*/
    right = spilt(inputStr, false);
    /*for (String string : right) {
      System.out.print(string + "/");
    }
    System.out.println("\n双向分词结果:");*/
    // 判断两头的分词拼接,是否已经在输入字符串的中间交汇,只要没有交汇,就不停循环
    while (left.get(0).length() + right.get(right.size() - 1).length() < inputStr
        .length()) {
      // 如果正逆向分词结果相等,那么取正向结果跳出循环
      if (isEqual(left, right)) {
        resultMiddle = left;
        break;
      }
      // 如果正反向分词结果不同,则取分词数量较少的那个,不用再循环
      if (left.size() != right.size()) {
        resultMiddle = left.size() < right.size() ? left : right;
        break;
      }
      // 如果以上条件都不符合,那么实行“贪吃蛇”算法
      // 让“左贪吃蛇”吃下正向分词结果的第一个词
      resultLeft.add(left.get(0));
      // 让“右贪吃蛇”吃下逆向分词结果的最后一个词
      resultRight.add(right.get(right.size() - 1));
      // 去掉被“贪吃蛇”吃掉的词语
      inputStr = inputStr.substring(left.get(0).length());
      inputStr = inputStr.substring(0,
          inputStr.length() - right.get(right.size() - 1).length());
      // 清理之前正逆向分词结果,防止造成干扰
      left.clear();
      right.clear();
      // 对没被吃掉的字符串重新开始分词
      left = spilt(inputStr, true);
      right = spilt(inputStr, false);
    }
    // 循环结束,说明要么分词没有歧义了,要么"贪吃蛇"从两头吃到中间交汇了
    // 如果是在中间交汇,交汇时的分词结果,还要进行以下判断:
    // 如果中间交汇有重叠了:
    // 正向第一个分词的长度 + 反向最后一个分词的长度 > 输入字符串总长度,就直接取正向的
    if (left.get(0).length() + right.get(right.size() - 1).length() > inputStr
        .length())
      resultMiddle = left;
    // 如果中间交汇,刚好吃完,没有重叠:
    // 正向第一个分词 + 反向最后一个分词的长度 = 输入字符串总长度,那么正反向一拼即可
    if (left.get(0).length() + right.get(right.size() - 1).length() == inputStr
        .length()) {
      resultMiddle.add(left.get(0));
      resultMiddle.add(right.get(right.size() - 1));
    }
    // 将没有歧义的分词结果添加到最终结果result中
    for (String string : resultLeft) {
      result.add(string);
    }
    for (String string : resultMiddle) {
      result.add(string);
    }
    // “右贪吃蛇”存储的分词要调整为正向
    for (int i = resultRight.size() - 1; i >= 0; i--) {
      result.add(resultRight.get(i));
    }
    return result;
  }
  /**
   * 将一段话分割成若干句话,分别进行分词
   *
   * @param inputStr
   *      待分割的一段话
   * @return 这段话的分词结果
   */
  public List<String> resultSpilt(String inputStr) {
    // 用于存储最终分词结果
    List<String> result = new ArrayList<String>();
    // 如果遇到标点就分割成若干句话
    String regex = "[,。;!?]";
    String[] st = inputStr.split(regex);
    // 将每一句话的分词结果添加到最终分词结果中
    for (String stri : st) {
      List<String> list = resultWord(stri);
      result.addAll(list);
    }
    return result;
  }
  public static void main(String[] args) {
    // example:过来看房价贵不贵?乒乓球拍卖完了
    Scanner input = new Scanner(System.in);
    String str = input.nextLine();
    WordSpilt wordSpilt = null;
    try {
      wordSpilt = new WordSpilt();
    } catch (IOException e) {
      e.printStackTrace();
    }
    List<String> list = wordSpilt.resultWord(str);
    for (String string : list) {
      System.out.print(string + "/");
    }
  }
}

其中的src/dict.txt为字典文件,这里设置如下:

欢迎
我们
脚本下载
脚本
教程
素材
下载

运行结果如下:

更多关于java算法相关内容感兴趣的读者可查看本站专题:《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总》

希望本文所述对大家java程序设计有所帮助。

(0)

相关推荐

  • 多模字符串匹配算法原理及Java实现代码

    多模字符串匹配算法在这里指的是在一个字符串中寻找多个模式字符字串的问题.一般来说,给出一个长字符串和很多短模式字符串,如何最快最省的求出哪些模式字符串出现在长字符串中是我们所要思考的.该算法广泛应用于关键字过滤.入侵检测.病毒检测.分词等等问题中.多模问题一般有Trie树,AC算法,WM算法等等. 背景 在做实际工作中,最简单也最常用的一种自然语言处理方法就是关键词匹配,例如我们要对n条文本进行过滤,那本身是一个过滤词表的,通常进行过滤的代码如下 for (String document : d

  • Java正则表达式过滤出字母、数字和中文

    1.Java中过滤出字母.数字和中文的正则表达式 (1)过滤出字母的正则表达式 [^(A-Za-z)] (2) 过滤出 数字 的正则表达式 [^(0-9)] (3) 过滤出 中文 的正则表达式 [^(\\u4e00-\\u9fa5)] (4) 过滤出字母.数字和中文的正则表达式 [^(a-zA-Z0-9\\u4e00-\\u9fa5)] 2.实例源码 ** * @Title:FilterStr.java * @Package:com.you.dao * @Description:Java中过滤数

  • Java基于正则表达式实现的替换匹配文本功能【经典实例】

    本文实例讲述了Java基于正则表达式实现的替换匹配文本功能.分享给大家供大家参考,具体如下: package replaceDemo; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by Frank * 替换匹配的文本 */ public class ReplaceDemo { public static void main(String[] args) { // 创建一个正则表达式模式

  • java中文分词之正向最大匹配法实例代码

    前言 基于词典的正向最大匹配算法(最长词优先匹配),算法会根据词典文件自动调整最大长度,分词的好坏完全取决于词典. 所谓词典正向最大匹配就是将一段字符串进行分隔,其中分隔 的长度有限制,然后将分隔的子字符串与字典中的词进行匹配,如果匹配成功则进行下一轮匹配,直到所有字符串处理完毕,否则将子字符串从末尾去除一个字,再进行匹配,如此反复. 算法流程图如下: 下面给大家主要讲一下中文分词里面算法的简单实现,废话不多说了,现在先上代码 示例代码 package com; import java.util

  • java实现验证码类生成中文验证码

    复制代码 代码如下: package xwcms.net.service;import java.awt.Color;import java.awt.Font;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.image.BufferedImage;import java.io.IOException;import java.util.Random;import javax.imageio.ImageIO;im

  • Java正则表达式实现在文本中匹配查找换行符的方法【经典实例】

    本文实例讲述了Java正则表达式实现在文本中匹配查找换行符的方法.分享给大家供大家参考,具体如下: 默认情况下,正则表达式 ^ 和 $ 忽略行结束符,仅分别与整个输入序列的开头和结尾匹配.如果激活 MULTILINE 模式,则 ^ 在输入的开头和行结束符之后(输入的结尾)才发生匹配.处于 MULTILINE 模式中时,$ 仅在行结束符之前或输入序列的结尾处匹配. NLMatch.java: package nlMatch; import java.util.regex.Pattern; /**

  • java 逐行读取txt文本如何解决中文乱码

    java读取txt文本中如含有中文,可能会出现乱码,解决方案是: 1.要统一编码,java工程的编码,txt文本编码,java工程中的java文本编码都统一为utf-8: 2.利用 InputStreamReader(new FileInputStream(fileUrl), "utf-8")将文本再次设置为utf-8 3.具体代码如下 复制代码 代码如下: InputStreamReader isr; try { isr = new InputStreamReader(new Fil

  • Java基于正则表达式实现查找匹配的文本功能【经典实例】

    本文实例讲述了Java基于正则表达式实现查找匹配的文本功能.分享给大家供大家参考,具体如下: REMatch.java: package reMatch; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by Frank */ public class REMatch { public static void main(String[] args) { String patt = "Q[^

  • Java实现的最大匹配分词算法详解

    本文实例讲述了Java实现的最大匹配分词算法.分享给大家供大家参考,具体如下: 全文检索有两个重要的过程: 1分词 2倒排索引 我们先看分词算法 目前对中文分词有两个方向,其中一个是利用概率的思想对文章分词. 也就是如果两个字,一起出现的频率很高的话,我们可以假设这两个字是一个词.这里可以用一个公式衡量:M(A,B)=P(AB)/P(A)P(B),其中 A表示一个字,B表示一个字,P(AB)表示AB相邻出现的概率,P(A)表示A在这篇文章中的频度,P(B)表示B在这篇文章中的频度.用概率分词的好

  • java页面中文乱码的解决办法

    在页面提交到tomcat乱码 解决方法是在tomcat/conf/server.xml中进行配置以tomcat6.0.32为例,需将以下代码:Xml代码 复制代码 代码如下: <Connectorport="8080"protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443"/><Connector port="8080"

  • Java 完美判断中文字符的方法

    Java判断一个字符串是否有中文一般情况是利用Unicode编码(CJK统一汉字的编码区间:0x4e00–0x9fbb)的正则来做判断,但是其实这个区间来判断中文不是非常精确,因为有些中文的标点符号比如:,.等等是不能识别的. 以下是比较完善的判断方法:CharUtil.java 复制代码 代码如下: import java.util.regex.Pattern; public class CharUtil { public static void main(String[] args) {  

随机推荐