数据结构与算法-21算法专项(中文分词)(END)

news2024/11/28 14:44:19

中文分词

搜索引擎是如何理解我们的搜索语句的?

mysql中使用 【like “%中国%”】,这样的使用方案

  • 缺点1:mysql索引会失效
  • 缺点2:不能模糊,比如我搜湖南省 就搜不到湖南相关的

1 trie树

Trie树,又称前缀树、字典树或单词查找树,是一种树形结构,用于快速检索字符串数据集中的键。Trie树的核心思想是利用字符串的公共前缀来降低查询时间的开销。在Trie树中,每个节点都代表一个字符串中的某个前缀,从根节点到某一节点的路径上的所有字符连接起来,就是该节点对应的字符串。Trie树中不存在值域,其值就隐含在树的路径中。

Trie树的基本性质包括:

  • 根节点不包含字符,除根节点外每个节点都包含一个字符。
  • 从根节点到某一节点路径上经过的字符连接起来,就是该节点对应的字符串。
  • 每个节点的子节点包含的字符都不相同。

在这里插入图片描述

2 示例


package cn.zxc.demo.leetcode_demo.search_algoritm;

class TrieNode {
    // 指向子节点的指针数组,假设只包含小写字母
    TrieNode[] children = new TrieNode[26];
    // 标记该节点是否是一个单词的结尾
    boolean isEndOfWord;

    public TrieNode() {
        isEndOfWord = false;
        for (int i = 0; i < 26; i++) {
            children[i] = null;
        }
    }
}

public class Trie {
    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

    // 插入一个单词到Trie中
    public void insert(String word) {
        TrieNode node = root;
        for (int i = 0; i < word.length(); i++) {
            int index = word.charAt(i) - 'a';  // 得到当前字母在数组中的索引
            if (node.children[index] == null) {
                node.children[index] = new TrieNode();
            }
            node = node.children[index];
        }
        node.isEndOfWord = true;
    }

    // 搜索Trie中是否存在一个单词
    public boolean search(String word) {
        TrieNode node = searchPrefix(word);
        return node != null && node.isEndOfWord;
    }

    // 搜索Trie中是否存在一个单词的前缀
    public boolean startsWith(String prefix) {
        TrieNode node = searchPrefix(prefix);
        return node != null;
    }

    // 辅助方法:搜索前缀,并返回最后一个节点
    private TrieNode searchPrefix(String word) {
        TrieNode node = root;
        for (int i = 0; i < word.length(); i++) {
            int index = word.charAt(i) - 'a';
            if (node.children[index] == null) {
                return null;
            }
            node = node.children[index];
        }
        return node;
    }  

    public static void main(String[] args) {
        Trie trie = new Trie();
        // 新增apple
        trie.insert("apple");
        System.out.println(trie.search("apple"));   // 返回 true
        System.out.println(trie.search("app"));     // 返回 false
        System.out.println(trie.startsWith("app")); // 返回 true
        trie.insert("app");
        System.out.println(trie.search("app"));     // 返回 true
    }
}

3 分析

优点

  1. 快速搜索:Trie树通过利用字符串的公共前缀来减少查询时间,查询效率非常高。在Trie树中搜索一个字符串的时间复杂度为O(k),其中k是字符串的长度,与树中存储的字符串数量无关。这使得Trie树特别适合于处理大量字符串的快速检索问题。
  2. 节省空间:Trie树通过共享公共前缀来节省存储空间。在Trie树中,每个节点只存储一个字符,并且只有必要的节点才会被创建,因此相比存储完整的字符串列表,Trie树可以显著减少存储空间的使用。
  3. 自动完成:Trie树非常适合实现自动完成功能,如搜索引擎中的自动补全、代码编辑器的自动提示等。通过遍历Trie树,可以快速地找到所有以用户输入的前缀开头的字符串,从而为用户提供有用的建议。
  4. 高效插入和删除:在Trie树中插入和删除字符串的操作也非常高效,时间复杂度同样为O(k),其中k是字符串的长度。这是因为插入和删除操作只需要遍历从根节点到目标节点的路径,并在必要时添加或删除节点。
  5. 高效排序:由于Trie树中的字符串是按照字典序排列的,因此可以利用Trie树对字符串进行高效排序。此外,Trie树还可以方便地支持范围查询等高级操作。
  6. 紧凑表示形式:Trie树提供了一种紧凑的数据表示形式,特别适合于存储和处理大量具有公共前缀的字符串数据。

缺点

  1. 存储空间需求高:尽管Trie树在节省空间方面有一定优势,但当处理的字符串数量非常大且字符串之间缺乏公共前缀时,Trie树可能会占用大量的存储空间。这是因为Trie树需要为每个字符串的每个字符都创建一个节点,从而导致节点数量激增。
  2. 相较哈希表效率更低:在某些情况下,如当需要频繁地进行随机访问时,哈希表可能会比Trie树更高效。哈希表通过哈希函数将字符串映射到固定的内存位置,从而实现快速的随机访问。而Trie树则更适合于处理具有前缀关系的字符串数据。
  3. 空指针耗费内存空间:在Trie树中,由于每个节点都可能有多个子节点(对应于不同的字符),因此即使某些子节点不存在,也需要用空指针来表示这种不存在关系。这些空指针会占用一定的内存空间,从而增加了Trie树的内存开销。

4 ik分词器

IK分词器是一款在中文自然语言处理(NLP)领域广泛应用的中文分词工具,其以高效、准确、灵活的特点受到了开发者和研究者的青睐。

1 介绍

  • 定义:IK分词器是一款基于Java开发的开源中文分词工具,它是Lucene的一个扩展,专门用于中文文本的分词处理。
  • 功能:IK分词器结合了词典分词和基于统计的分词方法,旨在为用户提供高效、准确、灵活的中文分词服务。

2 分词原理

  1. 词典分词
    • IK分词器会维护一个包含大量中文词汇的词典。
    • 文本预处理:将输入的文本进行预处理,包括去除标点符号、空格等无关字符。
    • 词典匹配:IK分词器会从文本的起始位置开始,依次与词典中的词汇进行匹配,使用“最大匹配法”策略,尽可能匹配最长的词汇。
  2. 基于统计的分词
    • 对于词典分词无法准确匹配的新词、缩写词或特殊表达方式,IK分词器会利用统计模型进行分词。
    • 统计模型通过大量已标注的语料库训练得到,能够学习到词汇之间的关联和出现频率等信息。
    • IK分词器会对候选词进行打分,选择概率最高的候选词序列作为分词结果。
  3. 解决歧义
    • IK分词器采用最短路径法和最大概率法来解决分词中的歧义问题。
    • 允许用户定义自定义规则来处理特定的歧义问题,提高分词的准确性。

5 ik源码

在这里插入图片描述

black.dic 黑名单词典集

main.dic 核心词典集,主要用于匹配

stopword.dic 暂停词 词典集

1 词典的加载

在这里插入图片描述

词典加载后以前缀树的方式进行存储

在这里插入图片描述

2 分词核心api

IKSegmenter.next

获取分词后的结果

	/**
	 * 分词,获取下一个词元
	 * 
	 * @return Lexeme 词元对象
	 * @throws IOException
	 */
	public synchronized Lexeme next() throws IOException {
		if (this.context.hasNextResult()) {
			// 存在尚未输出的分词结果
			return this.context.getNextLexeme();
		} else {
			/*
			 * 从reader中读取数据,填充buffer 如果reader是分次读入buffer的,那么buffer要进行移位处理
			 * 移位处理上次读入的但未处理的数据
			 */
			int available = context.fillBuffer(this.input);
			if (available <= 0) {
				// reader已经读完
				context.reset();
				return null;

			} else {
				// 初始化指针
				context.initCursor();
				do {
					// 遍历子分词器
					for (ISegmenter segmenter : segmenters) {
						segmenter.analyze(context);
					}
					// 字符缓冲区接近读完,需要读入新的字符
					if (context.needRefillBuffer()) {
						break;
					}
					// 向前移动指针
				} while (context.moveCursor());
				// 重置子分词器,为下轮循环进行初始化
				for (ISegmenter segmenter : segmenters) {
					segmenter.reset();
				}
			}
			// 对分词进行歧义处理
			this.arbitrator.process(context, this.cfg.useSmart());
			// 处理未切分CJK字符
			context.processUnkownCJKChar();
			// 记录本次分词的缓冲区位移
			context.markBufferOffset();
			// 输出词元
			if (this.context.hasNextResult()) {
				return this.context.getNextLexeme();
			}
			return null;
		}
	}

org.wltea.analyzer.core.CJKSegmenter.analyze

关键词:

tmpHits:存放命中项的集合

hit:命中项,可以是main.dic中匹配到的,也可以是前缀,如果是前缀的话存放在tmpHits中

Lexeme:存放分词后的结果

public void analyze(AnalyzeContext context) {
        if (CharacterUtil.CHAR_USELESS != context.getCurrentCharType()) { // 上下问中的字符串不是不可用的

            // 优先处理tmpHits中的hit
            if (!this.tmpHits.isEmpty()) {
                // 处理词段队列
                Hit[] tmpArray = this.tmpHits.toArray(new Hit[this.tmpHits.size()]);
                for (Hit hit : tmpArray) {
                    // 匹配main.dic
                    hit = Dictionary.getSingleton().matchWithHit(context.getSegmentBuff(), context.getCursor(), hit);
                    if (hit.isMatch()) { // hit命中的是完整的词
                        // 输出当前的词
                        Lexeme newLexeme = new Lexeme(context.getBufferOffset(), hit.getBegin(), context.getCursor()
                                - hit.getBegin() + 1, Lexeme.TYPE_CNWORD);
                        newLexeme.setProps(hit.getProps());
                        context.addLexeme(newLexeme);

                        if (!hit.isPrefix()) {// 不是词前缀,hit不需要继续匹配,移除
                            this.tmpHits.remove(hit);
                        }

                    } else if (hit.isUnmatch()) {
                        // hit不是词,移除
                        this.tmpHits.remove(hit);
                    }
                }
            }

            // *********************************
            // 再对当前指针位置的字符进行单字匹配
            Hit singleCharHit = Dictionary.getSingleton().matchInMainDict(context.getSegmentBuff(), context.getCursor(), 1);
            if (singleCharHit.isMatch()) {// 首字成词
                // 输出当前的词
                Lexeme newLexeme = new Lexeme(context.getBufferOffset(), context.getCursor(), 1, Lexeme.TYPE_CNWORD);
                newLexeme.setProps(singleCharHit.getProps());
                context.addLexeme(newLexeme);

                // 同时也是词前缀
                if (singleCharHit.isPrefix()) {
                    // 前缀匹配则放入hit列表
                    this.tmpHits.add(singleCharHit);
                }
            } else if (singleCharHit.isPrefix()) {// 首字为词前缀
                // 前缀匹配则放入hit列表
                this.tmpHits.add(singleCharHit);
            }

        } else {
            // 遇到CHAR_USELESS字符
            // 清空队列
            this.tmpHits.clear();
        }

        // 判断缓冲区是否已经读完
        if (context.isBufferConsumed()) {
            // 清空队列
            this.tmpHits.clear();
        }

        // 判断是否锁定缓冲区
        if (this.tmpHits.size() == 0) {
            context.unlockBuffer(SEGMENTER_NAME);

        } else {
            context.lockBuffer(SEGMENTER_NAME);
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2227538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【日志】力扣13.罗马数字转整数 || 解决泛型单例热加载失败问题

2024.10.28 【力扣刷题】 13. 罗马数字转整数 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/roman-to-integer/description/?envTypestudy-plan-v2&envIdtop-interview-150这题用模拟的思想可以给相应的字母赋值&#xff0c;官方的答案用的是用一…

超好玩又简单-猜数字游戏(有手就行)

云边有个稻草人-CSDN博客 我的个人主页 目录 云边有个稻草人-CSDN博客 前言 猜数字游戏的游戏要求 1. 随机数的生成 1.1 rand 1.2 srand 1.3 time 1.4 设置随机数的范围 2. 猜数字游戏实现 2.1 游戏实现基本思路 2.2 代码实现 Relaxing Time! —————————…

微信小程序25__实现卡片变换

先看效果图 实现代码如下&#xff1a; <view class"page" style"filter:hue-rotate({{rotation}}deg)"><view class"prev" catchtap"toPrev">《《《</view><view class"next" catchtap"toNext&q…

git下载和配置

git是什么&#xff1f; Git是一种分布式版本控制系统&#xff0c;用于跟踪文件的变化&#xff0c;尤其是源代码。它允许多个开发者在同一项目上进行协作&#xff0c;同时保持代码的历史记录。Git的主要特点包括&#xff1a; 分布式&#xff1a;每个开发者都有项目的完整副本&a…

前端自学资料(笔记八股)分享—CSS(4)

更多详情&#xff1a;爱米的前端小笔记&#xff08;csdn~xitujuejin~zhiHu~Baidu~小红shu&#xff09;同步更新&#xff0c;等你来看&#xff01;都是利用下班时间整理的&#xff0c;整理不易&#xff0c;大家多多&#x1f44d;&#x1f49b;➕&#x1f914;哦&#xff01;你们…

无人机避障——4D毫米波雷达Octomap从点云建立三维栅格地图

Octomap安装 sudo apt-get install ros-melodic-octomap-ros sudo apt-get install ros-melodic-octomap-msgs sudo apt-get install ros-melodic-octomap-server sudo apt-get install ros-melodic-octomap-rviz-plugins # map_server安装 sudo apt-get install ros-melodic-…

SLAM|2. 差异与统一:坐标系变换与外参标定

本章主要内容 1.坐标系变换 2.相机外参标定 上一章我们了解了相机内参的概念&#xff0c;内参主要解决三维世界与二维图像之间的映射关系。有了内参我们可以一定程度上还原相机看到了什么&#xff08;但缺乏尺度&#xff09;。但相机看到的数据只是处于相机坐标系&#xff0c;为…

Solidworks二次开发 获取装配体里面组件列表以及名称

******************************************************************************从装配体里面获取组件******************************************************************************Option Explicit 强制在模块级别显式声明所有变量 *********************定义SOLIDWOR…

《链表篇》---环形链表II(返回节点)

题目传送门 方法一&#xff1a;哈希表&#xff08;与环形链表类似&#xff09; 很容易就可以找到链表的相交位置。 public class Solution {public ListNode detectCycle(ListNode head) {if(head null || head.next null){return null;}Set<ListNode> visited new Ha…

2024 年 MathorCup 数学应用挑战赛——大数据竞赛-赛道 A:台风的分类与预测

2024年MathorCup大数据挑战赛-赛道A初赛-第四版论文.ziphttps://download.csdn.net/download/qq_52590045/89930645 ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ …

解决Github下载速度慢的问题

1. 方式一 先把hosts文件先复制一份到其他文件夹下&#xff0c;以免造成不小心改动出现的后果在C盘的C:\Windows\System32\drivers\etc 下的hosts文件 用编辑器打开后&#xff0c;在末尾处添加访问如下的两个网站所返回的两个IP https://github.com.ipaddress.com/ http://gi…

基于SSM大学生互动交流网站设计与实现

前言 随着社会的发展&#xff0c;系统的管理形势越来越严峻。越来越多的用户利用互联网获得信息&#xff0c;但各种信息鱼龙混杂&#xff0c;信息真假难以辨别。为了方便用户更好的获得信息&#xff0c;因此&#xff0c;设计一种安全高效的大学生互动交流网站极为重要。 开发…

C语言复习第8章 数据在内存中的存储

目录 一、数据类型1.1 数据类型介绍1.2 类型的意义1.3 类型的基本归类整形家族浮点数家族构造类型(自定义类型)指针类型空类型 二、整型在内存中的存储2.1 原反补2.2 为什么内存中要存补码?2.3 大小端介绍2.4 为什么会有大小端之分2.5 写一个程序 判断当前机器的字节序 三、练…

创业板权限开通有何要求?创业板的股票交易佣金最低是多少?

创业板 创业板又称二板市场&#xff08;Second-board Market&#xff09;即第二股票交易市场&#xff0c;是与主板市场&#xff08;Main-Board Market&#xff09;不同的一类证券市场&#xff0c;专为暂时无法在主板市场上市的创业型企业提供融资途径和成长空间的证券交易市场…

使用 FastGPT 工作流实现 AI 赛博算卦,一键生成卦象图

最近那个男人写的汉语新解火遍了全网&#xff0c;那个男人叫李继刚&#xff0c;国内玩 AI 的同学如果不知道这个名字&#xff0c;可以去面壁思过了。 这个汉语新解的神奇之处就在于它只是一段几百字的提示词&#xff0c;效果却顶得上几千行代码写出来的应用程序。 这段提示词…

【Qt】窗口——Qt窗口的概念、常用的窗口函数、菜单栏、工具栏、状态栏、浮动窗口、对话框

文章目录 Qt窗口Qt窗口的概念菜单栏工具栏状态栏浮动窗口对话框 Qt 窗口 Qt窗口的概念 QMainWindow 类概述&#xff1a; QMainWindow 是一个为用户提供主窗口程序的类&#xff0c;它继承自 QWidget 类&#xff0c;并且提供了一个预定义的布局。 菜单栏 菜单栏常用属性&#xf…

深入解析HTTP与HTTPS的区别及实现原理

文章目录 引言HTTP协议基础HTTP响应 HTTPS协议SSL/TLS协议 总结参考资料 引言 HTTP&#xff08;HyperText Transfer Protocol&#xff09;超文本传输协议是用于从Web服务器传输超文本到本地浏览器的主要协议。随着网络安全意识的提高&#xff0c;HTTPS&#xff08;HTTP Secure…

Android 下载进度条HorizontalProgressView 基础版

一个最基础的自定义View 水平横向进度条&#xff0c;只有圆角、下载进度控制&#xff1b;可二次定制度高&#xff1b; 核心代码&#xff1a; Overrideprotected void onDraw(NonNull Canvas canvas) {super.onDraw(canvas);int mW getMeasuredWidth();int mH getMeasuredHei…

服务器数据恢复—异常断电导致服务器挂载分区无法访问的数据恢复案例

服务器数据恢复环境&#xff1a; 某品牌服务器同品牌存储&#xff0c;Linux centos7EXT4文件系统。 服务器故障&#xff1a; 意外断电导致服务器操作系统不能正常启动。经过修复后系统可以正常启动&#xff0c;但是挂载的分区无法正常访问。使用fsck修复这个问题分区&#xff…

网络原理(数据链路层)->以太网帧格式解

前言 大家好我是小帅&#xff0c;今天我们来了解以太网帧格式 个人主页 文章目录 1.数据链路层1.1 认识以太⽹1.2 MAC地址&#xff08;⽹卡的硬件地址&#xff09;1.2.1 对⽐理解MAC地址和IP地址 1.3 认识MTU1.4 MTU对IP协议的影响1. 5 MTU对UDP协议的影响1.6 MTU对于TCP协议的…