【C++算法】线性DP详解:数字三角形、最长上升子序列、最长公共子序列、最长公共子串、字符串编辑距离

news2024/11/28 20:51:59

文章目录

    • 1)数字三角形
      • 1:顺推
      • 2:逆推
    • 2)最长上升子序列
      • 1:线性DP做法
      • 2:二分优化
    • 3)最长公共子序列
    • 4)最长公共子串
    • 5)字符串编辑距离

1)数字三角形

1:顺推

  • 顺推比较需要注意的问题就是边界问题,因为从上往下推每个元素会用到上方元素和左上方元素
    • 对于某一行的最后一个元素,那么上方的元素是没有被初始化的
    • 对于某一行的第一个元素,那么左上方的元素是没有被初始化的
    • 为了保证这两种情况一定不选择未被初始化的元素,所以首先把 f f f 数组初始化为 − I N F -INF INF
  • 随后把 f [ 1 , 1 ] f[1,1] f[1,1] 初始化为 a [ 1 , 1 ] a[1,1] a[1,1],因为从第二行开始计算,这样计算出来的值就是正常值,最后从最后一行的出口中枚举找一个最大值
#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;

// 解题思路: 

const int N=5e2+5;
const int INF=1e9;
int f[N][N];
int a[N][N];
int n;

int main() {
	cin>>n;
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=i;j++) {
			scanf("%d",&a[i][j]);
		}	
	}
	// 如果顺推,每个元素应该考虑上方和左上方元素
	// 如果当前计算元素的[i,j]刚好i==j即最后一个时,则上方无元素,会遇到边界问题
	// 为了一定不选择这个边界,可以把其初始化为-INF(因为三角形中数字可能有负值)
	// 别忘了左上角,所以ij均从0开始
	for(int i=0;i<=n;i++) {
		for(int j=0;j<=i+1;j++) {
			f[i][j]=-INF;
		}
	}
	f[1][1]=a[1][1]; // 从a[1][1]开始算,边界依然为-INF
	// 从第二行开始
	for(int i=2;i<=n;i++) {
		for(int j=1;j<=i;j++) {
			f[i][j]=max(f[i-1][j]+a[i][j],f[i-1][j-1]+a[i][j]);
		}	
	}
	int ans=INT_MIN;
	// 对出口求最大值,即为最大路径数字和
	for(int i=1;i<=n;i++) {
		ans=max(ans,f[n][i]);
	}
	cout<<ans;
	return 0;
}

2:逆推

  • 从上往下有五个出口,最终要用 O ( n ) O(n) O(n) 的时间来判断谁的值更大,如果从下往上那么出口只有一个,无需比较;并且从下往上逆推不会遇到边界问题,用到的每个元素都刚好有初始值,可以手动模拟一下为什么没有边界问题
#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;

// 解题思路: 经典数字三角形

const int N=5e2+5;
const int INF=1e9;
int n;
int a[N][N];

int main() {
	// 逆推,从下往上那么出口只有一个,注意元素只从下方和右下方来
	// 从下往上没有边界问题
	cin>>n;
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=i;j++) {
			scanf("%d",&a[i][j]);
		}	
	}
	// 从倒数第二行开始
	for(int i=n-1;i>=1;i--) {
		// 每一行的元素的个数应该就是i
		for(int j=1;j<=i;j++) {
			a[i][j]+=max(a[i+1][j],a[i+1][j+1]);
		}
	}
	cout<<a[1][1]<<endl;
	return 0;
}
  • 若需要输出路径,可以用 b b b 数组 m e m c p y memcpy memcpy 原二维数组,因为加和是直接在原数组上进行操作的,另外用 p p p 表示前驱数组用来记录路径,在记录时只需要记录在列方向的偏移量即可,比如往右下则 p [ i , j ] = 1 p[i,j]=1 p[i,j]=1,往下 p [ i , j ] = 0 p[i,j]=0 p[i,j]=0
#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;

// 解题思路: 经典数字三角形

const int N=5e2+5;
const int INF=1e9;
int n;
int a[N][N];

int p[N][N]; // 记录最大值路径
int b[N][N]; // 备份数组,路径跟踪

int main() {
	// 逆推,从下往上那么出口只有一个,注意元素只从下方和右下方来
	// 从下往上没有边界问题
	cin>>n;
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=i;j++) {
			scanf("%d",&a[i][j]);
		}	
	}
	// 拷贝
	memcpy(b,a,sizeof a); // 从a拷到b
	
	// 从倒数第二行开始
	for(int i=n-1;i>=1;i--) {
		// 每一行的元素的个数应该就是i
		for(int j=1;j<=i;j++) {
//			a[i][j]+=max(a[i+1][j],a[i+1][j+1]);
			if(a[i+1][j]>=a[i+1][j+1]) {
				a[i][j]+=a[i+1][j];
				p[i][j]=0; // 来自下方,y轴增量为0
			} else {
				a[i][j]+=a[i+1][j+1];
				p[i][j]=1; // 来自右下,y轴增量为1
			}
		}
	}
	cout<<a[1][1]<<endl;
	int i,j;
	// 输出最大数的路径(行数一直增大,列数根据存储的增量变化)
	for(i=1,j=1;i<=n-1;i++) {
		cout<<b[i][j]<<"->";
		j+=p[i][j];
	}
	cout<<b[n][j];
	return 0;
}

2)最长上升子序列

1:线性DP做法

时间复杂度: O ( n 2 ) O(n^2) O(n2)

  • 如果不理解状态转移方程,可以 E03 线性DP 最长上升子序列 bilibili 4 : 00 4:00 4:00 起看该问题的模拟过程
#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;

// 解题思路: 

const int N=1e5+5;
int a[N];
int f[N]; // 以第i个元素结尾的LIS(最长上升子序列)长度
int n;

int main() {
	cin>>n;
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);	
	}
	int res=INT_MIN;
	for(int i=1;i<=n;i++) {
		f[i]=1; // 所有元素起码可以以自身结尾
		// 遍历i之前的元素,如果比i小则可以拼接
		for(int j=1;j<=i;j++) {
			// 不理解可以看视频中的模拟过程
			if(a[j]<a[i]) {
				f[i]=max(f[i],f[j]+1);
			}
		}
		res=max(res,f[i]);
	}
	cout<<res<<endl;
	return 0;
}

2:二分优化

时间复杂度: O ( n l o g n ) O(nlog^n) O(nlogn),因为二分查找是 O ( l o g n ) O(log^n) O(logn)

  • 模拟过程在 E04 线性DP 最长上升子序列 二分优化 bilibili 6 : 15 6:15 6:15
  • 唯一比较疑惑的地方在于,为什么是找到第一个大于等于 a [ i ] a[i] a[i] 的元素做替换而不是大于呢?翻了一下评论区搞明白了,比如 { 1   2   6   7   2   3 } \{1\ 2\ 6\ 7\ 2\ 3\} {1 2 6 7 2 3} 的话,如果大于 x x x,那么序列中可能出现重复元素,最长上升子序列为 1   2   2   3 1\ 2\ 2\ 3 1 2 2 3,这样就不是严格单调递增的了
#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;

// 解题思路: 

const int N=1e5+5;
int a[N];
int b[N]; // 有序子序列
int len;
int n;

int main() {
	cin>>n;
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);	
	}
	// 遍历a中每一个元素,构造有序子序列
	len=1;
	b[1]=a[1];
	// 1)如果a中元素大于b中最后一个元素,则添加到末尾
	// 2)如果a中元素小于等于b中最后一个元素,则在b数组中找到第一个大于等于a的元素进行替换
	// 比如a[i]替换掉b[j]后,b[j]变小,则b[1...j]的结尾元素更小,则更可能续其他元素,使ILS更大
	for(int i=2;i<=n;i++) {
		if(a[i]>b[len]) {
			b[++len]=a[i];
		} else {
			// 用二分找到第一个大于等于a[i]的元素(答案在左边,压缩右边界)
			int l=1,r=len;
			while(l<=r) {
				int mid=l+r>>1;
				if(b[mid]>=a[i]) {
					r=mid-1;
				} else {
					l=mid+1;
				}
			}
			// l是答案
			b[l]=a[i];
		}
	}
	// 最终len的长度就是答案
	cout<<len<<endl;
	return 0;
}

3)最长公共子序列

  • 为什么没有 a [ i ] ≠ b [ j ] a[i]≠b[j] a[i]=b[j],且 a [ i ] ,   b [ j ] a[i],\ b[j] a[i], b[j] 都不在公共子序列的情况?其实可以把这种情况归为第 2 ,   3 2,\ 3 2, 3 种情况之一

在这里插入图片描述

  • 一边 d p dp dp 一边打标记记录状态转移,其中从左上方转移过来的元素即为 L C S LCS LCS 中的公共元素

在这里插入图片描述

  • 只要理解了状态转移方程,代码就很简单
#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;

// 解题思路: 

const int N=1e3+5; // 字符串最大长度
int f[N][N]; // f[i][j]:序列a[1...i]和b[1...j]的最长公共子序列的长度(LCS)
char a[N],b[N];
int n,m;

int main() {
	cin>>a+1>>b+1; // 从下标1开始存储
	n=strlen(a+1); // 起始位置是a+1
	m=strlen(b+1);
	// 初始化 f[0][j]=0,f[i][0]=0,即i和j中有未指向任意元素的指针存在时
    // 但是全局变量本身初始化为0,所以无需初始化
    // 枚举字符串a
	for(int i=1;i<=n;i++) {
        // 枚举字符串b
		for(int j=1;j<=m;j++) {
			if(a[i]==b[j]) {
				f[i][j]=f[i-1][j-1]+1;
			} else {
				f[i][j]=max(f[i-1][j],f[i][j-1]);
			}
		}
	}
	cout<<f[n][m];
	return 0;
}
  • 如果要带路径输出呢?同理,开一个数组 p p p 用来记录取得 L C S LCS LCS 的路径,注意,只有来自左上方的元素是 L C S LCS LCS 中的元素
#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;

// 解题思路: 

const int N=1e3+5; // 字符串最大长度
int f[N][N]; // f[i][j]:序列a[1...i]和b[1...j]的最长公共子序列的长度(LCS)
char a[N],b[N];
int p[N][N]; // 前驱数组
int n,m;

int main() {
	cin>>a+1>>b+1; // 从下标1开始存储
	n=strlen(a+1); // 起始位置是a+1
	m=strlen(b+1);
	// 初始化 f[0][j]=0,f[i][0]=0,即i和j中有未指向任意元素的指针存在时
	
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(a[i]==b[j]) {
				f[i][j]=f[i-1][j-1]+1;
				p[i][j]=1; // 来自↖
			} else if(f[i-1][j]>f[i][j-1]) {
				f[i][j]=f[i-1][j];
				p[i][j]=2; // 来自←
			} else {
				f[i][j]=f[i][j-1];
				p[i][j]=3; // 来自↑
			}
		}
	}
	cout<<f[n][m]<<endl; // 最长长度
	int i=n,j=m,k=f[n][m];
	vector<char> path;
	// i或j中任意一个元素=0时退出
	while(i>0 && j>0) {
		// 左上方
		if(p[i][j]==1) {
			path.push_back(a[i]); // LCS中
			i--,j--;
		} 
		// 上方
		else if(p[i][j]==2) {
			i--;
		}
		// 左方
		else {
			j--;
		}
	}
	reverse(path.begin(),path.end());
	for(auto x:path) {
		cout<<x<<' ';
	}
	cout<<endl;
	return 0;
}

4)最长公共子串

  • 这一题和上一题有什么区别呢?序列可以是不连续的,但是串一定是连续的,区别就在此
  • 最长公共子序列中 f [ i , j ] f[i,j] f[i,j] 表示序列 a [ 1... i ] a[1...i] a[1...i] b [ 1... j ] b[1...j] b[1...j] 的最长公共子序列的长度
  • 最长公共子串中 f [ i , j ] f[i,j] f[i,j] 表示以 a [ i ] a[i] a[i] b [ j ] b[j] b[j] 为结尾的公共子串的长度

在这里插入图片描述

在这里插入图片描述

  • 则可以得到状态转移方程
    • a [ i ] = = b [ j ] a[i]==b[j] a[i]==b[j],构成公共子串, f [ i , j ] = f [ i − 1 , j − 1 ] + 1 f[i,j]=f[i-1,j-1]+1 f[i,j]=f[i1,j1]+1
    • a [ i ] ! = b [ j ] a[i]!=b[j] a[i]!=b[j],不能构成公共子串, f [ i , j ] = 0 f[i,j]=0 f[i,j]=0(为什么不记录为最大值呢?因为串必须是连续的)
#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;

// 解题思路: 

const int N=1e3+5;
int n,m;
char a[N],b[N];
int f[N][N]; // 以a[i]和b[j]结尾的最长公共子串的长度

int main() {
	cin>>a+1>>b+1;
	n=strlen(a+1);
	m=strlen(b+1);
	// 无需初始化,全局变量
	int ans=INT_MIN;
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			// 必须要连续才相加
			if(a[i]==b[j]) {
				f[i][j]=f[i-1][j-1]+1;
			} else {
				f[i][j]=0;
			}
			ans=max(ans,f[i][j]);
		} 
	}
	// 以最后一个元素结尾的不一定是最长公共子串的长度
	cout<<ans<<endl;
	return 0;
}

5)字符串编辑距离

  • f [ i , j ] f[i,j] f[i,j] 表示从 a [ 1... i ] a[1...i] a[1...i] 变成 b [ 1... j ] b[1...j] b[1...j] 的编辑距离
  • a [ i ] = b [ j ] a[i]=b[j] a[i]=b[j] f [ i , j ] = f [ i − 1 , j − 1 ] f[i,j]=f[i-1,j-1] f[i,j]=f[i1,j1] :因为新位置 i i i j j j 的元素是相等的,无需编辑转移
  • a [ i ] ! = b [ j ] a[i]!=b[j] a[i]!=b[j]
    • 修改,即 a a a 中前 i − 1 i-1 i1 项 和 b b b 中前 j − 1 j-1 j1 项已然相等,只需要把最后一项修改为 b [ j ] b[j] b[j] 即可,所以有 f [ i , j ] = f [ i − 1 , j − 1 ] + 1 f[i,j]=f[i-1,j-1]+1 f[i,j]=f[i1,j1]+1
    • 插入,即 a a a 中前 i i i 项和 b b b 中前 j − 1 j-1 j1 项相等,只需要再插入一项 b [ j ] b[j] b[j] 即可,所以有 f [ i , j ] = f [ i , j − 1 ] + 1 f[i,j]=f[i,j-1]+1 f[i,j]=f[i,j1]+1
    • 删除,即 a a a 中前 i − 1 i-1 i1 项和 b b b 中前 j j j 项相等,但是多了一项,所以有 f [ i , j ] = f [ i − 1 , j ] + 1 f[i,j]=f[i-1,j]+1 f[i,j]=f[i1,j]+1
    • 由于属性是取最小值,所以三者中取 m i n min min 即可

在这里插入图片描述

  • 二维数组的常规做法如下,关于滚动数组优化这里不做解释,因为自己都搞得不是很清楚
#include<bits/stdc++.h>
#define x first
#define y second

using namespace std;

typedef long long ll;
typedef pair<int,int> PII;

// 解题思路: 

const int N=1e3+5;
int n,m;
char a[N],b[N];
int f[N][N];

int main() {
	cin>>a+1>>b+1;
	n=strlen(a+1);
	m=strlen(b+1);
	// 从a[1...i]变成空串,需要是删除i次
	for(int i=1;i<=n;i++) {
		f[i][0]=i;
	}
	// 从空串变成b[1...j],需要添加j次
	for(int j=1;j<=m;j++) {
		f[0][j]=j;
	}
	// 状态转移
	// 如果记录一下状态转移就可以输出变化过程
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			// 末尾相等,无需添加
			if(a[i]==b[i]) {
				f[i][j]=f[i-1][j-1];
			} else {
				f[i][j]=min(f[i-1][j],min(f[i][j-1],f[i-1][j-1]))+1; // 三种操作的最小值
			}
		}	
	}
	cout<<f[n][m]<<endl;
	return 0;
}

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

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

相关文章

LabVIEW闭环步进电机运动系统设计及精度分析

LabVIEW闭环步进电机运动系统设计及精度分析 在自动化设备不断发展的当代&#xff0c;闭环步进电机以其高精度和可靠性成为了自动化设备的重要组成部分。以LabVIEW软件为核心&#xff0c;结合运动控制卡及驱动器模块&#xff0c;设计并实现了一个闭环步进电机的多轴运动控制系…

内核驱动更新

1.声明我们是开源的 .c 文件末尾加上 2.在Kconfig里面修改设备&#xff0c;bool&#xff08;双态&#xff09;-----》tristate&#xff08;三态&#xff09; 3.进入menuconfig修改为M 4.编译内核 make modules 也许你会看到一个 .ko 文件 5.复制到根目录文件下 在板子…

XAI有什么用?探索LLM时代利用可解释性的10种策略

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了免费的人工智能中文站https://ai.weoknow.com 新建了收费的人工智能中文站https://ai.hzytsoft.cn/ 更多资源欢迎关注 你是否也好奇&#xff0c;在大模型时代&#xff0c;可解释性人工智能技术&#xff08;XAI&am…

SAP ABAP 连接外部数据库

前言 SAP 连接外部数据库有多种方法&#xff0c;这里我们介绍DBCO 连接 DBCO 代码 DATA: OREF TYPE REF TO CX_ROOT,TXT TYPE STRING,M_WERKS TYPE WERKS_D,STRCDATE TYPE C LENGTH 10. DATA:BEGIN OF T_ITEM OCCURS 0, CDATE TYPE C LENGTH 10, END OF T_ITEM. M…

八款禁用U盘的软件

八款禁用U盘的软件禁用U盘的软件通常用于企业或组织环境中&#xff0c;以防止未经授权的USB设备接入计算机&#xff0c;从而保护数据安全、防止病毒传播或限制员工使用U盘。以下是一些可以禁用U盘的软件推荐。 1、安企神软件 权限设置&#xff1a;为终端电脑设置使用权限&…

Terraform 语法配置

配置语法 Terraform 的配置文件都是以 .tf 为后缀Terraform 支持两种模式 HCL、JSON Provider 插件 providers 地址&#xff1a;Terraform Registry Terraform 通过 provider 管理基础设施&#xff0c;使用 provider 与云供应商 API 进行交互&#xff0c;每个 Provider 都包含…

OSCP靶场-- Sybaris

OSCP靶场–Sybaris 考点(redis MODULE LOAD命令执行) 1.nmap扫描 ## ┌──(root㉿kali)-[~/Desktop] └─# nmap 192.168.158.93 -sV -sC -Pn --min-rate 2500 -p- Starting Nmap 7.92 ( https://nmap.org ) at 2024-04-11 04:24 EDT Nmap scan report for 192.168.158.93…

差分数组加前缀和

暴力 #include <bits/stdc.h> using namespace std; char arr[(int)1e5*25]; //要强制转换为int int main() {//cout << (char)(a7);long long len0,op0;cin >> len >> op;cin >> arr; //cout << op;//cout << arr;int inx[(int)…

6.12物联网RK3399项目开发实录-驱动开发之UART 串口的使用(wulianjishu666)

嵌入式实战开发例程【珍贵收藏&#xff0c;开发必备】&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1tkDBNH9R3iAaHOG1Zj9q1Q?pwdt41u UART 使用 简介 AIO-3399J 支持 SPI 桥接/扩展 4 个增强功能串口&#xff08;UART&#xff09;的功能&#xff0c;分别为 UA…

【UE Niagara】光束发射模块学习

效果 步骤 1. 新建一个Niagara发射器&#xff0c;使用Empty模板&#xff0c;这里命名为“NE_Beam” 打开“NE_Beam”&#xff0c;添加条带渲染器 添加“Spawn Burst Instantaneous”模块&#xff0c;设置生成数量为100 添加一个“Beam Emitter Setup”模块 再添加一个“Spawn …

基于STM32G030F6部分芯片烧录程序后无法识别也不运行之救砖笔记

基于STM32G030F6部分芯片烧录程序后无法识别也不运行之救砖笔记 目录 问题背景思路总结 问题背景 使用STM32G030F6这颗ic作设计&#xff0c;程序中有设置L1级读保护。首次烧写全部显示成功&#xff0c;打算再次烧录时有极个别已经找不到芯片&#xff0c;其中不能再次烧入的板子…

什么是 DNS 记录?

DNS记录是存储在DNS服务器上的文本指令。它们表明与一个域名相关的IP地址&#xff0c;也可以提供其他信息。DNS记录是计算机用语&#xff0c;指域名系统&#xff08;Domain Name System&#xff0c;简称DNS&#xff09;中的一条记录&#xff0c;这条记录存储于DNS服务器中。每一…

数通学员分享丨在誉天学习数通课程怎么样

哈喽大家好&#xff0c;我是来自誉天的田同学&#xff0c;我与誉天的故事是2022年8月开始的。 2022年六月毕业之后&#xff0c;由于对自身专业的不喜欢&#xff08;学的工程&#xff09;&#xff0c;我对未来非常的迷茫&#xff0c;这个时候我接触到了誉天&#xff0c;开启了我…

稀碎从零算法笔记Day40-LeetCode:加油站

题型&#xff1a;贪心、模拟、数组 链接&#xff1a;134. 加油站 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第…

Harmony鸿蒙南向驱动开发-PIN接口使用

功能简介 PIN即管脚控制器&#xff0c;用于统一管理各SoC的管脚资源&#xff0c;对外提供管脚复用功能&#xff1a;包括管脚推拉方式、管脚推拉强度以及管脚功能。 PIN接口定义了操作PIN管脚的通用方法集合&#xff0c;包括&#xff1a; 获取/释放管脚描述句柄&#xff1a;传…

【C++第三阶段】stackqueue容器

以下内容仅为当前认识&#xff0c;可能有不足之处&#xff0c;欢迎讨论&#xff01; 文章目录 stack容器queue容器 stack容器 是什么&#xff1f;功能是什么&#xff1f;常用接口是什么&#xff1f;局限性有哪些&#xff1f;优势又有哪些&#xff1f; 栈容器&#xff0c;先进…

@Transactional失效的10种场景

Transactional失效的场景都有哪些呢&#xff1f;本章节针对Transactional的问题&#xff0c;做以下总结整理。 1、同一个类中&#xff0c;方法内部调用事务失效 2、事务方法被final、static修饰 3、当前类没有被Spring管理 4、非public修饰的方法&#xff08;存在版本差异&a…

Spring5深入浅出篇:bean的生命周期

Spring5深入浅出篇:bean的生命周期 什么是对象的⽣命周期 指的是⼀个对象创建、存活、消亡的⼀个完整过程 为什么要学习对象的⽣命周期 由Spring负责对象的创建、存活、销毁&#xff0c;了解⽣命周期&#xff0c;有利于我们使⽤好Spring为我们创建的对象 ⽣命周期的3个阶段…

知识图谱的演进与基于 OpenSPG+TuGraph 的推理实践

本文为蚂蚁集团开发工程师王少飞在TuGraph Meetup北京站的演讲&#xff0c;主要介绍了蚂蚁知识图谱平台经过多年金融领域业务沉淀的知识语义增强可编程框架SPG&#xff0c;及TuGraph作为执行引擎在图谱推理过程的作用。 作者介绍&#xff1a; 王少飞&#xff0c;蚂蚁知识图谱推…

微信小程序抓包教程

前言 笔者最近在研究如何又简单又精准的对微信小程序进行HTTP/HTTPS流量抓包&#xff0c;然后进行渗透测试。研究了几天&#xff0c;发现了一个最适合自己的方法&#xff0c;特此进行记录。 前期准备 流量工具&#xff1a;Proxifier抓包工具&#xff1a;BurpSuite电脑端微信…