字符串处理【后缀数组】 - 原理2 后缀数组

news2024/11/20 6:13:02

字符串处理【后缀数组】 - 原理2 后缀数组

在字符串处理中,后缀树和后缀数组(Suffix Array)都是非常有力的工具。

后缀数组是后缀树的一个非常精巧的替代品,比后缀树容易实现,可以实现后缀树的很多功能,时间复杂度也不逊色,比后缀树所占用的空间也小很多。在算法竞赛中,后缀数组比后缀树更为实用。

【1】后缀数组的相关概念

① 后缀。

后缀指从某个位置开始到字符串末尾的一个特殊子串。字符串s 从第i 个字符开始的后缀被表示为Suffix(i ),也可以称之为下标为i 的后缀。字符串s =“aabaaaab”,其所有后缀如下:

  • Suffix(0)=“aabaaaab”
  • Suffix(1)=“abaaaab”
  • Suffix(2)=“baaaab”
  • Suffix(3)=“aaaab”
  • Suffix(4)=“aaab”
  • Suffix(5)=“aab”
  • Suffix(6)=“ab”
  • Suffix(7)=“b”

② 后缀数组。

将所有后缀都从小到大排序之后,将排好序的后缀的下标i 放入数组中,该数组就叫作后缀数组。将上面的所有后缀都按字典序排序之后,取其下标i ,即可得到后缀数组:

  • Suffix(3)=“aaaab”
  • Suffix(4)=“aaab”
  • Suffix(5)=“aab”
  • Suffix(0)=“aabaaaab”
  • Suffix(6)=“ab”
  • Suffix(1)=“abaaaab”
  • Suffix(7)=“b”
  • Suffix(2)=“baaaab”

后缀数组SA[]={3, 4, 5, 0, 6, 1, 7, 2}。

③ 排名数组。

排名数组指下标为i 的后缀排序后的名次,例如在上面例子中排序后的下标和名次。若rank[i ]=num,则下标为i 的后缀排序后的名次为num:

在这里插入图片描述

下标为3的后缀,排名第1,即rank[3]=1;排名第1的后缀,下标为3,即SA[1]=3。排名数组和后缀数组是互逆的,可以来回转换:

在这里插入图片描述

【2】后缀数组的构建思路

构建后缀数组有两种方法:DC3算法和倍增算法。DC3算法的时间复杂度为O (n ),倍增算法的时间复杂度为O (n logn )。一般n >10^6时,DC3算法比倍增算法运行速度快,但是DC3算法的常数和代码量较大,因此倍增算法比较常用。

采用倍增算法,对字符串从每个下标开始的长度为2^k 的子串进行排序,得到排名。k 从0开始,每次都增加1,相当于长度增加了1倍。当2^k ≥n 时,从每个下标开始的长度为2^k 的子串都相当于所有后缀。每次子串排序都利用上一次子串的排名得到。

[举个例子]

① 将字符串s (aabaaaab)从每个下标开始长度为1的子串进行排名,直接将每个字符转换成数字s [i ]-‘a’+1即可,如下图所示。

在这里插入图片描述

② 求解长度为2的子串排名。将上一次rank值的第i 个和第i +1个结合,相当于得到长度为2的子串的每个位置排名,然后排序,即可得到长度为2的子串排名。

在这里插入图片描述

③ 求解长度为2^2 的子串排名。将上一次rank值的第i 个和第i+2个结合,相当于得到长度为2^2 的子串的每个位置排名,排序后可得到长度为2^2 的子串排名。

在这里插入图片描述

④ 求解长度为2^3 的子串排名。将上一次rank值的第i 个和第i+4个结合,相当于得到长度为2^3 的子串的每个位置排名,排序后可得到长度为2^3 的子串排名

在这里插入图片描述
第4步和第3步的结果一模一样,实际上,若在rank没有相同值时已经得到了后缀排名,就不需要再继续运算了。因为根据字符串比较的规则,两个字符串的前面几个字符已经分出大小,后面无须判断。

将排名数组转换为后缀数组,排名第1的下标为3,排名第2的下标为4,排名第3的下标为5,排名第4的下标为0,排名第5的下标为6,排名第6的下标为1,排名第7的下标为7,排名第8的下标为2,因此SA[]={3, 4, 5, 0, 6, 1, 7, 2}。

因为倍增算法,每次比较的字符数都翻倍,因此长度为n 的字符串最多需要O (logn )次排序,除了第1次排序,后面都是对二元组进行排序,若采用快速排序,则每次都需要O (n logn ),总时间复杂度为O(n log2 n );而使用基数排序,每次的时间复杂度都为O (n ),总时间复杂度都为O (n logn )。因此,这里采用基数排序实现。

【3】后缀数组的实现

① 将每个字符都转换为数字存入ss[],并通过参数传递赋值给x[]数组(相当于排名数组rank[]),进行基数排序。为了防止比较时越界,在末尾用0封装。

在这里插入图片描述

执行基数排序,按排名顺序将x []数组的下标放入桶中。

在这里插入图片描述

将排序结果(下标)存入后缀数组sa[]中。

在这里插入图片描述
[算法代码]

for(i = 0 ; i < m ; i ++){ // 基数排序
	c[i] = 0;
}
for(i = 0 ; i < n ; i ++){
	c[x[i] == ss[i]] ++;
}

for(i = 1; i < m; i ++){
	c[i] += c[i - 1];
}
for(i = n - 1; i >= 0 ; i --){
	sa[--c[x[i]]] = i;
}

② 求解长度为2^k 的子串排名(k =1),将上一次排名结果的每一个都和后一个结合,然后排序,即可得到长度为2的子串排名。

求解思路: 利用上一次的排名x []前移错位(-k ),得到第2关键字的排序结果(下标)y [],将第2关键词的排序结果转换成名次,正好是第1关键字,对第1关键字进行基数排序得到sa[],利用x []和sa[]求解新的x []。

实现过程如下。

(1) 对第2关键字进行基数排序。第2关键字实际上就是上次排序时下标1-8的部分,可以直接读取上次的排序结果(下标)sa[],减1即可,因为第2关键字此时对应的下标和原来差一位。例如在x []数组中,第2个1原来的下标为1,现在结合后对应的下标为0。将下标8(值为00)排在最前面,后面直接读取sa[]-1。将第2关键字的排序结果(下标)存储在y []中。

在这里插入图片描述
算法代码:

p = 0;
for(i = n - k ; i < n; i++){
	y[p++] = i;  // 将补零的位置下标排在最前面
}
for(i = 0 ; i < n ; i ++){
	if(sa[i] >= k){
		y[p ++] = sa[i] - k;  //读取上次排序结果的下标
	}
}

(2) 将第2关键字的排序结果(下标)y []转换为排名,正好是第1关键字。

在这里插入图片描述
算法代码:

for(i = 0 ; i < n ; i++){
	wv[i] = x[y[i]] ; //将第2 关键字的排序结果转换为排名,正好是第 1 关键字
}

(3) 对第1关键字进行基数排序。按第1关键字的排名顺序将x []数组下标放入桶中。

在这里插入图片描述
将排序结果(下标)存入后缀数组sa[]中。

在这里插入图片描述

算法代码:

for(i = 0 ; i < m ; i ++) {  //基数排序
	c[i] = 0;
}
for(i = 0 ; i < n ; i ++){
	c[wv[i]] ++;
}

for(i = 1; i < m ; i ++){
	c[i] ++ c[i- 1];
}
for(i = n - 1; i >= 0 ; i --){
	sa[--c[wv[i]]] = y[i];
}

(4) 根据sa[]和x []数组计算新的排名数组(长度为2的子串排名)。因为要使用旧的x []数组计算新的x []数组,而此时y []数组已没有用,因此将x []与y []交换,swap(x , y )。此时的y []数组就是原来的x []数组,现在计算新的x []数组。

  • 令x [sa[0]]=0,即x [8]=0,表示下标为8的数{0 0}排名名次为0。
    在这里插入图片描述

  • sa[1]=0,sa[0]=8,比较y [0]≠y [8],因此x [0]=p ++=1;p初值为1,加1后p =2。
    在这里插入图片描述

  • sa[2]=3,sa[1]=0,比较y [0]=y [3]且y [1]=y [4],则下标3的名次应该和前一个下标0的名次相同,因为下标为0的二元组是子串aa(由原来的下标0、1组成),下标为3的二元组是子串aa(由原来的下标3、4组成),因此x [3]=p -1=1,p =2。
    在这里插入图片描述

  • sa[3]=4,sa[2]=3,比较y [3]=y [4]且y [4]=y [5],则下标4的名次应该和前一个下标3的名次相同,因此x [4]=p -1=1,p =2。
    在这里插入图片描述

  • sa[4]=5,sa[3]=4,比较y [4]=y [5]且y [5]=y [6],则下标5的名次应该和前一个下标4的名次相同,因此x [5]=p -1=1,p =2。
    在这里插入图片描述

  • sa[5]=1,sa[4]=5,比较y [5]=y [1]且y [6]≠y [2],则下标1的名次与前一个下标5的名次不同,因此x [1]=p ++=2,p =3。
    在这里插入图片描述

  • sa[6]=6,sa[5]=1,比较y [1]=y [6]且y [2]=y [7],则下标6的名次应该和前一个下标1的名次相同,因此x [6]=p -1=2,p =3。
    在这里插入图片描述

  • sa[7]=7,sa[6]=6,比较y [6]≠y [7],则下标7的名次与前一个下标6的名次不同,因此x [7]=p ++=3,p =4。
    在这里插入图片描述

  • sa[8]=2,sa[7]=7,比较y [7]=y [2]且y [8]≠y [3],则下标2的名次与前一个下标7的名次不同,因此x [2]=p ++=4,p =5。
    在这里插入图片描述

  • 第1次排序的结果为sa[],第1次排名的结果为x []。

算法代码:

swap(x, y); //y 数组已经没有用了,更新x 时需要使用x 自身的数据,因此x、y 交换,将x 数组放入 y数组中再更新x

p = 1 , x[sa[0]] = 0;
for(i = 1; i < n ; i ++){
	x[sa[i]] = (y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k]) ? p - 1 : p ++;
}

③ 求解长度为2 k 的子串的排序名次(k =2)。将上一次的排名结果x []的第i 个和第i +2个结合,相当于得到长度为4的子串的每个位置排名,排序后可得到长度为4的子串排名,如下图所示。此时,排名数组中的名次各不相同,无须继续排名,算法结束。

在这里插入图片描述

④ 排名数组x []和后缀数组sa[]如下图所示,两者互逆,x[4]=2,sa[2]=4,末尾不需要再用。

在这里插入图片描述

【4】最长公共前缀( LCP)

最长公共前缀(Longest Common Prefix,LCP)指两个字符串长度最大的公共前缀,例如s 1 =“abcxd”,s 2 =“abcdef”,LCP(s 1 , s)=“abc”,其长度为3。

字符串s =“aabaaaab”,suffix(sa[i ])表示从第sa[i ]个字符开始的后缀,其排名为i 。例如,sa[3]=5,suffix(sa[3])=“aab”,表示从第5个字符开始的后缀,其排名为3。height表示排名相邻的两个后缀的最长公共前缀的长度,height[2]=3表示排名第2的后缀和前一个后缀的最长公共前缀的长度为3。

在这里插入图片描述

height[i ]表示suffix(sa[i ])和suffix(sa[i -1])的最长公共前缀的长度。

性质1: 对于任意两个后缀suffix(i )、suffix(j ),若rank[i ]<rank[j ],则它们的最长公共前缀长度为height[rank[i ]+1],height[rank[i ]+2], …, height[rank[j ]]的最小值。

例如,suffix(4)=“aaab”,suffix(1)=“abaaaab”,rank[4]=2,rank[1]=6,它们的最长公共前缀长度为height[3]、height[4]、height[5]、height[6]的最小值,如下图所示。这就转化为区间最值RMQ问题了。

在这里插入图片描述

如何计算height数组呢?若两两比较,则需要O (n^2 )时间;若利用它们之间的关系递推,则需要O (n )时间。

计算height数组之前,首先定义一个h 数组:h [i]=height[rank[i ]]。根据rank[]和sa[]的互逆性,rank[3]=1,h[3]=height[rank[3]]=height[1]=0;rank[4]=2,h[4]=height[rank[4]]=height[2]=3。实际上,heigt[]和h []只是下标不同而已,前者使用rank作为下标,后者使用sa作为下标。

在这里插入图片描述

性质2: h [i ]≥h [i -1]-1。

有了这个性质,求解出h [i -1],然后在h [i -1]-1的基础上继续计算h [i ]即可,没必要再从头比较了。递推求解h [1]、h [2]、h[3]……时间复杂度将为O (n )。

对该性质的证明过程如下。

(1) 设后缀i -1的前一名为后缀k ,h [i -1]为两个后缀的最长公共前缀长度。后缀k 表示从第k 个字符开始的后缀。

在这里插入图片描述

(2) 将后缀i -1和后缀k 同时去掉第1个字符,则两者变为后缀i和后缀k +1,两者之间可能存在其他后缀,如下图所示。后缀i 的前一名后缀的最长公共前缀为h [i ],有可能等于h [i -1]-1,也有可能大于h [i -1]-1,因此h [i ]≥h [i -1]-1。

在这里插入图片描述
举个栗子:

1 i =0

[1] 先将下标转换为排名,rank[0]=4。

[2] 求前一名(排名减1),rank[0]-1=3。

[3] 将前一名转换为下标,sa[3]=5,j =5。

[4] 从k =0开始比较,如果s [i +k ]==s [j +k ],k ++,在比较结束时k =3,则height[rank[0]]=height[4]=3。

在这里插入图片描述
2 i =1

[1] 将下标转换为排名rank[1]=6。

[2] 求前一名,rank[1]-1=5。

[3] 将前一名转换为下标,sa[5]=6,j =6。

[4] 此时k =3,k ≠0,因此从上次的运算结果k -1开始接着比较,k =2,因为s[i +k ]≠s[j +k ],k 不增加,因此height[6]=2。

在这里插入图片描述

3 继续求解i =2, 3, …, n -1,即可得到所有height[]。

算法代码:

void calheight(int *r, int *sa, int n){
	
	int i , j , k = 0;
	for(i = 1; i <= n ; i ++){
		rank[sa[i]] = i;
	}
	for(i = 0 ; i < n ; i ++){
		if(k){
			k --;
		}
		j = sa[rank[i] - 1];
		while(r[i + k] == r[j + k]){
			k ++;
		}
		height[rank[i]] = k;
	}
}

每次都在上一次比较结果的基础上继续比较,无须从头开始,这样速度加快,可以在O (n )时间内计算出height数组。有了height数组,求任意两个后缀suffix(i )、suffix(j ),若rank[i ]<rank[j ],则它们的最长公共前缀长度为height[rank[i ]+1], height[rank[i]+2], …, height[rank[j ]]的最小值。此问题为RMQ问题,可以使用ST算法解决,在O (n logn )时间预处理后,用O (1)时间得到任意两个后缀的最长公共前缀长度。

⑤ 后缀数组应用

(1) 最长重复子串

重复子串指一个字符串的子串在该字符串中至少出现两次。重复子串问题包括3种类型。

[1] 可重叠。给定一个字符串,求最长重复子串的长度,这两个子串可以重叠。例如,对于字符串“aabaabaac”,最长重复子串为“aabaa”,长度为5,求解最长重复子串(可重叠)的长度,等价于求两个后缀的最长公共前缀的最大值,即height数组的最大值。该算法的时间复杂度为O (n )。

[2] 可重叠且重复k 次。给定一个字符串,求至少出现k 次的最长重复子串的长度,这k 个子串可以重叠。可以使用二分法,判断是否存在k 个长度为l 的子串相同,将最长公共子串长度大于l 的分为一组,查看每一组内的后缀个数是否大于k 。例如对于字符
串“aabaaaab”,求至少出现4次的最长重复子串长度,则将最长公共子串长度大于2的分组后,第1组正好重复4次,至少出现4次的最长重复子串长度为2。该算法的时间复杂度为O (n logn )。

在这里插入图片描述

[3] 不可重叠。给定一个字符串,求最长重复子串的长度,这两个子串不可以重叠,可以使用二分法,判断是否存在两个长度为l 的子串相同,将最长公共子串长度大于l 的分为一组,查看每一组内后缀的sa最大值和最小值之差是否大于或等于l 。因为sa是后缀的开始下标,下标差值大于或等于l ,所以这两个后缀必然不重叠。例如,对于字符串“aabaaaab”,将最长公共子串长度大于3的分为一组:第1组,sa值之差为1,不满足条件;第2组,sa值之差为5,大于3,说明不重叠,满足条件。该算法的时间复杂度为O (n logn )。

在这里插入图片描述

(2) 不同子串的个数

给定一个字符串,求不同子串的个数。每个子串一定都是某个后缀的前缀,原问题转化为求所有后缀之间不同前缀的个数。对于每个sa[i],累加n -sa[i ]-height[i ]即可得到答案,该算法的时间复杂度为O(n )。

例如,对于字符串“aabaaaab”,求所有后缀之间不同前缀的个数,过程如下。

[1] sa[1]=3,即排名第1的后缀是从第3个字符开始的,该后缀为“aaaab”,其长度为n -sa[1]=5,将产生5个新的前缀:a、aa、aaa、aaaa、aaaab。

[2] sa[2]=4,将产生n -sa[2]个新的前缀,其中height[2]个前缀与前一个字符串的前缀重复,n -sa[2]-height[2]=8-4-3=1,因此将产生1个新的前缀。

[3] 对于sa[i ],将产生n -sa[i ]-height[i ]个新的前缀,累加后即可得到所有不同前缀的个数。

在这里插入图片描述

(3) 最长回文子串

给定一个字符串,求最长回文子串的长度。可将字符串反过来连接在原字符串之后,中间用一个特殊的字符间隔,然后求这个字符串的后缀的最长公共前缀即可。该算法的时间复杂度为O (n logn )。例如,求字符串“xaabaay”的最长回文子串长度,首先将字符串反过来,用特殊字符“# ”间隔连接在原字符串之后为“xaabaay# yaabaax”,很快可以求出两个后缀“aabaay# yaabaax”“aabaax”的最长公共前缀长度为5,即最长回文子串的长度为5。

(4) 最长公共子串

对多个字符串,求重复k 次的最长公共子串,可以将每个字符串都用一个原字符串没有的特殊字符连接起来,然后求它们的最长公共前缀,求解时要判断是否属于不同的字符串。例如,求3个字符串“abcdefg”“bcdefgh”“cdefghi”至少重复两次的最长公共子串,可以用特殊字符“#”将3个字符串连接起来,得到“abcdefg#bcdefgh# cdefghi”,需要标记每个字符属于哪一个字符串,求解最长公共前缀。至少重复两次的最长公共子串为“bcdefg”“cdefgh”,后缀数组如下图所示。

在这里插入图片描述

注意:最长公共子串问题除了特殊字符连接,还要标记每个字符属于哪个字符串,这样才可以判断两个公共前缀是否属于同一个字符串的子串。

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

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

相关文章

Jenkins

目录 一、什么是Jenkins 二、为什么需要使用持续集成工具 三、如何搭建jenkins服务 四、jenkins集成服务器上的JDK 五、jenkins集成git 5.1 jenkins所在的服务安装git 5.2 jenkins集成git 5.3 jenkins创建一个任务项 5.4 创建远程仓库 5.5 执行任务 六、jenkins集成maven…

[附源码]Python计算机毕业设计SSM基于的校园商城(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

人工智能写代码 !ChatGPT VScode 插件 真正打败AI人工智能的只能是AI人工智能自己。

前言&#xff1a; 真正打败你的只能是你自己。 真正打败程序员的只能是程序员自己。 真正打败AI人工智能的只能是AI人工智能自己。 人工智能写代码 &#xff01;上线3天&#xff0c;下载4万&#xff0c;ChatGPT中文版VSCode插件来了,程序员要失业了吗&#xff1f; 还没开始就遇…

[附源码]JAVA毕业设计医院挂号系统(系统+LW)

[附源码]JAVA毕业设计医院挂号系统&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&…

VoIP通话-基于SIP协议的Asterisk(零)-Demo

文章首发及后续更新&#xff1a;https://mwhls.top/4116.html&#xff0c;无图/无目录/格式错误/更多相关请至首发页查看。 新的更新内容请到mwhls.top查看。 欢迎提出任何疑问及批评&#xff0c;非常感谢&#xff01; VoIP通话-基于SIP协议的Asterisk这篇为 Demo&#xff0c;是…

LabVIEW如何实现高性能串口助手-附代码

LabVIEW基本上还是在Windows系统下进行开发。在Windows下&#xff0c;提供了非常多的系统接口&#xff0c;供应用开发时调用来实现各种功能&#xff0c;在LabVIEW里面也是可以通过对这些系统接口进行封装&#xff0c;形成LabVIEW里面的控件&#xff0c;供LabVIEW程序开发时调用…

mongo实时导入到clickhouse案例(包含复杂嵌套json的解析)

(一)案例介绍 本案例是把Mongo数据库的数据通过FlinkCDC实时导入到Kafka&#xff0c;消费Kafka数据把维表数据写入到MySQL。读取MySQL维表数据和消费Kafka的数据通过Flink SQL Join后导入到ClickHouse。 (二) maven依赖 <?xml version"1.0" encoding"UTF-…

数字人的生死疲劳

你看好数字人吗&#xff1f;这个问题在今天似乎颇难回答。如果从宏观趋势上看&#xff0c;数字人的利好要素似乎已经达到了一个空前的高度。比如有市场分析机构预测&#xff0c;到2026年中国AI数字人的市场规模将突破100亿人民币&#xff0c;整体市场呈现高速增长态势。又比如今…

git基础之三|初始化本地库、新建文件、提交代码、版本回退、穿梭等使用命令集合【2022最全版】

Git作为版本管理的软件&#xff0c;在我们的协同工作中非常重要。因此&#xff0c;对于Git的常见命令&#xff0c;如新建、编辑文件、提交版本、版本回退等操作必须要熟悉。 Git常用命令一、初始化本地库1、创建项目文件夹2、右击选择Git bash3、在bash中输入4、查看创建的结果…

mysql索引中最左前缀原则

最左前缀原则 最先匹配最左边的索引&#xff0c;匹配上就继续&#xff0c;如果匹配不上就检索不到 (a,b,c是索引) where后面的条件有没有给a对应的条件 不给定a等于几&#xff0c;是没法儿查询出结果的&#xff0c; 因为辅助聚簇索引是把索引按照组合索引的顺序存到一起的&…

IoTDB 可实现的基本操作 —— 数据写入、删除、导出、元数据管理、时区设置 | 小白教程文档(四)...

前言上篇教程介绍了 Apache IoTDB 处理时序数据时&#xff0c;能够实现的部分具体功能和具体的操作命令&#xff0c;包括数据导入、基本查询、和聚合查询。本篇将继续介绍 Apache IoTDB 可实现的其他功能和相关 SQL 语句命令&#xff0c;包括数据的写入、删除、导出、元数据操作…

python 解析库Beautiful Soup的安装

Beautiful Soup的安装一、Beautiful Soup的安装1.1 安装lxml库1.2 安装beautifulsoup41.3 验证beautifulsoup4能否运行一、Beautiful Soup的安装 Beautiful Soup是Python的一个HTML或XML的解析库&#xff0c;使用它可以很方便地从网页中提取数据。它的解析器是依赖于lxml库的&…

数组元素循环右移问题

目录 1008 数组元素循环右移问题 输入格式: 输出格式: 输入样例: 输出样例: 代码长度限制 时间限制 内存限制 思路: 1.右移函数 1.2函数代码: 2.main函数中 2.2main函数代码: 完整代码: 时间复杂度: 总结: 题目的链接: 1008 数组元素循环右移问题 一个数组A中存有…

MongoDB(一)【概念介绍安装】

MongoDB 概念以及安装 官方文档&#xff1a;https://www.mongodb.com/docs/manual/ 简介 官方介绍 MongoDB是一个文档数据库&#xff0c;旨在方便应用开发和扩展 百度百科 MongoDB是一个基于分布式文件存储的数据库。由C语言编写。旨在为WEB应用提供可扩展的高性能数据存储解…

【web前端期末大作业】html在线网上书店 基于html制作我的书屋(23页面)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

反序列化漏洞原理

序列化及其出现场景 ●远程和进程间通信&#xff08;RPC/IPC&#xff09; ●连线协议、Web服务、消息代理 ●缓存/持久性存储区 ●数据库、缓存服务器、文件系统 ●HTTP cookie、HTML参数、API身份验证令牌 序列化 (serialize)是将对象的状态信息转换为可以存储或传输的形…

基于java+springmvc+mybatis+jsp+mysql的电动车实名制挂牌管理系统

项目介绍 电动车实名制挂牌管理系统的需求和管理上的不断提升&#xff0c;电动车实名制挂牌管理的潜力将无限扩大&#xff0c;电动车实名制挂牌管理系统在业界被广泛关注&#xff0c;本网站及对此进行总体分析&#xff0c;将电动车实名制挂牌信息管理的发展提供参考。电动车实…

微信接入ChatGPT,使用Node+ChatGPT+Wechaty做一个微信机器人

目录 前言 准备工作 起步 实践 写在最后 前言 接上篇文章&#xff1a;站在巨人的肩膀上&#xff0c;用NodeChatGPT模块实现一个接口_DieHunter1024的博客-CSDN博客 我将ChatGPT模块的使用介绍了一下&#xff0c;使用自己的session发送请求达到调用ChatGPT进行聊天的目的&…

动态规划——背包问题(2)

文章目录多重背包的单调队列优化例题思路代码二维费用背包问题例题背包问题装法的总结&#xff1a;至多、恰好、至少背包最多装V体积背包恰好装V体积背包最少装V体积例题求解方案数初始化和循环顺序例题求解具体方案思路例题考察思维的一些题目多重背包与分组背包机器分配金明的…

原生JS开发手机端H5项目总结(FamilyChallenge)

一、 插件文件 &#xff08;可多选&#xff09; JQuery &#xff08;操作dom&#xff09;lottie.js &#xff08;播放动效&#xff09;preload-0.6.2.min.js&#xff08;资源预加载&#xff09;jweixin-1.6.0.js &#xff08;h5跳转微信小程序&#xff09;TweenMax.min.js &am…