初识树(二叉树,堆,并查集)

   

  本篇博客将涉及到一些专业名词:结点、深度、根、二叉树、满二叉树、完全二叉树、堆、并查集等。


概要                                                                                                

        树型结构是一类重要的非线性数据结构。其中以树和二叉树最为常用,直观看来,树是以分支关系定义的层次结构。把它叫做“树”是因为它常看起来像一棵倒挂的树,也就是说它常是根朝上,而叶朝下的。

        树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树来形象表示。树在计算机领域中也得到广泛应用,如在操作系统中,可用树来表示文件系统的结构。又如在数据库系统中,树形结构也是信息的重要组织形式之一 [1]。(来源《百度百科》)

         这是一棵树,我们把它倒过来,就是数据结构中的树了。

         树和图乍一看比较相似,但是图里面每个顶点之间是有回路的,但是在树中没有回路,树就是没有回路的连通无向图。如下图所示,左边是树,右边树图。

        Linux中的"tree"指令、家族谱,思维导图,树在生活中很常见,因为它一目了然。 

         我们对树进行一些定义,树是指任意两个结点有且仅有一条路径的无向图。没有回路的连通无向图就是树。

        数据结构中,树的形态可以是多变的,如上图所示,左右两边是同一棵树地不同形态。在树中的每一个点称为一个结点,我们为了更好地运用树和确定树的形态,我们在树中指定一个特殊的结点——根结点(也称祖先),根结点确定了,数的形态基本就确定了。

        树中的每个结点都有自己的深度,左边图中1的深度是1,2和3的深度是2,剩下的4、5、6、7深度为3;

        两个相邻深度中的相邻几个结点(如2、4、5)可以区分为父结点、子结点和兄弟结点。其中2为4、5的父结点,那么4、5就是2的子结点;4、5互为兄弟结点。

        没有父结点的就是根结点,没有子结点的就是叶结点,其余的叫做内部结点。

二叉树 

        顾名思义,二叉树就是每个结点最多只有两个子结点,左边为左儿子,右边为右儿子。二叉树有很多规律,帮助我们更好地解决问题。比如结点i,它的左儿子就是i*2,右儿子是i*2+1,它的父结点就是i/2。(均为整型,向下取整)

         

        二叉树可分为满二叉树和完全二叉树,满二叉树中每个内部结点都有两个儿子,所有叶结点地深度是一样的,严格定义:一个深度为h,且有2^h-1个结点的二叉树

        完全二叉树指除了最右边位置上有一个或者几个叶节点缺失外,其余都是满的。严格定义:若设二叉树的深度为h,除h层外,其它各层的结点数都达到最大,从h层从右向左连续缺若干个结点。这样就保证了如果一个结点有右结点,那么它一定有左结点。满二叉树可以理解为是一种完美的完全二叉树。

        

        满二叉树类似形状:

 

        因为二叉树是有规律的,所以我们通过一个一维数组便可以存储整个树。 如下图:

         如果一个完全二叉树有$N$个结点那么这个完全二叉树的深度为\log_2N+1,简写为log_2N,及最多有log_2N层结点。完全二叉树的最典型的应用就是——堆。

堆——优先队列

        堆是一种特殊的完全二叉树,利用$N$个结点深度为log_2N的特点,优化算法的时间复杂度。

如下图所示就是一个堆:

        

        特殊之处就是所有父结点都比子结点要小(本图中圆圈内为值,里面是编号)。 这个就是最小堆,相反如果所有父结点都比子结点要大那就是最大堆。

        现在我们要找出n个数中的最小值,直接遍历的话时间复杂度是O(N),除了n个这个操作的时间复杂度就是O(N^2),我们用堆就可以很好的优化。

        我们利用堆,就是在一次次构造堆,所以我认为会学会了构造堆就可以应用了。

最小堆

        我们现在有14个数:99、5、36、7、22、17、46、12、2、19、25、28、1、92。我们现在要构造一个最小堆。我们整个过程几乎只需要做一件事,那就是向下调整,保证每个子节点都小于父结点。(初始树如下)

        那么如何保证每个子节点都小于父结点呢?我们利用就像俄罗斯套娃的思路,从最后一个父结点开始,遍历每一个父节点,保证每一个父结点都小于它的子结点(兄弟结点之间的大小关系不用管)。也就是从N/2开始到第一个结点,这些都是父结点,通过这个这种方法我们便构造了一个最小堆,并且根结点一定是最小的这一点特别重要)。

        

        我们取出一部分来演示,我们从第N/2个结点开始(值为46结点),分别与其左右子结点比较(其只有一个结点,即左结点),方框内为每次形成的最小堆。注意红色和绿色箭头部分,如果我们按照红色箭头来走,那么值为1的结点的左儿子形成的堆就不是最小堆。

        因为在这个步骤中虽然保证了值为1的结点形成的是最小堆,但是由于交换的值36没有继续向下调整,不能保证下边还是最小堆,所以我们需要将每次交换的值继续向下调整,直到无法向下调整为止(绿色箭头所示)。

        这样我们就构成了一个最小堆,构成最小堆之后,我们可以交换根结点和第n个结点的值,然后输出n结点的值,再执行n--和将根节点向下调整;每次n--便是依次循环,直到所有结点全部输出,输出结点的顺序就是从小到大排列的顺序了。(这一点类似于队列出队)

        这样我们就通过一遍遍构建最小堆的方法,从小到大输出数值,这便是最小堆排序。

最大堆

        相比较最小堆,我认为更常用的是最大堆排序,因为最小堆是将原数组的数一个个全部取出来输出,如果想储存的话,还需要一个数组,而最大堆排序后直接从1到N输出原数组就是排序后的结果了。

        

        同一个数组构造最大堆,还是将根结点与n结点交换,还是n- -,还是向下调整,只不过每次是父结点都大于子结点,根节点为最大值,故每次交换后n结点都是最大值(n越来越小),这样的到的原数组就是从小到大排序了。 

        向下调整和向上调整 

        上边已经理清了堆排序的思路,现在还有一个重要的问题们就是如何向下调整(也可以向上调整)

        向下调整的话,因为叶节点没有子结点,所以我们只需要从N/2到1的每个结点进行向下调整就行了。每次的思路就是 $i$ 结点(1 \leq i \leq \frac{N}{2})、i*2结点、i*2+1结点,这三个结点选出最大的(最小堆就是选最小的),如果需要交换,就将新得到的子结点作为父结点继续向下交换,直到不能交换为止。 

/*最大堆*/
void siftdown(int i,int n,int *h)/*向下调整*/
{
    int t,flag=0;
    while (i*2<=n && flag==0)
    {
        if (h[i] < h[i*2])/*与左儿子比较*/
            t=i*2;/*大的是左儿子*/
        else    
            t=i;/*大的是父亲*/
        if(i*2+1 <= n)
        {
            if (h[t] < h[i*2+1])/*将t再与右儿子比较*/
                t=i*2+1;      
        }
        if (t!=i)
        {
            swap(t,i,h);/*需要交换,将交换过的子节点作为父结点继续向下交换*/
            i=t;
        }
        else 
            flag = 1;/*不需要再继续循环交换*/
    }   
}

        向上调整其实就是反过来,只不过它的范围要从N到2结点,故需要遍历N-1次,相比较向上调整时间多一点,因为只有根结点没有父结点。

void siftup(int i,int *h)/*向上调整*/
{
    int flag=0;
    if (i==1)
    {
        return ;
    }
    while (i!=1 && flag == 0)
    {
        //判断是否比父节点小
        if (h[i]<h[i/2])
        {
            swap(i,i/2,h);
        }
        else 
            flag=1;
        i=i/2;
    }  
}

 最小堆排序完整代码

输入:

14

99 5 36 7 22 17 46 12 2 19 25 28 1 92

输出:

1  2  5  7 12 17 19 22 25 28 36 46 92 99

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void swap(int x,int y,int *num);
void siftdown(int i,int n,int *h)/*向下调整*/
{
    int t,flag=0;
    while (i*2<=n && flag==0)
    {
        if (h[i] > h[i*2])
            t=i*2;
        else    
            t=i;
        if(i*2+1 <= n)
        {
            if (h[t] > h[i*2+1])/*兄弟结点之间比较或者父子之间比较,保证与最小的交换*/
                t=i*2+1;      
        }
        if (t!=i)
        {
            swap(t,i,h);
            i=t;
        }
        else 
            flag = 1;
    }
    
}

void swap(int x,int y,int *num)
{
    int t;
    t=num[x];
    num[x]=num[y];
    num[y]=t;
}

int main() 
{
    int n;
    scanf("%d",&n);
    int *num=(int *)malloc(sizeof(int)*(n+1));
    memset(num,0,sizeof(int)*n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d",&num[i]);
    }
    for (int  i = n/2; i>=1; i--)
    {
        for (int j = 1; j <= n; j++)
        {
            printf("%8d",num[j]);
        }
        putchar('\n');
        siftdown(i,n,num);
    }
        for (int j = 1; j <= n; j++)
        {
            printf("%8d",num[j]);
        }
        putchar('\n');

    for (int i = 1,n_falg=n; i <= n; i++)
    {
        int t;
        t=num[1];
        num[1]=num[n_falg];
        n_falg--;
        siftdown(1,n_falg,num);
        printf("%3d",t);
    }
    return 0;
}

 

 最大堆排序完整代码

输入:

14

99 5 36 7 22 17 46 12 2 19 25 28 1 92

输出:

1  2  5  7 12 17 19 22 25 28 36 46 92 99

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void swap(int x,int y,int *num);
void siftdown(int i,int n,int *h)/*向下调整*/
{
    int t,flag=0;
    while (i*2<=n && flag==0)
    {
        if (h[i] < h[i*2])/*与左儿子比较*/
            t=i*2;/*大的是左儿子*/
        else    
            t=i;/*大的是父亲*/
        if(i*2+1 <= n)
        {
            if (h[t] < h[i*2+1])/*将t再与右儿子比较*/
                t=i*2+1;      
        }
        if (t!=i)
        {
            swap(t,i,h);/*需要交换,将交换过的子节点作为父结点继续向下交换*/
            i=t;
        }
        else 
            flag = 1;/*不需要再继续循环交换*/
    }   
}

void swap(int x,int y,int *num)
{
    int t;
    t=num[x];
    num[x]=num[y];
    num[y]=t;
}

int main() 
{
    int n;
    scanf("%d",&n);
    int *num=(int *)malloc(sizeof(int)*(n+1));
    memset(num,0,sizeof(int)*n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d",&num[i]);
    }
    for (int  i = n/2; i>=1; i--)
    {
        for (int j = 1; j <= n; j++)
        {
            printf("%8d",num[j]);
        }
        putchar('\n');

        siftdown(i,n,num);
    }
    int n_flag=n;
    while (n_flag>1)
    {
        for (int i = 1; i <= n; i++)
        {
            printf("%5d",num[i]);
        }
        putchar('\n');
        
        swap(1,n_flag,num);
        n_flag--;
        siftdown(1,n_flag,num);

    }
    for (int i = 1; i <= n; i++)
    {
        printf("%3d",num[i]);
    } 
    return 0;
}

 并查集

        并查集(Disjoint Set Union,DSU)是一种森林结构,所谓森林结构,就是由多棵树组成的数据结构。

并查集支持两种操作:

  • 合并(Union):合并两个元素所属集合(合并对应的树)
  • 查询(Find):查询某个元素所属集合(查询对应的树的根节点),这可以用于判断两个元素是否属于同一集合

        我们直接看一道例题:

        擒贼先擒王

        快过年了,犯罪分子们也开始为年终奖“奋斗”了,小哼的家乡出现了多次抢劫事件。由于强盗人数过于庞大,作案频繁,警方想查清有几个犯罪团伙不太容易,不过警察收集到了一些线索,需要咱们帮忙分析一下:

        现在有10个强盗,关系如下:

1号强盗与2号强盗是同伙。

3号强盗与4号强盗是同伙。

5号强盗与2号强盗是同伙。

4号强盗与6号强盗是同伙。

2号强盗与6号强盗是同伙。

8号强盗与7号强盗是同伙。

9号强盗与7号强盗是同伙。

1号强盗与6号强盗是同伙。

2号强盗与4号强盗是同伙。

        有一点需要注意:强盗同伙的同伙也是同伙,你能帮助警方查出有多少个独立的犯罪团伙嘛?

        这十个强盗就可以看成是10颗树,如果两个强盗之间是同伙就把他们连线,形成一片森林,最后有几个森林就代表有几个犯罪团伙。思路很好理解,接下来就是通过代码实现。

        我们用一个数组num来表示这个树,数组大小为n,每个数的初始化为自己的下标,代表自己的父节点为自己,即自己构成一个树林,初始的时候有十个树林(每个森林都是以树这个数据结构储存)。

        这里我们就规定如果x号强盗与y号强盗是同伙。那么x就是y的父节点。(成为“靠左定则”)

num[y]=x;

        通过这一个操作,我们边=便已经形成了上边那张图,我们通过肉眼区分,直到这是三个森林,但怎么样让计算机知道呢?

        这里采用的方法是通过判断num[y]==y来做的,如果等于,那就代表有一个森林,因为一个森林中只有一个根结点,只有根结点才指向自己

        这个时候我们就需要对上边大代码进行修改了,我们将上边的图稍微整理一下就会发现问题:这更像是一个图,而不是树!        

         那么我们就需要把这个图转化为树,来解决这个问题,怎么转化呢?我们要保证每一个结点只有一个父节点

        这里我们重新模拟一下这个过程:

1号强盗与2号强盗是同伙。

3号强盗与4号强盗是同伙。

5号强盗与2号强盗是同伙。

        再执行第三个线索的时候,发现2结点已经有一个父结点了,不能再有第二个父结点了,这里2已经和别的结点构成一个森林,森林中只有根结点没有父结点,这里我们采用的方法是将2结点的根结点作为y,5结点为x,做 $num[y]=x$ (如果 x 也有父节点,那么也是找到这个森林的根节点作为 y) ,这样便完美解决了这个问题,如下图所示:

     

        通过这样的方法执行,便形成了树: 

        代码实现: 

int getf(int v,int *num)
{
    if (num[v]==v)/*v结点就是根结点*/
    {
        return v;
    }
    else
    {
        num[v]=getf(num[v],num);/*寻找v的父结点*/
        return num[v];
    }
    
}

 void Union(int x,int y,int *num)
 {
    int t1=getf(x,num);/*判断x是不是本森林的根节点*/
    int t2=getf(y,num);/*判断y是不是本森林的根节点*/
    if (t2!=t1)/*t1与t2不属于同一个森林*/
    {
        num[t2]=t1;
    }
    return ;
 }

        输入:m+1行,第一行输入n,m代表罪犯人数和显示,接下来m行输入x,y代表x号强盗与y号强盗是同伙

 11 10

1 2

3 4

5 2

4 6

2 6

7 11

8 7

9 7

9 11

1 6

输出:一个数字代表犯罪团伙 

3

完整代码:

 #include<stdio.h>
 #include<stdlib.h>
 #include<string.h>

int getf(int v,int *num)
{
    if (num[v]==v)/*v结点就是根结点*/
    {
        return v;
    }
    else
    {
        num[v]=getf(num[v],num);/*寻找v的父结点*/
        return num[v];
    }
    
}

 void Union(int x,int y,int *num)
 {
    int t1=getf(x,num);/*判断x是不是本森林的根节点*/
    int t2=getf(y,num);/*判断y是不是本森林的根节点*/
    if (t2!=t1)/*t1与t2不属于同一个森林*/
    {
        num[t2]=t1;
    }
    return ;
 }

 int main()
 {
    int n,m;
    scanf("%d%d",&n,&m);
    int * dis=(int *)malloc(sizeof(int)*(n+1));
    for (int i = 0; i <= n; i++)
    {
        dis[i]=i;
    }
    for (int i = 1,x,y; i <= m; i++)
    {
        scanf("%d%d",&x,&y);
        Union(x,y,dis);/*靠左定则*/
    }
    int sum=0;
    for (int i = 1; i <= n; i++)
    {
        if (dis[i]==i)
        {
            // printf("dis=%d\n",dis[i]);/* 根结点 */
            sum++;
        }
            
    }
    printf("%d",sum);
    return 0;
 }

参考文献

《啊哈!算法》

算法导论之树形数据结构——并查集 - 知乎

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

知从科技闪耀汽车智能底盘大会:共探软件安全新篇章

在汽车科技蓬勃发展的浪潮中&#xff0c;智能底盘技术正成为引领行业变革的关键力量。11月27日-28日&#xff0c;盖世汽车 2024 第四届汽车智能底盘大会盛大召开&#xff0c;上海知从科技有限公司受邀出席此次盛会&#xff0c;与众多汽车领域的精英齐聚一堂&#xff0c;共话智能…

TCP Analysis Flags 之 TCP Spurious Retransmission

前言 默认情况下&#xff0c;Wireshark 的 TCP 解析器会跟踪每个 TCP 会话的状态&#xff0c;并在检测到问题或潜在问题时提供额外的信息。在第一次打开捕获文件时&#xff0c;会对每个 TCP 数据包进行一次分析&#xff0c;数据包按照它们在数据包列表中出现的顺序进行处理。可…

241205_使用本地vosk模型实现流式语音识别

241205_使用本地vosk模型实现流式语音识别 上一篇我们使用了vosk模型实现了本地的语音识别&#xff0c;但是存在一个严重的问题&#xff0c;因为我们采用的是整段音频录制结束之后再进行转文字并进行关键字检索&#xff0c;就会导致识别不实时&#xff0c;在环境噪音较为复杂&…

CMake笔记之在CMakeLists.txt文件中开启Debug模式

CMake笔记之在CMakeLists.txt文件中开启Debug模式 code review! 文章目录 CMake笔记之在CMakeLists.txt文件中开启Debug模式1.设置 CMake 的构建类型2.添加编译器的调试选项3.使用 CMAKE_CXX_STANDARD (可选)4.编译和构建5.针对多配置生成器6.最终示例 CMakeLists.txt 1.设置 …

VINS_MONO视觉导航算法【一】基础知识介绍

文章目录 VINS-Mono其他文章说明简介单目相机存在的尺度不确定问题缺乏深度信息尺度等价性对极几何和三角化平移和深度的关系解决尺度不确定问题的方法视觉惯性里程计&#xff08;VIO&#xff09;初始尺度估计持续尺度校正 摄像头数据处理直接法&#xff08;Direct Method&…

【CC2530开发基础篇】光敏和热敏传感器

一、前言 1.1 开发背景 本实验通过CC2530单片机接入光敏传感器和热敏传感器&#xff0c;进行数据采集与检测&#xff0c;并将检测结果通过串口终端输出。光敏传感器和热敏传感器是常见的环境感知设备&#xff0c;分别用于测量光强和温度。在实际应用中&#xff0c;这些传感器…

计算机网络-GRE基础实验二

前面我们学习了GRE隧道的建立以及通过静态路由指向的方式使得双方能够网络互联&#xff0c;但是通过静态路由可能比较麻烦&#xff0c;GRE支持组播、单播、广播因此可以在GRE隧道中运行动态路由协议使得网络配置更加灵活。 通过前面的动态路由协议的学习我们知道动态路由协议都…

中建海龙:科技创新引领建筑业革新,铸就行业影响力

在建筑业这个古老而又充满活力的行业中&#xff0c;中建海龙科技有限公司&#xff08;以下简称“中建海龙”&#xff09;凭借其卓越的科技实力和一系列荣誉奖项&#xff0c;正逐步确立其在建筑工业化领域的领导地位&#xff0c;并对整个行业产生了深远影响。 中建海龙自成立以来…

Y20030028 JAVA+SSM+MYSQL+LW+基于JAVA的考研监督互助系统的设计与实现 源代码 配置 文档

基于JAVA的考研监督互助系统 1.项目描述2. 课题开发背景及意义3.项目功能4.界面展示5.源码获取 1.项目描述 随着高等教育的普及和就业竞争的加剧&#xff0c;越来越多的学生选择继续深造&#xff0c;参加研究生入学考试。考研人数的不断增加&#xff0c;使得考研过程中的学习监…

HarmonyOS NEXT开发进阶(一):初识 HarmonyOS NEXT开发

文章目录 一、前言二、HarmonyOS NEXT 开发框架三、HarmonyOS NEXT开发指导3.1 Windows环境准备 四、项目拆解4.1 工程目录4.2 全局配置4.2.1 APP全局配置: AppScope层&#xff08;AppScope/app.json5&#xff09;4.2.3 签名全局配置 4.3 APP代码初始化4.4 APP签名文件配置4.5 …

【机器学习算法】——逻辑回归

目录 逻辑回归理解损失函数代码练习1. 房屋价格与面积的关系2.基于学生特征的录取概率预测 逻辑回归理解 逻辑回归是用来二分类的&#xff01; 是在线性回归模型之后加了一个激活函数&#xff08;Sigmoid)将预测值归一化到【0~1】之间&#xff0c;变成概率值。 一般计算其中一…

mongo开启慢日志及常用命令行操作、数据备份

mongo开启慢日志及常用命令行操作、数据备份 1.常用命令行操作2.mongo备份3.通过命令临时开启慢日志记录4.通过修改配置开启慢日志记录 1.常用命令行操作 连接命令行 格式&#xff1a;mongo -u用户名 -p密码 --host 主机地址 --port 端口号 库名&#xff1b; 如&#xff1a;连…

排序的事

排序的事 C语言实现C实现Java实现Python实现 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 输入n个不相同的正整数&#xff0c;每个数都不超过n。现在需要你把这些整数进行升序排序&#xff0c;每次可以交换两个数的位置&#xff0c;最少需…

.NET Framework修复工具

某些精简Windows系统或者第三方下载的改版Windows系统在安装.NET Framework的时候会出现类似下面的错误信息: 可以使用微软官方出的.NET Framework修复工具解决, 下载地址: 【免费】.NETFramework修复工具资源-CSDN文库 下载后运行即可修复系统里的.NET Framework

PyTorch 本地安装指南:全面支持 macOS 、 Linux 和 Windows 系统

PyTorch 本地安装指南&#xff1a;全面支持 macOS 、 Linux 和 Windows 系统 PyTorch 是一个功能强大的深度学习框架&#xff0c;支持高效的计算图和 GPU 加速&#xff0c;广泛应用于人工智能和机器学习项目中。本文从安装前的准备工作开始&#xff0c;详细介绍了如何使用 con…

【单片机基础知识】MCU三种启动方式(Boot选择)[主Flash/系统存储器(BootLoader)/嵌入式SRAM]

参考资料&#xff1a; MCU的三种启动方式 - EdgeAI Lab 立芯嵌入式的视频 在SRAM中运行代码 - EdgeAI Lab 利用 Boot 选择不同的启动方式&#xff1a; 根据不同的启动方式&#xff0c;将不同的地址(主 FLASH/系统存储器/嵌入式 SRAM)映射到 0x0000 0000(系统中断向量表) 上…

记录一个Flutter 3.24单元测试点击事件bug

哈喽&#xff0c;我是老刘 这两天发现一个Flutter 3.24版本的单元测试的一个小bug&#xff0c;提醒大家注意一下。 老刘自己写代码十多年了&#xff0c;写Flutter也6年多了&#xff0c;没想到前两天在一个小小的BottomNavigationBar 组件上翻了车。 给大家分享一下事件的经过。…

第四篇:k8s 理解Service工作原理

什么是service&#xff1f; Service是将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。 简单来说K8s提供了service对象来访问pod。我们在《k8s网络模型与集群通信》中也说过k8s集群中的每一个Pod&#xff08;最小调度单位&#xff09;都有自己的IP地址&#xff0c;都…

基于ResNet50和VGG16深度学习模型的阿尔茨海默病MRI图像分类与早期诊断研究

阿尔茨海默病&#xff08;AD&#xff09;是目前全球范围内最常见的神经退行性疾病之一&#xff0c;早期诊断对延缓疾病进程和改善患者生活质量至关重要。随着医学影像学的进步&#xff0c;基于MRI图像的阿尔茨海默病检测成为一种重要的研究方向。本文提出了一种基于深度学习的M…

【日常记录-Mybatis】PageHelper导致语句截断

1. 简介 PageHelper是Mybatis-Plus中的一个插件&#xff0c;主要用于实现数据库的分页查询功能。其核心原理是将传入的页码和条数赋值给一个Page对象&#xff0c;并保存到本地线程ThreadLocal中&#xff0c;接下来&#xff0c;PageHelper会进入Mybatis的拦截器环节&#xff0c;…