平衡树 -- Splay & Treap
Treap & Splay学习笔记
前置知识 -- BST
二叉搜索树,一种比较好玩的数据结构,其实现原理是运用每个点的权值构建,其中满足这样的构造方式:
若 \(value > t[x].value\) , 则权值为 \(value\) 的点在 \(x\) 的左子树
反之( \(value < t[x].value\) ) , 则权值为 \(value\) 的点在 \(x\) 右子树
比如我们可以构建这样的 \(BST\) :
4
/ \
3 5
/ \
2 10
/ \
9 12
... 看起来如果这个数据给的比较正的话,就会使查点的时间复杂度为 \(\log{n}\) 的。
但如果这个数据这么给你: 1 2 3 4 5 6 7 8 9 10
那么你的树就变成链了,查的复杂度为 \(n\) 的。
我们发现,在随机数据下,树是趋于平衡的,这样就形成了以随机化为主要思想的平衡树—— Treap
它使用小根堆性质维护树,改变树的形态,却不改变中序遍历。
Treap (rotate)
这是第一种 \(Treap\) ,带旋转的,即用旋转来维护小根堆性质。
具体代码:
void rotate(int &x , int d) { // d 代表左旋还是右旋
int child = t[x].son[d] ;
t[x].son[d] = t[child].son[d ^ 1] , t[child].son[d ^ 1] = x ;
push_up(x) , push_up(x = son) ;
}
普通平衡树的代码:
CODE
#include <bits/stdc++.h>
#define int long long
using namespace std ;
const int N = 1e5 + 10 ;
inline int read() {
int x = 0 , f = 1 ;
char c = getchar() ;
while (c < '0' || c > '9') {
if (c == '-') f = -f ;
c = getchar() ;
}
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0' ;
c = getchar() ;
}
return x * f ;
}
namespace Treap {
#define lson t[x].son[0]
#define rson t[x].son[1]
class Treap_Point_ {
public :
int Rand , value , son[2] , Size , cnt ;
} t[N] ; int numbol = 0 ;
inline void push_up(int x) {
t[x].Size = t[lson].Size + t[rson].Size + t[x].cnt ;
}
inline void rotate(int &x , int d) {
int child = t[x].son[d] ;
t[x].son[d] = t[child].son[d ^ 1] ; t[child].son[d ^ 1] = x ;
push_up(x) ; push_up(x = child) ;
}
inline void New_Value_Create(int value) {
numbol ++ ; t[numbol].Rand = rand() ;
t[numbol].value = value , t[numbol].Size = t[numbol].cnt = 1 ;
}
void Insert(int &x , int value) {
if (!x) {
New_Value_Create(value) ;
x = numbol ;
return ;
}
if (value == t[x].value) {
t[x].cnt ++ ; t[x].Size ++ ;
return ;
}
t[x].Size ++ ;
int d = value > t[x].value ; Insert(t[x].son[d] , value) ;
if (t[x].Rand > t[t[x].son[d]].Rand) rotate(x , d) ;
}
void deleted(int &x , int value) {
if (!x) return ;
if (t[x].value == value) {
if (t[x].cnt > 1) {
t[x].cnt -- , t[x].Size -- ;
return ;
}
if (lson == 0 || rson == 0) {
x = lson + rson ; return ;
}
bool d = t[lson].Rand > t[rson].Rand ;
rotate(x , d) ; deleted(t[x].son[d ^ 1] , value) ;
push_up(x) ;
} else {
t[x].Size -- ;
int d = value > t[x].value ; deleted(t[x].son[d] , value) ;
push_up(x) ;
}
}
int Rank(int x , int value) {
if (!x) return 114514 - 114514 ;
if (t[x].value == value) return t[lson].Size + 1 ;
if (value < t[x].value) return Rank(lson , value) ;
else return Rank(rson , value) + t[x].cnt + t[lson].Size ;
}
int The_K_th(int root , int k) {
int x = root ;
while (114514) {
if (k <= t[lson].Size) x = lson ;
else if (k > t[x].cnt + t[lson].Size) k -= t[x].cnt + t[lson].Size , x = rson ;
else return t[x].value ;
}
return 1145141919810ll ;
}
int Precursor(int x , int value) {
if (!x) return -1145141919810ll ;
if (t[x].value >= value) return Precursor(lson , value) ;
else return max(t[x].value , Precursor(rson , value)) ;
}
int Subsequent(int x , int value) {
if (!x) return 1145141919810ll ;
if (t[x].value <= value) return Subsequent(rson , value) ;
else return min(t[x].value , Subsequent(lson , value)) ;
}
void Print_Tree(int x) {
cerr << x << ' ' << t[x].value << ":\n" ;
cerr << lson << ' ' << rson << '\n' ;
if (lson) Print_Tree(lson) ;
if (rson) Print_Tree(rson) ;
}
#undef lson
#undef rson
} using namespace Treap ;
int n , opt , root ;
signed main() {
#ifndef ONLINE_JUDGE
freopen("1.in" , "r" , stdin) ;
freopen("1.out" , "w" , stdout) ;
#endif
n = read() ;
int x , y , z ;
for (int i = 1 ; i <= n ; ++ i) {
opt = read() ;
// cerr << opt << '\n' ;
switch (opt) {
case 1 :
x = read() ;
Insert(root , x) ;
break ;
case 2 :
x = read() ;
deleted(root , x) ;
break ;
case 3 :
x = read() ;
cout << Rank(root , x) << '\n' ;
break ;
case 4 :
x = read() ;
cout << The_K_th(root , x) << '\n' ;
break ;
case 5 :
x = read() ;
cout << Precursor(root , x) << '\n' ;
break ;
case 6 :
x = read() ;
cout << Subsequent(root , x) << '\n' ;
break ;
}
}
}
Treap -- without rotating (FHQ Treap)
使用分割和合成来维护堆性质。
其中分割可以用值分,也可以按大小分,后者常用来维护序列。
合并时你要保证 \(x\) 在中序遍历时全部位于 \(y\) 前,即 xy
普通平衡树代码:
CODE
#include <bits/stdc++.h>
#define int long long
using namespace std ;
const int N = 3e5 + 10 ;
const int INF = 1145141919810 ;
inline int read() {
int x = 0 , f = 1 ;
char c = getchar() ;
while (c < '0' || c > '9') {
if (c == '-') f = -f ;
c = getchar() ;
}
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0' ;
c = getchar() ;
}
return x * f ;
}
namespace Data_Structure {
namespace FHQ_Treap { // As Well as Treap With No Rotate .
#define lson t[x].son[0]
#define rson t[x].son[1]
class Treap_Point_ {
public:
int Rand , cnt , Size , value , son[2] ;
} t[N] ; int numbol = 0 , root = 0 ;
inline void push_up(int x) {
t[x].Size = t[lson].Size + t[rson].Size + t[x].cnt ;
}
int Merge(int x , int y) {
if (!x || !y) return x | y ;
if (t[x].Rand < t[y].Rand) {
rson = Merge(rson , y) ; push_up(x) ;
return x ;
} else {
t[y].son[0] = Merge(x , t[y].son[0]) ; push_up(y) ;
return y ;
}
}
void Split(int id , int value , int &x , int &y) {
if (!id) {
x = y = 0 ; return ;
}
if (t[id].value <= value) {
x = id ; Split(t[id].son[1] , value , t[x].son[1] , y) ;
push_up(x) ;
} else {
y = id ; Split(t[id].son[0] , value , x , t[y].son[0]) ;
push_up(y) ;
}
}
inline int New_Value_Create(int value) {
++ numbol ;
t[numbol].value = value , t[numbol].Size = t[numbol].cnt = 1 ; t[numbol].Rand = rand() ;
return numbol ;
}
int The_K_th(int root , int k) {
int x = root ;
while (114514) {
if (k <= t[lson].Size) x = lson ;
else if (k > t[lson].Size + t[x].cnt) k -= t[lson].Size + t[x].cnt , x = rson ;
else return t[x].value ;
}
}
void Insert(int value) {
int x , y ;
Split(root , value , x , y) ;
root = Merge(Merge(x , New_Value_Create(value)) , y) ;
}
void Delete(int value) {
int x , y , z ;
Split(root , value , x , z) ;
Split(x , value - 1 , x , y) ;
y = Merge(t[y].son[0] , t[y].son[1]) ;
root = Merge(Merge(x , y) , z) ;
}
int Rank(int value) {
int x , y , ans ;
Split(root , value - 1 , x , y) ;
ans = t[x].Size + 1 ;
root = Merge(x , y) ;
return ans ;
}
int Precursor(int value) {
int x , y , ans ;
Split(root , value - 1 , x , y) ;
ans = The_K_th(x , t[x].Size) ;
root = Merge(x , y) ;
return ans ;
}
int Subsequent(int value) {
int x , y , ans ;
Split(root , value , x , y) ;
ans = The_K_th(y , 1) ;
root = Merge(x , y) ;
return ans ;
}
#undef lson
#undef rson
}
} using namespace Data_Structure ;
using namespace FHQ_Treap ;
int n , opt ;
signed main() {
#ifndef ONLINE_JUDGE
freopen("1.in" , "r" , stdin) ;
freopen("1.out" , "w" , stdout) ;
#endif
n = read() ; int x , y , z ; int num = 0 ;
while (n --) {
opt = read() ;
switch (opt) {
case 1 :
x = read() ; Insert(x) ;
break ;
case 2 :
x = read() ; Delete(x) ;
break ;
case 3 : ++ num ;
x = read() ; cout << Rank(x) << '\n' ;
break ;
case 4 : ++ num ;
x = read() ; cout << The_K_th(root ,x) << '\n' ;
break ;
case 5 : ++ num ;
x = read() ; cout << Precursor(x) << '\n' ;
break ;
case 6 : ++ num ;
x = read() ; cout << Subsequent(x) << '\n' ;
break ;
}
}
}
Splay (伸展树)
一种神奇数据结构。
其实现是基于 局部性原理
局部性原理
局部性(locality) 可以分为时间局部性(temporal locality) 和空间局部性(spatial locality)
假如你在书桌旁工作,需要查阅某本书籍,你又发现这本书用的非常之经常,于是你就把书放在手边,不再放回去。这就是 时间局部性
如果你在图书馆里找到了蓝书,但发现蓝书上并没有讲 \(Splay\) ,但是其隔壁的书上可能有 \(Splay\) . 这就是 空间局部性
于是我们整体归纳一下:
1> 刚刚被访问的元素极有可能再次被访问
2> 刚刚被访问的元素旁极有可能放着下一个被访问的元素
而我们的 \(Splay\) , 使用了这一原理,虽然可能单独的复杂度是 \(O(n)\) , 但均摊为 \(O(\log n)\)
Splay 用法
我们使用 \(E\) , 则节点 \(E\) 就会被上升到根:

因为两侧子树的结构在不断地调整,所以形象称之为伸展。但是好像进行单次的旋转,某些情况下复杂度依然高达 \(O(n)\)
我们来看最坏情况下的旋转5:

还是一条链!
这种情况如何伸展?
双层伸展
如果 \(x\) 和 \(fa_x\) 和 \(fa_{fa_x}\) 在同一条链上时,我们采用双层伸展,使其高度迅速减小

下面展示了一种因为节点更多,更加效率的树:

尽管 \(Splay\) 不像 \(AVL\) 和 红黑树这么严格,如果存在超深节点,就会因为 \(Splay\) 操作使得高度迅速减半。因此保证了整体的高效率。
具体实现
rotate : 其实很像 \(Treap\) 的,就是需要保存父亲。
template <typename T> void rotate(T x) {
int Grandfather = t[fath].father , Old_fa = fath ; bool d = Get_Direction(x) ;
t[Grandfather].son[Get_Direction(Old_fa)] = x , t[Old_fa].son[d] = t[x].son[d ^ 1] , t[x].son[d ^ 1] = Old_fa ;
Recognize_Father(Grandfather) ; Recognize_Father(Old_fa) ; Recognize_Father(x) ;
push_up(Old_fa) , push_up(x) ;
}
Splay : 注意一点细节即可。
void Splay(int x , int End) {
while (fath != End) {
int Old_fa = fath , Grandfather = t[fath].father ;
if (Grandfather != End) {
if (Get_Direction(x) == Get_Direction(Old_fa)) rotate(Old_fa) ;
else rotate(x) ;
}
rotate(x) ;
}
if (!End) root = x ;
}
一个很重要的事情:
你是否觉得Splay很高级很好用?那么我要告诉你,缺点就是:
常数巨大
Splay 的应用
由于旋旋旋旋,所以可以进行一点区间操作。
例: \([l , r]\) 将 \(l - 1\) 放置于根,然后将 \(r + 1\) 放到根的右儿子,那么根的右儿子的左子树就是这个区间。
呃呃呃,由于 \(Splay\) 常数巨大所以跑的有点小慢(⊙o⊙)…
例题: luoguP3380树套树
CODE
#include <bits/stdc++.h>
#define getchar() getchar_unlocked()
#define int long long
using namespace std ;
const int N = 4e5 + 10 ;
const int INF = 2147483647 ;
const int Size = 1e8 + 1 ;
inline int read() {
int x = 0 , f = 1 ;
char c = getchar() ;
while (c < '0' || c > '9') {
if (c == '-') f = -f ;
c = getchar() ;
}
while (c >= '0' && c <= '9') {
x = x * 10 + c - '0' ;
c = getchar() ;
}
return x * f ;
}
int root[N * 10] ;
namespace SPLAY {
#define lson t[x].son[0]
#define rson t[x].son[1]
#define fath t[x].father
class Splay_Point_ {
public :
int son[2] , father , value , Size , cnt , pos ;
} t[N * 20] ; int numbol = 0 ;
void Print(int x) {
cerr << t[x].pos << ":\n" ;
cerr << t[lson].pos << ' ' << t[rson].pos << '\n' ;
if (lson) Print(lson) ;
if (rson) Print(rson) ;
}
int Create(int value) {
numbol ++ ;
t[numbol].cnt = t[numbol].Size = 1 ; t[numbol].value = value ;
return numbol ;
}
inline void push_up(int x) {
t[x].Size = t[lson].Size + t[rson].Size + t[x].cnt ;
}
inline bool Get_Direction(int x) {
return t[fath].son[1] == x ;
}
inline void Recognize_Father(int x) {
t[lson].father = t[rson].father = x ;
}
inline void rotate(int x) {
int Grandfather = t[fath].father , Old_fa = fath ; bool d = Get_Direction(x) ;
t[Grandfather].son[Get_Direction(Old_fa)] = x , t[Old_fa].son[d] = t[x].son[d ^ 1] , t[x].son[d ^ 1] = Old_fa ;
Recognize_Father(Grandfather) ; Recognize_Father(Old_fa) ; Recognize_Father(x) ;
push_up(Old_fa) , push_up(x) ;
}
void Splay(int x , int End , int Tree) {
while (fath != End) {
int Old_fa = fath , Grandfather = t[fath].father ;
if (Grandfather != End) {
if (Get_Direction(x) == Get_Direction(Old_fa)) rotate(Old_fa) ;
else rotate(x) ;
}
rotate(x) ;
}
if (!End) root[Tree] = x ;
}
void Insert(int x , int pos , int value , int Tree) {
int needfather = 0 ;
while (x && t[x].pos != pos) {
needfather = x ;
x = t[x].son[pos > t[x].pos] ;
}
if (x) {
t[x].Size ++ ; t[x].cnt ++ ; Splay(x , 0ll , Tree) ;
} else {
x = Create(value) ; fath = needfather ; t[fath].son[pos > t[fath].pos] = x ; t[x].pos = pos ;
Splay(x , 0ll , Tree) ;
}
}
int Rank(int root , int pos , int Tree) {
if (root == 0) return 0 ;
int x = root , ans = 0 , need = 0 ;
while (x) {
need = x ;
if (t[x].pos > pos) x = lson ;
else ans += t[x].cnt + t[lson].Size , x = rson ;
}
Splay(need , 0ll , Tree) ;
return ans ;
}
int Find(int root , int pos , int Tree) {
int x = root ;
while (x && t[x].pos != pos) x = t[x].son[pos > t[x].pos] ;
if (x == 0) return 0 ;
else {
Splay(x , 0ll , Tree) ; return x ;
}
}
int Precursor_Or_Subsquent(int x , bool d , int Tree) {
Splay(x , 0ll , Tree) ;
x = t[x].son[d] ;
while (t[x].son[d ^ 1]) x = t[x].son[d ^ 1] ;
return x ;
}
void Delete(int root , int val , int Tree) {
int x = Find(root , val , Tree) ;
if (!x) return ;
if (t[x].cnt > 1) {
t[x].cnt -- ; t[x].Size -- ;
Splay(x , 0ll , Tree) ;
return ;
}
int Pre = Precursor_Or_Subsquent(x , 0 , Tree) , Sub = Precursor_Or_Subsquent(x , 1 , Tree) ;
Splay(Pre , 0ll , Tree) ; Splay(Sub , Pre , Tree) ; t[Sub].son[0] = 0 ;
}
#undef lson
#undef rson
#undef fath
}
namespace SEGMENT_TREE {
#define lson t[id].son[0]
#define rson t[id].son[1]
#define mid ((l + r) >> 1)
using SPLAY :: Insert ;
using SPLAY :: Rank ;
using SPLAY :: Delete ;
using SPLAY :: Print ;
struct Tree_Point_ {
int son[2] ;
} t[N * 20] ; int numbol = 0 ;
int New_Code() {
++ numbol ;
Insert(root[numbol] , INF , INF , numbol) ; Insert(root[numbol] , -INF , INF , numbol) ;
return numbol ;
}
void updata(int &id , int l , int r , int x , int v) {
if (!id) id = New_Code() ;
Insert(root[id] , v , x , id) ;
if (l == r) return ;
if (x <= mid) updata(lson , l , mid , x , v) ;
else updata(rson , mid + 1 , r , x , v) ;
}
int GetRank(int id , int l , int r , int x , int y , int v) {
if (l == r) return 1 ;
if (v <= mid) return GetRank(lson , l , mid , x , y , v) ;
else {
int ans = Rank(root[lson] , y , lson) - Rank(root[lson] , x - 1 , lson) ;
return GetRank(rson , mid + 1 , r , x , y , v) + ans ;
}
}
int The_Kth(int id , int l , int r , int x , int y , int k) {
if (l == r) return l ;
int ans = Rank(root[lson] , y , lson) - Rank(root[lson] , x - 1 , lson) ;
if (ans >= k) return The_Kth(lson , l , mid , x , y , k) ;
else return The_Kth(rson , mid + 1 , r , x , y , k - ans) ;
}
void Delete_Tree(int id , int l , int r , int x , int v) {
Delete(root[id] , v , id) ;
if (l == r) return ;
if (x <= mid) Delete_Tree(lson , l , mid , x , v) ;
else Delete_Tree(rson , mid + 1 , r , x , v) ;
}
int Precursor(int id , int l , int r , int x , int y , int v) {
if (Rank(root[id] , y , id) - Rank(root[id] , x - 1 , id) == 0) return -INF ;
if (l == r && l == v) return -INF ;
if (l == r) return l ;
if (v <= mid) return Precursor(lson , l , mid , x , y , v) ;
else {
int ans = Precursor(rson , mid + 1 , r , x , y , v) ;
if (ans == -INF) return Precursor(lson , l , mid , x , y , v) ;
else return ans ;
}
}
int Subsquent(int id , int l , int r , int x , int y , int v) {
if (Rank(root[id] , y , id) - Rank(root[id] , x - 1 , id) == 0) return INF ;
if (l == r && l == v) return INF ;
if (l == r) return l ;
if (v >= mid + 1) return Subsquent(rson , mid + 1 , r , x , y , v) ;
else {
int ans = Subsquent(lson , l , mid , x , y , v) ;
if (ans == INF) return Subsquent(rson , mid + 1 , r , x , y , v) ;
else return ans ;
}
}
#undef lson
#undef rson
#undef mid
} using namespace SEGMENT_TREE ;
int n , m ; int a[N] , rad , opt ;
signed main() {
#ifndef ONLINE_JUDGE
freopen("1.in" , "r" , stdin) ;
freopen("1.out", "w" ,stdout) ;
#endif
n = read() , m = read() ;
for (int i = 1 ; i <= n ; ++ i) {
a[i] = read() ;
updata(rad , 0 , Size , a[i] , i) ;
}
int x , y , z ; int num = 0 ;
for (int i = 1 ; i <= m ; ++ i) {
opt = read() ; x = read() , y = read() ;
switch (opt) {
case 3 :
Delete_Tree(rad , 0 , Size , a[x] , x) ;
a[x] = y ;
updata(rad , 0 , Size , a[x] , x) ;
break ;
case 1 :
z = read() ;
printf("%lld\n" , GetRank(rad , 0 , Size , x , y , z)) ;
break ;
case 2 :
z = read() ;
printf("%lld\n" , The_Kth(rad , 0 , Size , x , y , z)) ;
break ;
case 4 :
z = read() ;
printf("%lld\n" , Precursor(rad , 0 , Size , x , y , z)) ;
break ;
case 5 :
z = read() ;
printf("%lld\n" , Subsquent(rad , 0 , Size , x , y , z)) ;
break ;
}
}
}
完结撒花 \(\color{pink}✿✿ヽ(°▽°)ノ✿\)
平衡树 -- Splay & Treap的更多相关文章
- UOJ#55. 【WC2014】紫荆花之恋 点分树 替罪羊树 平衡树 splay Treap
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ55.html 题解 做法还是挺容易想到的. 但是写的话…… 首先这种题如果只要求一棵树中的满足条件的点数( ...
- 数组splay ------ luogu P3369 【模板】普通平衡树(Treap/SBT)
二次联通门 : luogu P3369 [模板]普通平衡树(Treap/SBT) #include <cstdio> #define Max 100005 #define Inline _ ...
- hiho #1329 : 平衡树·Splay
#1329 : 平衡树·Splay 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Ho:小Hi,上一次你跟我讲了Treap,我也实现了.但是我遇到了一个关键的问题. ...
- 洛谷P3369 【模板】普通平衡树(Treap/SBT)
洛谷P3369 [模板]普通平衡树(Treap/SBT) 平衡树,一种其妙的数据结构 题目传送门 题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 插入x数 删除 ...
- [luogu P3369]【模板】普通平衡树(Treap/SBT)
[luogu P3369][模板]普通平衡树(Treap/SBT) 题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: 插入x数 删除x数(若有多个相同的数,因只删 ...
- Hihocoder 1329 平衡树·Splay(平衡树)
Hihocoder 1329 平衡树·Splay(平衡树) Description 小Ho:小Hi,上一次你跟我讲了Treap,我也实现了.但是我遇到了一个关键的问题. 小Hi:怎么了? 小Ho:小H ...
- AC日记——【模板】普通平衡树(Treap/SBT) 洛谷 P3369
[模板]普通平衡树(Treap/SBT) 思路: 劳资敲了一个多星期: 劳资终于a了: 劳资一直不a是因为一个小错误: 劳资最后看的模板: 劳资现在很愤怒: 劳资不想谈思路!!! 来,上代码: #in ...
- 平衡树——splay 二
上文传送门:平衡树--splay 一 - yi_fan0305 - 博客园 (cnblogs.com) OK,我们继续上文,来讲一些其他操作. 七.找排名为k的数 和treap的操作很像,都是通过比较 ...
- 平衡树——splay 一
splay 一种平衡树,同时也是二叉排序树,与treap不同,它不需要维护堆的性质,它由Daniel Sleator和Robert Tarjan(没错,tarjan,又是他)创造,伸展树是一种自调整二 ...
- 【BZOJ3224】Tyvj 1728 普通平衡树 Splay
Description 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:1. 插入x数2. 删除x数(若有多个相同的数,因只删除一个)3. 查询x数的排名(若有多个相同的数 ...
随机推荐
- 详解Web应用安全系列(4)失效的访问控制
在Web安全中,失效的访问控制(也称为权限控制失效或越权访问)是指用户在不具备相应权限的情况下访问了受限制的资源或执行了不允许的操作.这通常是由于Web应用系统未能建立合理的权限控制机制,或者权限控制 ...
- 读懂反向传播算法(bp算法)
原文链接:这里 介绍 反向传播算法可以说是神经网络最基础也是最重要的知识点.基本上所以的优化算法都是在反向传播算出梯度之后进行改进的.同时,也因为反向传播算法是一个递归的形式,一层一层的向后传播误差即 ...
- 基于 .net core 8.0 的 swagger 文档优化分享-根据命名空间分组显示
前言 公司项目是是微服务项目,网关是手撸的一个.net core webapi 项目,使用 refit 封装了 20+ 服务 SDK,在网关中进行统一调用和聚合等处理,以及给前端提供 swagger ...
- ISCTF2023
ISCTF 2023 Misc 签到题 公众号发送:小蓝鲨,我想打ctf ISCTF{W3lcom3_7O_2023ISCTF&BlueShark} 你说爱我?尊嘟假嘟 你说爱我替换.,真嘟替 ...
- yb课堂之高并发项目必备利器之分布式缓存和本地缓存 《十九》
什么是缓存? 程序经常要调用的对象存储在内存中,方便其使用时可以快速调用,不必去数据库或者其他持久化设备中查询,主要就是提高性能 DNS.前端缓存.代理服务器缓存Nginx.应用程序缓存(本地缓存.分 ...
- 洛谷P1464
搜索优化后是记忆化搜索,再优化就是dp了 #include <iostream> #include <utility> using namespace std; typedef ...
- [oeasy]python0139_尝试捕获异常_ try_except_traceback
- 不但要有自己的报错 - 还要保留系统的报错 - 有可能吗? ### 保留报错 ! ...
- [oeasy]python0040_换行与回车的不同_通用换行符_universal_newlines
换行回车 回忆上次内容 区分概念 terminal终端 主机网络中 最终的 端点 TeleTYpewriter 电传打印机 终端硬件 shell 终端硬件基础上的 软件壳子 Console 控制台 主 ...
- 常用ffmpeg命令集合(收藏吧,帮你省一个录屏软件的会员费)
录制屏幕: 全屏的话用screen-capture-recoder下载地址:https://github.com/rdp/screen-capture-recorder-to-video-window ...
- 人工智能时代,前端全栈成就独立开发工程师 next.js 开发实战
next 可以服务端渲染,可以客户端渲染,让前端同事更有性价比,让我们做得可以更多 由于next.js 是基础于react 所以在正式学习next.js 之前我们了解一下react 什么叫模块 ,就 ...