Trie、并查集、堆、Hash表学习过程以及遇到的问题
Trie、并查集、堆、Hash表:
Trie
快速存储和查找字符串集合 字符类型统一,将单词在最后一个字母结束的位置上打上标记
练习题:Trie字符串统计
import java.util.*;
public class Main{
static int N = 100010;
static int[][] son = new int[N][26];
static int[] con = new int[N];
static int idx =0;
static char[] str = new char[N];
// 插入操作
public static void insert(char[] str){
// 初始化根节点
int p = 0;
// 遍历字符串的每个字符
for(int i = 0; i < str.length; i++){
//将字母映射为数组 'a'-->97;
int u = str[i] -'a';
//如果子节点没有 及:son[p][u] ==0;
//那么 添加节点
if(son[p][u] == 0) son[p][u] = ++idx;
//更新节点位置
p = son[p][u];
}
//统计最后以str[p]这个结尾的字母
con[p]++;
}
// 查找操作
public static int select(char[] str){
//初始化根节点
int p = 0;
for(int i = 0; i < str.length; i++){
int u = str[i] - 'a';
//如果子节点==0;代表没有所查找的字母,及没有该单词,返回0次
if(son[p][u] == 0) return 0;
p = son[p][u];
}
//最后遍历完成后p就是以str[p]的字母
//返回单词的次数
return con[p];
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while(n-- != 0){
String common = sc.next();
String str = sc.next();
if(common.equals("I")){
//toCharArray() -->将字符串转为字符数组
insert(str.toCharArray());
}else if(common.equals("Q")){
System.out.println(select(str.toCharArray()));
}
}
}
}
暴力方法:
public class Main{
static int res = 0;
public static void main(String[] args){
for(int i = 0; i < n; i++){ //枚举第一个数
for(int j = 0; j < i; j++){ //枚举第二个数
res = Math.max(res,a[i] ^ a[j]);
}
}
System.out.println(res);
}
}
使用Trie树来做:
import java.util.*;
public class Main{
static int N = 100010,M = 3000000;
static int[] a = new int[N];
//树的长度最多不超过N*31,节点为0,1;
static int[][] son = new int[M][2];
static int idx;
// 创建Trie
public static void insert(int a){
int p = 0;
for(int i = 30; i >= 0; i--){
// 判断a的二进制位的第i个数是0还是1;
int s = a >> i & 1;
if(son[p][s] == 0) son[p][s] = ++idx;
p = son[p][s]; //把当前节点移动到下一节点;
}
}
// 查询
public static int query(int a){
int p = 0,res = 0;
for(int i = 30; i >= 0; i--){
//判断数字a在第i位的二进制是0还是1;
int s = a >> i&1;
//要想使得a^x最大,那么x要最小,也就是x得二进制与a二进制相反才行
//判断子节点与当前a中二进制相反得分支存不存在(不存在 son[p][1-s] ==0);
if(son[p][1-s] != 0){
//二进制转十进制操作
//原:3-->110,查:001; index:2、1、0;
//查到一位转换成十进制相加即等于最后与原数异或为最大值
res = res + (1<< i);
//更新p位置,即往下走下一节点(相反得一条路)
p = son[p][1-s];
//查找得分支点没有得话,只能走已存在得分支;
}else p = son[p][s];
}
//返回结果
return res;
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int res = 0;
for(int i = 0; i < n; i++){ //初始化数组
a[i] = sc.nextInt();
insert(a[i]);
}
//遍历数组中所有得元素,找到数组中异或得最大得数;
for(int i = 0; i < n; i++){
//query返回得使a[i]^x最大得值
res = Math.max(res,query(a[i]));
}
System.out.println(res);
}
}
问题汇总:
开的son中,第一个取得M是什么含义。
Trie树得深度不是31吗,那只开31个空间不久好了吗?
答:
son的第一维度存的是trie数一共有多少节点,如果是存储一个数的话,确实开31个空间就好了,但是存储的是N = 100000个数,每个数循环31次,那就是31*100000 = 310w,因为会有复用的节点,用不上这么多,300w就可以了
问:
int 是32位的,请问在对一个数进行遍历,判断该位是否为1时,为啥是从30~0;不用关心第31位吗?
答:
题目中规定了 0 ≤ Ai <2^31,所以循环到30就够了
问:
什么是从i=30开始而不是0
答:
因为是求最大值,所以从最高位开始比较,要有限保证最高位为1
并查集:O(1)
1、 将两个集合合并
2、 询问两个元素是否再一个集合当中
基本原理:每个集合用一颗树来表示,树根的编号就是整个集合的编号,每个节点存储它的父节点,p[x]表示x的父节点;
问题一:如何判断树根:if(p[x] == x) x就是树根
问题二:如何求x的集合编号:while(p[x] != x) x = p[x]---->包含路径压缩算法(优化)
问题三:如何合并两个集合:px是x的集合编号,py是y的集合编号,p[x] = y
核心操作:
public static void find(int x){ // 返回x的祖宗节点 + 路径压缩
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
朴素并查集--无扩展
import java.util.*;
public class Main{
// 每个集合用树来存储
static int N = 100010;
// 建立父节点数组
static int[] p = new int[N];
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
// 初始化父节点数组
for(int i = 1; i <= n; i++) p[i] = i;
while(m-- != 0){
char s = sc.next().charAt(0);
int a = sc.nextInt();
int b = sc.nextInt();
if(s == 'M'){
//将a根节点的父节点指向b的根节点--》实现两个集合的合并
p[find(a)] = find(b);
}else{
// 如果a的根节点等于b的根节点-->在同一个集合中
if(find(a) == find(b)) System.out.println("Yes");
else System.out.println("No");
}
}
}
// 返回x的根节点
public static int find(int x){
// 如果父节点不等于根节点,则递归寻找
if(p[x] != x) p[x] = find(p[x]);
//返回x的所在的根节点
return p[x];
}
}
以下扩展情况:维护集合大小
import java.util.*;
public class Main{
static int N = 100010;
// 每个集合
static int[] p = new int[N];
// 每个集合的大小
static int[] size = new int[N];
// 返回集合(x)的跟节点
public static int find(int x){
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
// 对每个集合以及集合大小进行初始化
for(int i = 0; i < n; i++){
p[i] = i;
size[i] = 1;
}
while(m -- != 0){
String s = sc.next();
if(s.equals("C")){
int a = sc.nextInt();
int b = sc.nextInt();
//不加判断,当a与b集合相同时, 执行了自己加自己,不符合题意,需要特判
if(find(a) != find(b)){
// 合并后的连通块数量(有多少个点)
size[find(b)] += size[find(a)];
// a集合的父节点执行b集合
p[find(a)] = find(b);
}
}
else if(s.equals("Q1")){
int a = sc.nextInt();
int b = sc.nextInt();
if(find(a) == find(b)) System.out.println("Yes");
else System.out.println("No");
}
else{
int a = sc.nextInt();
// 查询a所在集合的连通块大小,找到a的根,并统计根的数量
System.out.println(size[find(a)]);
}
}
}
}
堆:
堆
:就是一个用一维数组来表示一个完全二叉树的这么一个数据结构。所谓二叉树就是一种树,每一个父节点,有最多两个子节点,一般叫做左右子树
完美二叉树:是一个二叉树层数为k的时候,它的元素数量等于2k-1
而一个完全二叉树可以理解为是一个完美二叉树缺少一部分或者不缺少一部分的二叉树,但是内容一定是从上到下,从左到右的填充,也就是缺少的部分总在右边;
小根堆: 即根节点小于等于它的左孩子,也小于等于它的右孩子,且每个点都小于左右子节点;左孩子是左边集合的最小值,右孩子是右边的最小值
根节点是左右孩子的最小值--->推论出:根节点为堆的最小值
如何手写一个堆:
size-->表示堆的大小;
插入一个数:
heap[++size] = x;
up(size);
求集合当中的最小值
heap[1];
删除最小值:
heap[1] = heap[size];
size--;
down(1);
删除任意一个元素:
heap[k] = heap[size];
size--;
down(k);
up(k);
修改任意一个元素:
heap[k] = x;
down(k);
up(k);
堆排序:
步骤:(输出前m个的最小值)
- 初始化堆
- 建堆
- down操作
其中down操作的实现过程:
比较三个点的最小值,如果不符合堆的定义那么就交换、递归执行down操作
import java.util.*;
import java.io.*;
public class Main{
static int N = 100010;
// 定义堆
static int[] h = new int[N];
// 确定堆的大小
static int size;
// 当数在三个数中大时,使数往下沉
public static void down(int u){
// 设三个数的最小值为t;
int t = u;
// u*2为 u的左儿子; u*2+1 为u的右儿子;
// 如果左儿子的下标小于堆的大小,则表示存在这个点;
// 并且左儿子值比最小t的值小,则将t指向左儿子
if(u*2 <= size && h[u*2] < h[t]) t = u * 2;
// 如果右儿子的下标小于堆的大小,则表示存在这个点;
// 并且右儿子值比最小t的值小,则将t指向右儿子
if(u*2+1 <= size && h[u*2+1] < h[t]) t = u*2+1;
// 如果最后t的最小值不是自己(u);
// 那么交换两个下标所在的值;交换完在down一下,防止破坏堆结构;
if(t != u){
int temp = h[u];
h[u] = h[t];
h[t] = temp;
down(t);
}
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
String[] s = br.readLine().split(" ");
int n = Integer.parseInt(s[0]);
int m = Integer.parseInt(s[1]);
String[] str = br.readLine().split(" ");
// 初始化堆
for(int i = 1; i <= n; i++){
h[i] = Integer.parseInt(str[i-1]);
}
// 设置堆的大小
size = n;
//通过递推可得到时间复杂度:建堆-->时间复杂度为O(n)
for(int i = n/2;i != 0;i--){
down(i);
}
while(m-- != 0){
// 输出最当前最小值,也就是堆顶
bw.write(h[1]+" ");
// 将最后的值覆盖掉堆顶--就是删掉堆顶
h[1] =h[size];
// 堆大小--
size--;
// 在把堆顶down一下 找出最小值;
down(1);
}
bw.flush();
br.close();
bw.close();
}
}
实现up操作:
u/2为u的父节点,h[u] < h[u/2]-->子节点小于父节点;交换完后,up(u父节点的父节点);
首先堆是完全二叉树:(以下是编号)
1
2 3
4 5 6 7
2 / 2 = 1, 3 / 2 = 1.
4 / 2 = 2, 5 / 2 = 2, 6 / 2 = 3, 7 / 2 = 3
通过上面操作就能找到父节点;
public static void up(int u){
if(u / 2 > 0 && h[u] < h[u / 2]){
heapSwap(u, u / 2);
up(u/2);
}
}
模拟堆:
import java.util.*;
import java.io.*;
public class Main{
static int N = 100010;
static int[] h = new int[N];
static int[] ph = new int[N]; //存放第k个点的值的下标
static int[] hp = new int[N]; //存放队中点的值是第几个插入的
static int size; //size 记录的是堆当前的数据多少
public static void down(int u){
int t = u;
if(u*2 <=size && h[u*2] < h[t]) t = u*2;
if(u*2+1 <= size && h[u*2+1] < h[t]) t = u*2+1;
if(t != u){
heap_swap(u,t);
down(t);
}
}
public static void up(int u){
if(u / 2 > 0 && h[u] < h[u / 2]){
heap_swap(u,u/2);
up(u/2);
}
}
public static void heap_swap(int u,int v)
{
// 相对照
// swap(h[u],h[v]); //值交换
// swap(hp[u],hp[v]); //堆中点的插入顺序(编号)交换
// swap(ph[hp[u]],ph[hp[v]]); //对编号第h[u] h[v]的值交换
swap(h,u,v);
swap(hp, u, v);
swap(ph, hp[u], hp[v]);
}
public static void swap(int[] a, int u, int v){
int tmp = a[u];
a[u] = a[v];
a[v] = tmp;
}
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
int n = Integer.parseInt(br.readLine());
size = 0;
int m = 0;
while(n-- != 0){
String[] s = br.readLine().split(" ");
String op = s[0];
if("I".equals(op)){
int x = Integer.valueOf(s[1]);
m++;
h[++size]=x;
// ph[m]=size;
// hp[size]=m;
// 建立映射关系即:第m个插入的数的编号是size;
ph[m] = size;
//第size编号下的是第m个插入的数;
hp[size] = m;
// 将插入的数向上调整
up(size);
}else if("PM".equals(op)) bw.write(h[1]+"\n");
else if("DM".equals(op)){
heap_swap(1,size);
size--;
down(1);
}else if("D".equals(op)){
int k = Integer.parseInt(s[1]);
int u=ph[k]; //这里一定要用u=ph[k]保存第k个插入点的下标
heap_swap(u,size); //因为在此处heapSwap操作后ph[k]的值已经发生
size--; //如果在up,down操作中仍然使用ph[k]作为参数就会发生错误
up(u);
down(u);
}else if("C".equals(op)){
int k = Integer.parseInt(s[1]);
int x = Integer.parseInt(s[2]);
h[ph[k]]=x; //此处由于未涉及heapSwap操作且下面的up、down操作只会发生一个所以
down(ph[k]); //所以可直接传入ph[k]作为参数
up(ph[k]);
}
}
bw.flush();
br.close();
bw.close();
}
}
哈希表:
求质数:
import java.util.Scanner;
public class 求质数 {
public static void main(String[] args) {
for(int i = 100000;;i++){
boolean flag = true;
for(int j = 2; j* j <= i; j++){
if(i % j == 0){
flag = false;
break;
}
}
if(flag){
System.out.println(i);
break;
}
}
}
}
拉链法:
import java.util.*;
import java.io.*;
public class Main{
//h[]是哈希函数的一维数组
//N为数据范围外的最小质数
//模N这个数一般要取成质数且离2的整次幂尽可能的远---减少哈希冲突的概率
static int N = 100003;
static int[] h = new int[N];
//e[]是链表中存的值
static int[] e = new int[N];
//ne[]是指针存的指向的地址
static int[] next = new int[N];
//idx是当前指针
static int idx;
// 插入操作
public static void insert(int x){
//对负数的处理,k是哈希值
//如果x%N的余数为零,+N则一定为正数,然后再取模
int k = (x % N + N) % N;
// 单链表的实现
//头插法
e[idx] = x;
next[idx] = h[k];
h[k] = idx++;
}
// 查找元素是否存在
public static boolean find(int x){
int k = (x% N + N) % N;
for(int i = h[k]; i != -1; i=next[i]){
//找到返回true
if(e[i] == x) return true;
}
return false;
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
//初始化h[]
for(int i=0;i<N;i++){
h[i]=-1;
}
while(n-->0){
String[] s = br.readLine().split(" ");
int x = Integer.parseInt(s[1]);
if(s[0].equals("I")){
insert(x);
}else{
if(find(x))System.out.println("Yes");
else System.out.println("No");
}
}
}
}
开放寻址法:
好处:只开一个数组即可;
package ACWing.数据结构与算法;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class 开放寻址法_哈希表 {
//一般开到范围的两到三倍
//N的值也需要取模判断一下
static int N= 200003;
static int[] h = new int[N];
//设当前值不在题目给出的范围中,表示当前无数据--空数据
static int bound = (int)(1e9+1);
public static int find(int x){
int k = ( x % N + N) % N;
//如果当前空间有数据,且该空间的数据不等于我们要查找的数据
//那么继续往下寻找
while(h[k] != bound && h[k] != x){
k++;
if(k== N) k = 0;
}
//返回的情况有两种
/*
1、k指代的是当前的空间没有人
2、k指代的是当前的空间有人且就是我们要查找的元素
*/
return k;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
for(int i = 0; i < N; i++) h[i] = bound;
while(n-- != 0){
String[] s = br.readLine().split(" ");
String op = s[0];
int x = Integer.parseInt(s[1]);
int k = find(x);
if(op.equals("I")){
h[k] = x;
}else{
//如果当前元素不为空
if(h[k] != bound) System.out.println("Yes");
//为空
else System.out.println("No");
}
}
br.close();
}
}
字符串哈希方式:
- 先将字符串转换成P进制的数字;
- 然后求出前缀和的哈希值
怎么求前缀和的哈希值:
将字符串看成P进制的数:
例:求ABCD的哈希值
看成P进制的话,该字符串有四位:
如果数据量太大的话,我们需要mod上一个Q,通过取模可以映射到(0-Q-1)上的数
注意:
不能映射为0,因为0与任何数进行运算都为零,这样会造成数据重复;
当映射的时候必定会出现两个不同的数取模成相同的数
解决的方法:假定人品足够好,不会出现冲突,且经验取值为:P=131或者13331,Q=2^64次方的时候,可以避免99.99%的冲突;
求哈希值:
h[]数组表示前缀和的哈希值;
h[R] : 1-R的前缀和hash值
h[L-1] : 1-(L-1)的前缀和hash值
在h[R]中:
在h[L-1]中:
我们的目标时求出L-R的前缀和哈希值,以下看图说话
对于区间和公式的理解:h[l,r]=h[r]−h[l−1]×P^(r−l+1)
求L-R 也就是求D-E,也就是求4-5
123*100= 12300;12345-12300 = 45;
ABC*100 = ABC00; ABCDE-ABC=DE;
h[R]-h[L-1]*P^(R-1-(L-2));
h[R] - h[L-1]*P^(R-L+1)
解题步骤:
- 先保存每位的权值;
- 求每个字符的前缀和哈希值
- 获取区间的前缀和哈希值
- 调用函数进行比较
字符串前缀哈希法:
import java.util.*;
import java.io.*;
public class Main{
static int N = 100010;
static int h[] = new int[N];
static int p[] = new int[N];
static int P = 131;
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
String[] s = br.readLine().split(" ");
//输入长度为n的字符串
int n = Integer.parseInt(s[0]);
//m次询问
int m = Integer.parseInt(s[1]);
String str = br.readLine();
p[0] =1; //一定不要忘记设置为1;
for(int i = 1; i <= n; i++){
//预处理保存每位的权值
p[i] = p[i-1]*P;
//获取前缀和哈希值
h[i] = h[i-1]*P+str.charAt(i-1);
}
while(m-- != 0){
String[] s1 = br.readLine().split(" ");
int l1 = Integer.parseInt(s1[0]);
int r1 = Integer.parseInt(s1[1]);
int l2 = Integer.parseInt(s1[2]);
int r2 = Integer.parseInt(s1[3]);
if(getHash(l1,r1) == getHash(l2,r2)) bw.write("Yes"+"\n");
else bw.write("No"+"\n");
}
bw.flush();
br.close();
bw.close();
}
public static long getHash(int l,int r){
return h[r] - h[l-1]*p[r-l+1];
}
}
Trie、并查集、堆、Hash表学习过程以及遇到的问题的更多相关文章
- 栈&队列&并查集&哈希表(julyedu网课整理)
date: 2018-11-25 08:31:30 updated: 2018-11-25 08:31:30 栈&队列&并查集&哈希表(julyedu网课整理) 栈和队列 1. ...
- LOJ2014 SCOI2016 萌萌哒 并查集、ST表优化连边
传送门 一个朴素的做法就是暴力连边并查集,可是这是\(O(n^2)\)的.发现每一次连边可以看成两个区间覆盖,这两个区间之间一一对应地连边.可线段树对应的两个节点的size可能不同,这会导致" ...
- Colored Sticks - poj2513(trie + 并查集)
问题便转化为:给定一个图,是否存在“一笔画”经过涂中每一点,以及经过每一边一次.这样就是求图中是否存在欧拉路Euler-Path.由图论知识可以知道,无向图存在欧拉路的充要条件为:① 图是连通的:② ...
- NOIp 数据结构专题总结 (1):STL、堆、并查集、ST表、Hash表
系列索引: NOIp 数据结构专题总结 (1) NOIp 数据结构专题总结 (2) STL structure STL 在 OI 中的运用:https://oi.men.ci/stl-in-oi/ s ...
- [LOJ#6198]谢特[后缀数组+trie+并查集]
题意 给你一个长度为 \(n\) 的字符串,问 \(LCP(i,j)+(w_i\ xor\ w_j)\) 的最大值,其中 \(LCP\) 表示两个后缀的最长公共前缀. \(n\le 10^5\) 分析 ...
- HDU 6326.Problem H. Monster Hunter-贪心(优先队列)+流水线排序+路径压缩、节点合并(并查集) (2018 Multi-University Training Contest 3 1008)
6326.Problem H. Monster Hunter 题意就是打怪兽,给定一棵 n 个点的树,除 1 外每个点有一只怪兽,打败它需要先消耗 ai点 HP,再恢复 bi点 HP.求从 1 号点出 ...
- 海量路由表能够使用HASH表存储吗-HASH查找和TRIE树查找
千万别! 非常多人这样说,也包括我. Linux内核早就把HASH路由表去掉了.如今就仅仅剩下TRIE了,只是我还是希望就这两种数据结构展开一些形而上的讨论. 1.hash和trie/radix ha ...
- 2017"百度之星"程序设计大赛 - 资格赛【1001 Floyd求最小环 1002 歪解(并查集),1003 完全背包 1004 01背包 1005 打表找规律+卡特兰数】
度度熊保护村庄 Accepts: 13 Submissions: 488 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/3276 ...
- Colored Sticks POJ - 2513 并查集+欧拉通路+字典树hash
题意:给出很多很多很多很多个棒子 左右各有颜色(给出的是单词) 相同颜色的可以接在一起,问是否存在一种 方法可以使得所以棒子连在一起 思路:就是一个判欧拉通路的题目,欧拉通路存在:没奇度顶点 或者 ...
随机推荐
- how to enable vue cli auto open the localhost url
how to enable vue cli auto open the localhost URL bad you must click the link by manually, waste of ...
- SVG viewBox & coordinate system
SVG viewBox & coordinate system https://codepen.io/xgqfrms/pen/abOOrjp <html> <body> ...
- NGK公链:在规则明确的环境下运行超级节点机制
首先要跟大家明确的一点是,21个超级节点是投票选举出来的,并不是系统在创立之初就已经确定好了的.那么相信大家也一定很好奇,这21个超级节点是通过什么方式产生? NGK.IO对分布式超级节点使用了一个自 ...
- Django Admin 实现三级联动的示例代码(省市区)===>小白级
一 使用环境 开发系统: windows IDE: pycharm 数据库: msyql,navicat 编程语言: python3.7 (Windows x86-64 executable in ...
- Hexo一键部署到阿里云OSS并设置浏览器缓存
自建博客地址:https://bytelife.net,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客 本文作者: Jeffrey 本文链接: https://bytel ...
- Rocket broker启动失败?
安装 Rocket 时, 执行 nohup sh bin/mqbroker -n localhost:9876 & 启动 broker 失败 更改其内存试试 在下面目录下 : cd distr ...
- STM32学习笔记——序言
写AVR已经两年了.如果初中时候玩Arduino也算的话,就是6年. 两年以来,我用AVR单片机完成了两个大项目: AVR单片机教程,一时兴起写的,效果不好: MEDS,参赛用的课题,半完成,比赛都结 ...
- 看完我的笔记不懂也会懂----git
Git学习笔记 - 什么是Git - 首次使用Git - DOS常用命令 - Git常用命令 - 关于HEAD - 版本回退 - 工作区.暂存区与版本库 - git追踪的是修改而非文件本身 - 撤销修 ...
- Wireguard 全互联模式(full mesh)配置指南
上篇文章给大家介绍了如何使用 wg-gen-web 来方便快捷地管理 WireGuard 的配置和秘钥,文末埋了两个坑:一个是 WireGuard 的全互联模式(full mesh),另一个是使用 W ...
- 【HTB系列】靶机Querier的渗透测试
出品|MS08067实验室(www.ms08067.com) 本文作者:大方子(Ms08067实验室核心成员) 总结与反思: 1.收集信息要全面 2.用snmp-check检查snmp目标是否开启服务 ...