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

中文分词

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

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();// 新增appletrie.insert("apple");System.out.println(trie.search("apple"));   // 返回 trueSystem.out.println(trie.search("app"));     // 返回 falseSystem.out.println(trie.startsWith("app")); // 返回 truetrie.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中的hitif (!this.tmpHits.isEmpty()) {// 处理词段队列Hit[] tmpArray = this.tmpHits.toArray(new Hit[this.tmpHits.size()]);for (Hit hit : tmpArray) {// 匹配main.dichit = 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.rhkb.cn/news/457738.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

群控系统服务端开发模式-应用开发-业务架构逻辑开发API准备工作

安装与仓库已经调整完毕&#xff0c;现在开发业务架构逻辑&#xff0c;其次再开发功能逻辑。业务架构逻辑开发与功能逻辑开发不是一回事&#xff0c;一定要明白。业务架构指的是做某一件事或是某一种类型的事的逻辑&#xff0c;在互联网web应用中通常指一套系统的外在逻辑&…

「Qt Widget中文示例指南」如何实现半透明背景?

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 本文将为大家展示如…

android openGL ES详解——缓冲区VBO/VAO/EBO/FBO/离屏渲染

目录 一、缓冲区对象概念 二、分类 三、顶点缓冲区对象VBO 1、概念 2、为什么使用VBO 3、如何使用VBO 生成缓冲区对象 绑定缓冲区对象 输入缓冲区数据 更新缓冲区中的数据 删除缓冲区 4、VBO应用 四、顶点数组对象VAO 1、概念 2、为什么使用VAO 3、如何使用VAO…

jupyter notebook改变默认启动路径

安装好Anaconda 3以后&#xff0c;就可以使用Jupyter notebook了&#xff0c;但是我们打开Jupyter notebook后&#xff0c;发现界面是一个默认的目录&#xff0c;这个目录在哪里&#xff1f;如果想把自己写的程序文件保存在自己新建的一个文件夹里&#xff0c;修改默认目录到自…

实现内网穿透的最佳解决方案(无实名认证,完全免费)

目录 ngrok&#xff08;不是很推荐&#xff0c;服务器在国外&#xff0c;已被gfw k了不能用&#xff09; cpolar&#xff08;推荐&#xff0c;无需实名操作简单、服务器在国内&#xff09; SAKURA FRP&#xff08;我的世界玩家中的呼声很高&#xff0c;但要实名认证&#xf…

虚拟光驱软件 PowerISO v8.7.0 中文激活版

PowerISO 是一款虚拟光驱工具及强大的光盘映像文件制作工具。支持创建、编辑、提取、压缩、加密和转换ISO/BIN图像文件。同时自带DISM工具&#xff0c;支持ESD/ISO/WIM/ESD格式转换&#xff0c;制作镜像文件制作U盘启动&#xff0c;支持ISO/BIN/IMG/DAA/WIM等各种常见文件类型。…

C语言初阶:十.结构体基础

♥感谢您阅读本篇文章&#xff0c;文章内容为个人对所学内容的整理总结&#xff0c;欢迎大佬在评论区指点一二。♥ ♥个人主页&#xff1a;折枝寄北-CSDN博客折枝寄北擅长C语言初阶,等方面的知识,折枝寄北关注python,c,java,qt,c语言领域.https://blog.csdn.net/2303_80170533?…

【遗传算法】基于遗传模拟退火算法的风电功率聚类分析

摘要 本文提出了一种基于遗传模拟退火算法的风电功率聚类分析方法。风电功率受气象条件的影响波动较大&#xff0c;给电网调度带来较大挑战。本文通过遗传算法结合模拟退火算法&#xff0c;对风电功率进行聚类分析&#xff0c;旨在挖掘风电功率数据中的模式&#xff0c;提升风…

单管放大电路的分析(Multisim仿真)

绘制原理图 在工作区加入NPN型晶体管 图 1 NPN晶体管 基极电阻R1为50kΩ&#xff0c;集极电阻R2为5 kΩ&#xff0c;直流电源12V&#xff0c;并将电阻与晶体管连接起来。 图 2直流通路 修改晶体管的BF&#xff08;放大倍数&#xff09;为100和VJC&#xff08;等效电阻&#…

coze案例|标准证件照(下)–工作流+Bot设计

项目背景 和 图像流见 教程coze案例|标准证件照(上)–图像流 三、工作流 1、新建工作流 首页“个人空间-工作流-创建工作流” 输入工作流的名称和描述后&#xff0c;点击确认即可。 2、工作流设计 工作流整体流程如下 主要分为以下几个步骤&#xff1a; 开始节点&#…

使用语音模块的开发智能家居产品(使用雷龙LSYT201B 语音模块)

在这篇博客中&#xff0c;我们将探讨如何使用 LSYT201B 语音模块 进行智能设备的语音交互开发。通过这个模块&#xff0c;我们可以实现智能设备的语音识别和控制功能&#xff0c;为用户带来更为便捷和现代的交互体验。 1. 语音模块介绍 LSYT201B 是一个基于“芯片算法”的语音…

Vue3 学习笔记(五)Vue3 模板语法详解

在 Vue3 的世界里&#xff0c;模板语法是我们构建用户界面的基石。今天&#xff0c;让我们一起深入了解 Vue3 的模板语法&#xff0c;我将用通俗易懂的语言和实用的例子&#xff0c;带你掌握这项必备技能。 1、文本插值&#xff1a;最基础的开始 想在页面上显示数据&#xff1f…

2024 BuildCTF 公开赛|MISC

1.what is this? BuildCTF{S0_TH1S_15_M0R5E_C0DE_!!} 2.一念愚即般若绝&#xff0c;一念智即般若生 解压缩密码&#xff1a;s2j6dg* BuildCTF{D3crypt10n_1s_4_l0ng_r04d} 3.如果再来一次&#xff0c;还会选择我吗&#xff1f; 修复png 密码&#xff1a;8!67adz6&#xff…

【AI绘画】Midjourney进阶:对角线构图详解

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AI绘画 | Midjourney 文章目录 &#x1f4af;前言&#x1f4af;什么是构图为什么Midjourney要使用构图 &#x1f4af;对角线构图特点应用场景提示词书写技巧测试 &#x1f4af;小结 &#x1f4af;前言 【AI绘画】Midjourney进阶&a…

【QT】windows 平台 QT6.8 安装

qt-online-installer-windows-x64-4.8.1.exe Index of /qt/archive/online_installers/4.8/登录,第一个字母是大写的 如果忘记了,可以在这里“ https://my.qt.io/## D:\Qt6

LDR6328:助力小家电实现TYPE-C接口快充输入

在小家电市场日益繁荣的今天&#xff0c;消费者对产品的要求越来越高&#xff0c;不仅关注功能性和实用性&#xff0c;更追求便捷和高效的充电体验。传统的充电接口如DC、Micro USB等&#xff0c;已经无法满足现代消费者对快速充电和高效数据传输的需求。为此&#xff0c;许多小…

基于SSM轻型卡车零部件销售系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;配件类型管理&#xff0c;配件信息管理&#xff0c;订单信息管理&#xff0c;检修休息管理&#xff0c;系统管理 用户账号功能包括&#xff1a;系统首页&#xff0c;个人中心&…

SMA-BP时序预测 | Matlab实现SMA-BP黏菌算法优化BP神经网络时间序列预测

SMA-BP时序预测 | Matlab实现SMA-BP黏菌算法优化BP神经网络时间序列预测 目录 SMA-BP时序预测 | Matlab实现SMA-BP黏菌算法优化BP神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现SMA-BP黏菌算法优化BP神经网络时间序列预测&#xff08;完…

【C#】调用本机AI大模型流式返回

【python】AI Navigator的使用及搭建本机大模型_anaconda ai navigator-CSDN博客 【Python】AI Navigator对话流式输出_python ai流式返回-CSDN博客 前两章节我们讲解了使用AI Navigator软件搭建本机大模型&#xff0c;并使用python对大模型api进行调用&#xff0c;使其流式返…

JS面试八股文(一)

&#x1f60a;JS面试八股文&#xff08;一&#xff09; 1.JS由哪三部分组成&#xff1f;2.JS有哪些内置对象&#xff1f;3.操作数组的方法有哪些&#xff1f;4.JS对数据类型的检测方式有哪些&#xff1f;5.说一下闭包&#xff0c;闭包有什么特点&#xff1f;6.前端的内存泄漏怎…