双方案-基于Mysql 与 ElasticSearch实现关键词提示搜索与全文检索

news2024/9/29 21:19:51

文章目录

  • 前言
  • Mysql检索
    • 简述原理
    • 其他索引
    • 构建
      • 实例
      • 代码
      • 搜索流程
      • 搜索
  • ElasticSearch 实现
    • 环境配置
    • 编码
    • 查询与插入

前言

就喜欢搞这种不需要怎么费劲的东西,只需要把思路阐述清楚,随笔性质的博文,顺手啊,几乎不用改定就可以当博文发布出去。

那么,这里的话我们要做的就是实现这个关键词的一个搜索功能,这个前端我就不说了,实现起来起来其实还是容易的,就是费劲。我们主要关注到后端,然后关于这个的话,我们这里还是提供两个方案,一个就是直接基于Mysql>=5.7去做的,还有一个就是直接基于ElasticSearch去做。当然自己做一个也可以,那么之后的话,工程结束的话,那么以后碰到这种容易写的或者,非常有必要的东西,我还是会回来记录一下的。

Mysql检索

那么首先我们基于Mysql去做。这个方案最省钱,当然由于Mysql的数据是在磁盘当中的,首先会慢一点,然后,在重新对数据进行修改的时候,需要重新构建索引,如果我们做全文检索的话,那么如果这个词很多的话,更新是会比较慢的,因此适合小批量的数据检索,或者说仅仅针对某一个字段进行检索。例如我们做的是一个博文技术交流社区,我们可以选择对标题进行检索。当然一般情况下来说,以CSDN这种网站为例,我们在检索的时候,往往会发现它匹配的其实不仅仅只是标题,其实对文章的内容也会做检索,因此,当我们需要做这种检索的时候,使用MySQL 的话,其实就不是那么好了,但是凡事是没有绝对的。不同的场景还是有不同的优势的。

当然最最简单,直接暴力的方式就是,mysql like。至于效果,我就不说了。

简述原理

全文检索通常会使用倒排索引实现。倒排索引同B+树索引一样,也是一种索引结构。
只是数据结构肯定是不同的,它通过一个辅助表,这个辅助表中存储了单词与单词自身在一个或多个文档中所在位置之间的映射。这通常采用关联数组实现,有两种表现形式:
1)inverted file index:{单词,单词所在的文档ID}
2)full inverted index:{单词,{单词所在的文档ID,在具体文档中的位置} }

有机会可以聊一聊这个关于B+ 树,其实还是很有意思的,以前一直以为数据库会CURD就行了,现在自己做项目发现,八股文还有点用的,涉及到的很多优化那省下来的可是白花花的银子。有什么方案,为什么这样,理解大致原理还是很重要的

首先大概形式可能是这样的:

一句话 we dislike java lucene

存进去的东西大概是这个样子的:
(数据表)
在这里插入图片描述
这个东西的话其实和我们的表结构是类似的,Documentid 其实就是我们ID呗,text只是我们的一个字段嘛。其实在MySQL实现里面,我们就是按照表来的,只是这个表很多个字段罢了。

最后构建出的索引大概是这个样子。(full inverted index)
(索引表)

在这里插入图片描述

那么此时当我们去查找的时候,那么我们是这样的。

那么,在MySQL>=5.7 中InnoDB存储引擎支持全文索引采用full inverted index的方式,将(DocumentId,Position)视为一个“ilist” 。因此在全文检索的表中,一共有两列,一列是word字段,另一个是“ilist”,并且在word字段上设有索引。
此外,由于在ilist字段中存放了Poistion信息,所以可以进行Proximity Sreach,但是MyISAM存储引擎不支持该特性。

那么我们去查找的时候呢,我们就是先把一句话,先做个分词,然后呢,我们去索引里面匹配,把这些id找出来,然后做个交集,当然还会计算一下匹配程度,然后拿到id之后,我们再根据我们原来的数据表去找到对应的一个行。

其他索引

同样的,在检索的其他索引的时候,工作原理是类似的。比如我们经常用的数字ID主键之类的,用B+ 树进行一个实现。
我们的一个表的数据大概是这样放的:(假设用B+ 树构建索引)
{id,数据}
那么之后的话我们构建一个索引。大概是这样的:
在这里插入图片描述

我们这样的数据节点,放到这样一颗树当中,这样就比遍历方便多了。

所以的话,这里你应该可以理解为什么要用B+ 树了,B树和B+ 树是类似的,只是B+ 树多了一个子节点间的连接,就是这样呗:
{id,数据,nexid} 为啥只需要nexid, 这个应该不需要我多说了吧。为什么是B树,不是其他树也可以理解了。

构建

okey,扯了一下犊子,我们继续来说说,如何使用到MySQL进行到一个检索。
那么首先要做的就是添加我们刚刚说的全文检索的索引。这里的话,我要说明的就是,其实这个全文索引是很早就支持了的,但是对于中文的分词是没有实现的,这个就很尬了,当然以前的解决方案就是搞个拼音去,但是>=5.7以后支持了中文分词,这个就棒。

ALTER TABLE `table_name` ADD FULLTEXT ( `col` )

如果不是innoDB,那么可以使用这个进行切换,当然直接用图形化软件也可以,应该没人喜欢怼命令。

ALTER TABLE my_table ENGINE = InnoDB;

实例

那么按照我这个为例,那么我先对博文进行一个创建,对标题进行创建全文检索。

ALTER TABLE blogs_blog ADD FULLTEXT INDEX ft_blog  (`blog_title`) WITH PARSER ngram;

ft_blog: 所以名字
blog_title: 字段名字

这个可以搞多个字段哈。

那么接下来我们做一个简单的查询。

SELECT * FROM blogs_blog WHERE blogid>3 and MATCH(blog_title) AGAINST ('问题' IN NATURAL LANGUAGE MODE)

那么这里可以看到一个结果。
在这里插入图片描述
同样的我们刚刚说了他会做一个匹配,所以的话,我们可以看到它和全部进行匹配的结果

SELECT blogid as id, MATCH (blog_title) AGAINST ('问题' IN NATURAL LANGUAGE MODE) AS score FROM blogs_blog;

在这里插入图片描述
这里的话,其实是把全部的数据进行了一个评分。所以本来人家就慢,数据多,需要计算的得分多,单条数据的量有多,算得就慢。当然主要消耗还是在IO这里,但是放内存烧钱啊。

代码

那么接下来的话,我们就来简单的实现一下这个东西,这里比较遗憾的,虽然MybatisPlus可以满足大量的查询构建,但是对于这个,咱们还是需要自己手动去编写SQL。

这里的话也是简单一点,由于咱们这个SQL的话比较长,比较复杂,所以的话,我把这个SQL给放到了这个xml文件 当中。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


    <mapper namespace="com.huterox.whitehole.whiteholeblog.dao.BlogDao">

    <!--sql-->

        <select resultType="com.huterox.whiteholecould.entity.blog.BlogEntity" id="fullMatch">
            SELECT *
            FROM (SELECT * FROM blogs_blog WHERE status=1) as a
            WHERE MATCH(blog_title) AGAINST (#{key} IN NATURAL LANGUAGE MODE)
            ORDER BY fork_number DESC,collect_number DESC,like_number DESC, view_number DESC
            Limit 0,5
        </select>

</mapper>

之后的话,我们在页面测试一下就好了。
在这里插入图片描述
可以发现,接口返回了正常的数据。

搜索流程

okey,那么之后的话,就是我们需要去实现这样的一个功能,我们直接以csdn为例子吧。
当你在搜索栏进行搜索的时候呢,我们需要跳出这样的提示。
在这里插入图片描述
当然在CSDN这个是对搜索做了一个统计,对这个做了一个维护检索。不过我们这边是直接对内容进行搜索。然后下拉列表给出提示,然后去进行搜索。

那么逻辑的话,我这里是这样的。

在这里插入图片描述

搜索

所以的话,我们其实是有两个接口的,那么接下来的就是全文检索的接口,这个接口比较特别的就是,我们需要做一个分页。那么对应分页,由于这个Sql比较复杂,因此我们还是手写SQL,那么这块的话,由于我们并不是需要展示全部的搜索结果,所以的话,完全没有必要做物理分页,那么我们就做一个逻辑分页,也就是内存分页。那么首先我们依然需要写sql.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


    <mapper namespace="com.huterox.whitehole.whiteholeblog.dao.BlogDao">

    <!--sql-->
    <select id="fullSearch" resultType="com.huterox.whiteholecould.entity.blog.BlogEntity">
        SELECT *
        FROM (SELECT * FROM blogs_blog WHERE status=1) as a
        WHERE MATCH(blog_title) AGAINST (#{key} IN NATURAL LANGUAGE MODE)
        ORDER BY fork_number DESC,collect_number DESC,like_number DESC, view_number DESC
            Limit 0,#{size}
    </select>

    </mapper>

然后的话我们需要实现一个逻辑分页。

        String key = blogSearchQ.getKey();
        Integer page = blogSearchQ.getPage();
        Integer limit = blogSearchQ.getLimit();

//        这里我们实现的逻辑分页,而不是物理分页,首先是物理分页要做两次查询,然后是,我们
//        匹配的结果不可能全部给用户展示出来的,
        List<BlogEntity> blogEntities = blogDao.fullSearch(key,50);
        int end = Math.min(page * limit, blogEntities.size());
        List<BlogEntity> entities = blogEntities.subList((page - 1) * limit, end);
        PageUtils pageUtils = new PageUtils(
                entities,
                blogEntities.size(),
                limit,
                page
        );
        return R.ok().put("page",pageUtils);

最多查询50条数据,然后分页给你。

然后这个PageUtis其实就是把MP的Ipage拿了过来,只是我这边重写了一些方法,所以做了提取。

public class PageUtils implements Serializable {
	private static final long serialVersionUID = 1L;
	/**
	 * 总记录数
	 */
	private int totalCount;
	/**
	 * 每页记录数
	 */
	private int pageSize;
	/**
	 * 总页数
	 */
	private int totalPage;
	/**
	 * 当前页数
	 */
	private int currPage;
	/**
	 * 列表数据
	 */
	private List<?> list;
	
	/**
	 * 分页
	 * @param list        列表数据
	 * @param totalCount  总记录数
	 * @param pageSize    每页记录数
	 * @param currPage    当前页数
	 */
	public PageUtils(List<?> list, int totalCount, int pageSize, int currPage) {
		this.list = list;
		this.totalCount = totalCount;
		this.pageSize = pageSize;
		this.currPage = currPage;
		this.totalPage = (int)Math.ceil((double)totalCount/pageSize);
	}

	/**
	 * 分页
	 */
	public PageUtils(IPage<?> page) {
		this.list = page.getRecords();
		this.totalCount = (int)page.getTotal();
		this.pageSize = (int)page.getSize();
		this.currPage = (int)page.getCurrent();
		this.totalPage = (int)page.getPages();
	}

	public int getTotalCount() {
		return totalCount;
	}

	public void setTotalCount(int totalCount) {
		this.totalCount = totalCount;
	}

	public int getPageSize() {
		return pageSize;
	}

	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}

	public int getTotalPage() {
		return totalPage;
	}

	public void setTotalPage(int totalPage) {
		this.totalPage = totalPage;
	}

	public int getCurrPage() {
		return currPage;
	}

	public void setCurrPage(int currPage) {
		this.currPage = currPage;
	}

	public List<?> getList() {
		return list;
	}

	public void setList(List<?> list) {
		this.list = list;
	}
	
}

ElasticSearch 实现

okey,到了咱们的主角了,当然如果你觉得前面的就够了,大可不必用ES,我这个就是纯纯的怼技术栈了属于是。没办法你只要用了分布式,那么nacos,这种中间件, 分布式事务这种中间件,就是必不可少的,这些就是额外的硬件成本,如果在上ES,那么成本继续上升。

那么这里的话,我选择使用Elastic-Easy来实现。(其实Python早就有这种类型的ORM框架了,只有Java先前还在坚守半自动,少扯底层,容易扯到蛋,又不是没有提供原生API,自己调去)

那么在在这里的话,需要考虑的问题就是,我们需要同时把我们的这个数据进行更新到我们的ES当中,那么这里的更新也是两种方式,增量更新和全量更新。也就是当数据修改,插入是更新一下,或者,搞个定时任务,对MySQL里面的全部数据更新到ES当中。

那么这里对应的是三种解决方案,首先对于增量更新,我们肯定是异步的,对应全量,就分为两种喽,同步与异步。

这个的话,方案比较多,所以我选择不烧钱的方案,我们自己实现一个增量的更新。

环境配置

在此之前,如果你想要详细了解elastic-easy的话,那么可以查阅官方文档:https://www.easy-es.cn/pages/ec7460/
我们这边的话就只是用到了我们需要的东西,当然这边还是做演示哈。然后文档的话,没有覆盖到很多操作,如果你对于ES有了解的话,那么直接进入它的源码地址,里面的test有样例:https://gitee.com/dromara/easy-es

那么这里我们要做的非常简单,导入依赖:

        <dependency>
            <groupId>cn.easy-es</groupId>
            <artifactId>easy-es-boot-starter</artifactId>
            <version>1.0.2</version>
        </dependency>

然后编写elastic的配置:
在你的application.yml当中

easy-es:
  address: 66.6666.6666.66:9200
  username: elastic #es用户名,若无则删去此行配置
  password: 大年三十博主生日(狗头) #es密码,若无则删去此行配置

编码

这个非常简单,先写一个实体类,我这里的话是把博文的标题和博文的简介给搞进去了,博文内容的话,我没有搞进去,内容太多了。
我的实体类是这样的:

@Data
@IndexName("esblogmodel")
public class EsBlogModel {

    /**
     * es中的唯一id
     */
    private String id;

    /**
     * 文章标题
     */
    @HighLight()
    @IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_SMART, searchAnalyzer = Analyzer.IK_MAX_WORD)
    private String title;
    /**
     * 博文简介
     */
    @HighLight()
    @IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_SMART, searchAnalyzer = Analyzer.IK_MAX_WORD)
    private String info;

    private Long blogid;

}


这里的话还做了一个高亮,如果是以前的话,看到那些API我就头大,边查文档边写可不好玩。

然后在创建一个Mapper。
在这里插入图片描述

@Component
public interface EsBlogModelMapper extends BaseEsMapper<EsBlogModel> {


}

很多操作的话其实和MybatisPlus很像,作者也说了,就是仿造MP搞的。

然后记得别忘了在启动类,或者配置类,搞一个扫描路径。

xxxx
@EsMapperScan("xxxx.es.mapper")
public class WhiteholeBlogApplication {

    public static void main(String[] args) {
        SpringApplication.run(WhiteholeBlogApplication.class, args);
    }

}

然后的话就可以先愉快的happy了。

查询与插入

其实这个,API接口和MP差不多,操作也类似,所以几乎我们不需要做太多工作。 插入非常简单:

        EsBlogModel esBlogModel = new EsBlogModel();
        esBlogModel.setBlogid(3L);
        esBlogModel.setInfo("WhiteHole简介");
        esBlogModel.setTitle("WhiteHole简介,白洞技术交流社区");
        esBlogModelMapper.insert(esBlogModel);

这样做我就完成了插入,当然使用前我们需要注入:

    EsBlogModelMapper esBlogModelMapper;

    @Autowired
    public void setEsBlogModelMapper(EsBlogModelMapper esBlogModelMapper){
        
        this.esBlogModelMapper = esBlogModelMapper;
        String indexName = "esblogmodel";
        if(!esBlogModelMapper.existsIndex(indexName)){

            Boolean index = esBlogModelMapper.createIndex();
            assert index;

        }

    }

不过这里的话其实就没必要使用这段代码了:

		String indexName = "esblogmodel";
        if(!esBlogModelMapper.existsIndex(indexName)){
            Boolean index = esBlogModelMapper.createIndex();
            assert index;

        }

因为在insert的时候,如果没有它会自己创建,先前看文档的时候没看仔细,有个大图:
在这里插入图片描述

之后的话是查询,这个查询也是简单的:

        LambdaEsQueryWrapper<EsBlogModel> esQueryWrapper = new LambdaEsQueryWrapper<>();
        esQueryWrapper.multiMatchQuery(key,EsBlogModel::getTitle,EsBlogModel::getInfo);
        esQueryWrapper.limit(50);
        List<EsBlogModel> esBlogModels = esBlogModelMapper.selectList(esQueryWrapper);

        return R.ok().put("list",esBlogModels);

最后的话我们做一个简单验证:
在这里插入图片描述
可以发现返回的结果也是有高亮的。

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

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

相关文章

day19|669. 修剪二叉搜索树、108.将有序数组转换为二叉搜索树、538.把二叉搜索树转换为累加树

669. 修剪二叉搜索树 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即&#xff0c;如果没有被移除&#xff0c;原有的父代…

生物信息【蛋白序列对比blosum】

参考学习&#xff1a;传统蛋白质序列比对算法 - 知乎 (zhihu.com) 一、蛋白序列同源、相似 同源”&#xff08;homology&#xff09;和“相似”&#xff08;similarity&#xff09;&#xff1a; 同源是指有相同的祖先&#xff0c;在这个意义上&#xff0c;无所谓同源的程度&…

MybatisPlus学习笔记(二)

分页查询 分页在网站使用十分之多1.原始的limit进行分页2.pageHelper第三方插件3.MP内置的分页插件如何使用 1.配置拦截器组件 2.使用page对象 删除操作 逻辑删除 物理删除&#xff1a;从数据库中直接移除逻辑删除&#xff1a;在数据库中没有被删除&#xff0c;而是通…

Centos 7升级系统内核版本

步骤一&#xff1a;检查内核版本 [rootmaster ~]# uname -rs Linux 3.10.0-1160.el7.x86_64 步骤二&#xff1a;升级内核 CentOS 允许使用 ELRepo&#xff0c;这是一个第三方仓库&#xff0c;可以将内核升级到最新版本 rpm --import https://www.elrepo.org/RPM-GPG-KEY-elr…

二叉树知识锦囊(二)

作者&#xff1a;爱塔居 专栏&#xff1a;数据结构 作者简介&#xff1a;大三学生&#xff0c;希望和大家一起进步&#xff01; 文章目录 文章目录 一、二叉树的存储 二、二叉树的遍历&#xff08;重点&#xff09; 2.1 前序遍历 2.2 中序遍历 2.3 后序遍历 2.4 层序遍历 2.5 小…

AX7A200教程(2): DDR3仿真平台搭建(二)

本章主要新建ddr3工程&#xff0c;然后将官方的ddr3仿真文件加入到工程里进行仿真&#xff0c;开发环境2020.1。新建ddr3_test工程新建ddr3工程顶层新建的ddr3_top顶层文件&#xff0c;目前还是空白的调用mig控制器&#xff0c;请参考我上一个章节&#xff0c;这里不在具体写调…

搜索引擎——Elasticsearch

文章目录1.ElasticSearch简介2.基本概念3.Elasticsearch概念-倒排索引4.Elasticsearch和Kibana的安装5.Elasticsearch入门操作5.1_cat5.2PUT&POST新增数据5.3PUT&POST修改数据5.4GET查询数据5.5DELETE删除数据5.7bulk批量操作5.6乐观锁字段6.Elasticsearch进阶操作6.1批…

蓝桥杯重点(C/C++)(随时更新)

目录 1 重点 1.1 取消同步&#xff08;节约时间&#xff0c;甚至能多骗点分&#xff0c;最好每个程序都写上&#xff09; 1.2 万能库&#xff08;可能会耽误编译时间&#xff0c;但是省脑子&#xff09; 1.3 蓝桥杯return 0千万别忘了写&#xff01;&#xff01; 1.4 …

【nginx】Windows下的常见问题踩坑

▒ 目录 ▒&#x1f6eb; 导读需求1️⃣ 安装2️⃣ 中文路径3️⃣ alias指定目录错误及原因正确示例&#x1f4d6; 参考资料&#x1f6eb; 导读 需求 最近写了一个前端应用&#xff0c;需要部署后&#xff0c;让别人能访问&#xff0c;想来想去&#xff0c;还是选择了目前最强…

TeeChart Pro VCL FMX 2022.36.220929 Crack

TeeChart Pro VCL FMX图表组件库提供数百种用于数据可视化的 2D 和 3D 图形样式、56 种数学、统计和财务函数供您选择&#xff0c;还有无限数量的轴和 30 个调色板组件。 快速浏览 跨平台的一个来源 针对 Windows、Web 和移动应用程序 Delphi VCL 图表组件 使用 Embarcadero 的…

elasticsearch搜索功能(二)

一、DSL查询文档&#xff08;P100&#xff09; 1. DSL查询分类 Elasticsearch提供了基于JSON的DSL&#xff08;Domain Specific Language&#xff09;来定义查询。常见的查询类型包括&#xff1a; &#xff08;1&#xff09;查询所有&#xff1a;查询出所有数据&#xff0c;一…

《Python程序设计(第3版)》[美] 约翰·策勒(John Zelle) 第 9 章 答案

《Python程序设计&#xff08;第3版&#xff09;》[美] 约翰策勒&#xff08;John Zelle&#xff09; 第 9 章 答案 答案仅供参考&#xff0c;若有错误欢迎指正 判断对错 计算机可以生成真正的随机数。Python 的 random 函数返回伪随机整数。自顶向下的设计也称为逐步求精。…

Redis优惠券秒杀 | 黑马点评

目录 一、全局唯一ID 1、全局ID生成器 二、实现秒杀下单 1、基本的下单功能 2、超卖问题 3、乐观锁解决并发问题 三、实现一人一单 1、思路分析 2、代码初步实现 3、关于锁的范围 4、关于事务失效 5、集群下线程并发问题 一、全局唯一ID 订单如果用自增长会存在…

QT动画实例代码QPropertyAnimation的应用

用QT实现动画&#xff0c;我们必定用到QPropertyAnimation&#xff0c;这里我们介绍几种情形的动画实现。如直线动画&#xff0c;曲线动画&#xff0c;路径动画。 一、基础知识 1、QPropertyAnimation的初始化 我们首先必须在包涵QPropertyAnimation的头文件或者模块&#x…

Android之WorkManager处理后台定时任务

WorkManager和Service并不相同&#xff0c;也没有直接的联系。Service是Android系统四大组件之一&#xff0c;它没有被销毁的情况下是一直保持在后台运行的。而WorkManager只是一个处理定时任务的工具&#xff0c;它可以保证即使在应用退出甚至手机重启的情况下&#xff0c;之前…

动手深度学习-pytorch数据操作

N维数组是机器学习和神经网络的主要数据结构创建数组需要形状&#xff1a;如3*4的矩阵每个元素的类型&#xff1a;例如32位浮点数每个元素的值&#xff1a;例如全是0.或者随机数数据操作首先&#xff0c;导入torch张量表示一个数值组成的数组&#xff0c;这个数组可能有多个维度…

acwing基础课——欧拉函数

由数据范围反推算法复杂度以及算法内容 - AcWing 常用代码模板4——数学知识 - AcWing 基本思想&#xff1a; 这里我们了解一下欧拉函数是什么以及用筛法求欧拉函数&#xff0c;我们先给出欧拉函数的定义&#xff1a; 然后我们了解一下互质的概念&#xff0c;只要两数的公因…

连你女朋友都能看懂的分布式架构原理!

目录 从一个新闻门户网站案例引入推算一下你需要分析多少条数据&#xff1f;黄金搭档&#xff1a;分布式存储分布式计算 这篇文章聊一个话题&#xff1a;什么是分布式计算系统&#xff1f; 一、从一个新闻门户网站案例引入 现在很多同学经常会看到一些名词&#xff0c;比如分…

CSDN官方模板

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

数据结构(栈)

目录 栈的定义 形象比喻 栈的相关术语 栈的抽象数据类型&#xff08;栈Stack的ADT&#xff09; 顺序栈 顺序栈类的声明 顺序栈类成员函数的实现 基本效率分析 顺序栈的应用&#xff08;小测试&#xff09; main.cpp 共享栈 双共享栈 链式栈 链式栈基本操作分析 链…