FHQ Treap学习笔记

这篇具有很好参考价值的文章主要介绍了FHQ Treap学习笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前置芝士(了解即可啦~):C++、BST 二叉搜索树、堆、二叉堆


Treap 的概念

Treap 树堆,即树(Tree)+堆(Heap),是一棵弱平衡的二叉搜索树(BST),能同时满足二叉搜索树的性质。

BST 满足任意一个节点的权值都大于等于左子树所有点的权值,且小于等于右子树所有点的权值的性质,我们可以用来求数的排名、前驱和后继,比如这是一棵可爱的 BST:

FHQ Treap学习笔记

众所周知,当添加点的权值依次递增或递减时,一般 BST 将会退化成丑陋的链,such as:

FHQ Treap学习笔记

那么每次添加点的时间复杂度便能被卡成 \(O(n)\)\(n\) 大一点就直接 TLE,寄。

为了规避一般 BST 容易退化成链的问题,Treap 光荣产生力!

Treap 在 BST 的基础上,为每个节点赋上了(随机的)优先值,并在建树时时刻维护堆的性质,由于随机性,建出来的树(也是一种二叉堆)深度期望大小为 \(log\ n\),因此规避了此类问题。

常规 Treap 的两种写法

常规 Treap 有两种写法,Splay 与 FHQ Treap:

Splay 为有旋 Treap,貌似是依靠左旋右旋节点的伸展操作来维护的 Treap,可以康康具体的图:

FHQ Treap学习笔记

详细戳我

FHQ Treap 即无旋 Treap,与 Splay 不同,它只依靠分裂、合并能够进行添加节点、查询排名等等操作,分裂、合并如图:

FHQ Treap学习笔记

FHQ Treap 相对来说代码量较小,也更好理解,应该更适合新手学习吧?

正题:FHQ Treap

FHQ Treap,其中 FHQ 指此做法的发明者——范浩强神犇,是依赖于分裂合并操作实现的 Treap,这种操作方式使得它天生支持维护序列、可持久化等特性,可持久化就以后再补吧。

基础节点信息、建点

首先我们需要一个 \(rt\) 表示树堆的根,\(l\)\(r\) 表示左右儿子的 \(id\),还要存一个自己的权值 \(key\),当前子树大小 \(siz\),以及优先值 \(pri\),有必要的话还要存一个 \(lazy\) 标记。此外还需要一个更新节点信息的函数,当然,随机函数也可以自己手写 qwq,新建点就比较简单,代码如下:

#define uLL unsigned long long
......
int ...rt, gs...;
uLL sd=1;
uLL rd() {
	return sd=sd*1145141ull*1145141ull;
}
struct node {
	int l, r, siz, key, lazy;
	uLL pri;
}tr[100005];
int blt(int key) {//新建点
	++gs;
	tr[gs].key=key;
	tr[gs].lazy=0;
	tr[gs].l=tr[gs].r=0; tr[gs].siz=1;
	tr[gs].pri=rd();
	return gs;
}
void upup(int now) {//更新节点信息
	tr[now].siz=tr[tr[now].l].siz+tr[tr[now].r].siz+1;
}

基础的分裂、合并

上文有说到,FHQ Treap 即无旋 Treap,要依靠分裂、合并这两种基础操作。

按权值分裂

分裂有两种,第一种便是上图的按权值分裂,即给定 \(key\),分裂出所有点权值小于等于 \(key\) 的树堆 \(x\) 与所有点权值都大于 \(key\) 的树堆 \(y\),此分裂可以用于加点或求前驱、后继,带详解注释的代码如下:

void fl_key(int now, int key, int& x, int& y) {
	if(!now) {//当前节点为空
		x=y=0;
		return ;
	}
    down(now);//有些题目需要用lazy标记下传,比如文艺平衡树中的区间反转操作
	if(tr[now].key <= key) {
		x=now;//权值小于等于key的添加到x树堆上,左子树一定在x树堆上
		fl_key(tr[now].r, key, tr[now].r, y);
        //再继续搜右子树,看右子树的点是添加到y树堆上(权值大于key)还是添到当前节点的右子树
	}
	else {
		y=now;//权值大于key了就添加到y树堆上,而右子树一定也在y树堆上
		fl_key(tr[now].l, key, x, tr[now].l);
        //再继续搜左子树,看左子树的点是添加到x树堆上(权值小于等于key)还是添到当前节点的左子树
	}
	upup(now);//更新节点信息
}

盗来的模拟代码过程的 Gif 图:

FHQ Treap学习笔记

上文说过,由于随机性,我们建出来的树堆的深度期望为 \(log\ n\) 层,而分裂相当于每层都只搜到了一次,所以分裂一次的时间复杂度是 \(O(log_2n)\)

按子树大小分裂

而第二种是按子树大小分裂,即给定 \(siz\),优先保留左子树,分裂出大小小于等于 \(siz\) 的树堆 \(x\),和剩余的树堆 \(y\)(可能没有),如图:

FHQ Treap学习笔记

这种分裂可以用于一些特殊的操作(比如说文艺平衡树中的翻转区间),在普通平衡树中可用于求排名对应的值,带详解注释代码如下:

void fl_siz(int now, int siz, int& x, int& y) {
	if(!now) {//空节点
		x=y=0;
		return ;
	}
	down(now);//有些题目需要用lazy标记下传,比如文艺平衡树中的区间反转操作
	if(tr[tr[now].l].siz+1 <= siz) {
		x=now;//左子树+自己的size小于等于siz,将左子树和自己加到树堆x上
		fl_siz(tr[now].r, siz-tr[tr[now].l].siz-1, tr[now].r, y);
        //接着搜右子树(注意!!!siz要减去左子树+当前节点的size!!!)
	}
	else {
		y=now;//不行的话就连同右子树加到树堆y上,看左子树能不能加到树堆x上
		fl_siz(tr[now].l, siz, x, tr[now].l);
	}
	upup(now);//更新节点信息
}

和按权值分裂的时间复杂度一样,\(O(log_2n)\)

合并

合并操作只有一种,但是要求合并的两个树堆 \(x\)\(y\),树堆 \(x\) 的所有点的权值都要严格小于树堆 \(y\) 的任意点,(但本蒟蒻还有幸遇见过答辩题要用 fhq treap 合并有交集的树堆的),而此处就要用到我们赋予的优先值啦,如图(可能看起来有点难懂):

FHQ Treap学习笔记

如图所示,合并时优先考虑优先值,并以优先的点为合并后的父节点,然后再将非优先点与优先点的儿子合并,这是一个递归的过程,要注意的是合并要从祖先开始,且一般合并是直接合并两个无交集的树,若是有交集那么这个合并就只能用添加点的做法(详见下文),此处直接上代码吧:

int merge(int x, int y) {//合并时需要已经满足x的所有点的权值小于y的所有点的权值
	down(x);//有需要的话标记下传
	down(y);
	if(x == 0 || y == 0) return x+y;//x、y有一个为空节点直接返回非空的节点
	if(tr[x].pri < tr[y].pri) {//维护堆的性质,用优先值确定父亲
		tr[x].r=merge(tr[x].r, y);//y应该在x的右子树,因此要合并x的右儿子和y
		upup(x);//更新节点信息
		return x;//返回当前根节点
	}
	else {
		tr[y].l=merge(x, tr[y].l);//x应该在y的左子树,因此要合并y的左儿子和x
		upup(y);//更新节点信息
		return y;//返回当前根节点
	}
}

合并的话递归层数取决于树堆深度,而深度期望为 \(log\ n\) 层,那么一次合并操作时间复杂度便为 \(O(log_2n)\)

添加点

既然我们已经会分裂、合并了,那么如果想要添加一个值为 \(X\) 的点,我们可以直接将原树堆按 \(key = X\),以权值大小分裂,然后新建点,将分裂出的树堆 \(x\) 和新建点合并后再和分裂出的树堆 \(y\) 合并,时间复杂度为 \(O(log_2n)\),如图:

FHQ Treap学习笔记

此处为什么不直接合并原树堆和新建点呢?那是因为我们写的 \(merge\) 合并函数要求合并的是无交集的两树堆,且树堆 \(x\) 的所有点的权值都要严格小于树堆 \(y\) 的任意点,所以我们必须要先分裂再合并以保证树堆的正确性,代码如下:

void insert(int key) {
	int x, y;
	fl_key(rt, key, x, y);
	rt=merge(merge(x, blt(key)), y);
}

删除点

删除一个值为 \(X\) 的点的话可以先按 \(key = X\),以权值大小分裂得到树堆 \(x\)\(y\),再按 \(key = X-1\),以权值大小分裂树堆 \(x\),得到树堆 \(z\)\(a\),然后将树堆 \(a\) 的根节点丢掉(即合并 \(a\) 的左右儿子,不合并 \(a\) 的根节点,这就相当于删了一个点),所有树堆依次合并,时间复杂度依然为 \(O(log_2n)\),如图:

FHQ Treap学习笔记

代码如下:

void dlt(int key) {
	int x, y, z;
	fl_key(rt, key, x, y);
	fl_key(x, key-1, z, x);
	rt=merge(merge(z, merge(tr[x].l, tr[x].r)), y);
}

查询排名

若是查询 \(X\) 的排名,直接按 \(key = X-1\),以权值大小分裂得到树堆 \(x\)\(y\),答案为树堆 \(x\)\(size+1\),时间复杂度为 \(O(log_2n)\),代码如下:(注意最后要合并回来!)

int ask(int key) {
	int x, y, ret=0;
	fl_key(rt, key-1, x, y);
	ret=tr[x].siz+1;
	rt=merge(x, y);
	return ret;
}

查询排名对应的值

查询排名 \(X\) 对应的值,有两种做法。

其一是上文提及的按子树大小分裂,按 \(siz = X-1\),以子树大小分裂得到树堆 \(x\)\(y\),然后再按 \(siz = 1\),以子树大小分裂树堆 \(y\),得到树堆 \(a\)\(z\)\(a\) 的根节点的值就是答案,时间复杂度为 \(O(log_2n)\),代码如下:(注意最后都要合并回来!)

int asks(int siz) {
	int x, y, z;
    fl_siz(rt, siz-1, x, y);
    fl_siz(y, 1, y, z);
    int ret = tr[y].key;
    rt = merge(merge(x, y), z);
    return ret;
}

其二是直接硬上,从根节点开始 dfs,如果左儿子+自己的 \(size\)\(X\) 小,那么 \(X\) 减去 \(size\),搜右儿子,否则就搜左儿子,\(X = 0\) 时就输出当前点的权值,时间复杂度依旧是 \(O(log_2n)\)(毕竟深度期望为 \(log\ n\)),代码如下:

int asks(int siz) {
	int o = rt;
	while(1) {
		int qwq = tr[tr[o].l].siz+1;
		if(qwq == siz) break;
		if(siz < qwq) o=tr[o].l;
		else o=tr[o].r, siz-=qwq;
	}
	return tr[o].key;
}

查询前驱、后继

查询 \(X\) 的前驱,先按 \(key = X-1\),以权值大小分裂得到树堆 \(x\)\(y\),然后从树堆 \(x\) 的根节点开始,右儿子非空就一直往右边走(因为树堆 \(x\) 的所有点已经满足了其点值小于等于 \(X-1\),只需找到里面最大的点值),最后输出即可。

查询 \(X\) 的后继,先按 \(key = X\),以权值大小分裂得到树堆 \(x\)\(y\),然后从树堆 \(y\) 的根节点开始,左儿子非空就一直往左边走(因为树堆 \(y\) 的所有点已经满足了其点值大于 \(X\),只需找到里面最小的点值),最后输出即可。

时间复杂度均为 \(O(log_2n)\),代码:(注意最后都要合并回来!)

int findl(int key) {
	int x, y, ret=0;
	fl_key(rt, key-1, x, y);
	ret=x;
	while(tr[ret].r != 0) ret=tr[ret].r;
	ret=tr[ret].key;
	rt=merge(x, y);
	return ret;
}
int findr(int key) {
	int x, y, ret=0;
	fl_key(rt, key, x, y);
	ret=y;
	while(tr[ret].l != 0) ret=tr[ret].l;
	ret=tr[ret].key;
	rt=merge(x, y);
	return ret;
}

普通平衡树——代码整合

总的时间复杂度大致为 \(O(n\times log_2n)\)\(n\) 表示操作数量。

#include <bits/stdc++.h>
#define uLL unsigned long long
using namespace std;
int n, opt, k, gs, rt;
uLL sd=1;
uLL rd() {
	return sd=sd*1145141ull*1145141ull;
}
struct node {
	int l, r, siz, key;
	uLL pri;
}tr[100005];
int blt(int key) {
	++gs;
	tr[gs].key=key;
	tr[gs].l=tr[gs].r=0; tr[gs].siz=1;
	tr[gs].pri=rd();
	return gs;
}
void upup(int now) {
	tr[now].siz=tr[tr[now].l].siz+tr[tr[now].r].siz+1;
}
void fl_key(int now, int key, int& x, int& y) {
	if(!now) {
		x=y=0;
		return ;
	}
	if(tr[now].key <= key) {
		x=now;
		fl_key(tr[now].r, key, tr[now].r, y);
	}
	else {
		y=now;
		fl_key(tr[now].l, key, x, tr[now].l);
	}
	upup(now);
}
int merge(int x, int y) {
	if(x == 0 || y == 0) return x+y;
	if(tr[x].pri < tr[y].pri) {
		tr[x].r=merge(tr[x].r, y);
		upup(x);
		return x;
	}
	else {
		tr[y].l=merge(x, tr[y].l);
		upup(y);
		return y;
	}
}
void insert(int key) {
	int x, y;
	fl_key(rt, key, x, y);
	rt=merge(merge(x, blt(key)), y);
}
void dlt(int key) {
	int x, y, z;
	fl_key(rt, key, x, y);
	fl_key(x, key-1, z, x);
	rt=merge(merge(z, merge(tr[x].l, tr[x].r)), y);
}
int ask(int key) {
	int x, y, ret=0;
	fl_key(rt, key-1, x, y);
	ret=tr[x].siz+1;
	rt=merge(x, y);
	return ret;
}
int asks(int siz) {
	int o = rt;
	while(1) {
		int qwq = tr[tr[o].l].siz+1;
		if(qwq == siz) break;
		if(siz < qwq) o=tr[o].l;
		else o=tr[o].r, siz-=qwq;
	}
	return tr[o].key;
}
int findl(int key) {
	int x, y, ret=0;
	fl_key(rt, key-1, x, y);
	ret=x;
	while(tr[ret].r != 0) ret=tr[ret].r;
	ret=tr[ret].key;
	rt=merge(x, y);
	return ret;
}
int findr(int key) {
	int x, y, ret=0;
	fl_key(rt, key, x, y);
	ret=y;
	while(tr[ret].l != 0) ret=tr[ret].l;
	ret=tr[ret].key;
	rt=merge(x, y);
	return ret;
}
int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i) {
		scanf("%d%d", &opt, &k);
		if(opt == 1) insert(k);
		else if(opt == 2) dlt(k);
		else if(opt == 3)
			printf("%d\n", ask(k));
		else if(opt == 4) 
			printf("%d\n", asks(k));
		else if(opt == 5)
			printf("%d\n", findl(k));
		else if(opt == 6)
			printf("%d\n", findr(k));
	}
	return 0;
}

文艺平衡树

建议做完普通平衡树后就直接上这道题,也许能让你对平衡树的理解加深哦 qwq

题目简述

初始有长度为 \(n\)\(1\)\(2\)\(3\)……\(n-1\)\(n\) 的原序列,进行 \(m\) 次翻转 \([l, r]\) 区间的数后,输出当前序列。

题解

既然题目有个平衡树,那我们直接 FHQ Treap 硬上吧!Wait Wait Wait,仔细想想,题目要求我们维护一个序列,那我们一定也要用 FHQ Treap 维护这个序列,但是他需要支持区间翻转操作,那么我们就需要将问题转化一下了 qaq。

对于每一次操作 \([l, r]\),我们先假设我们能用 FHQ Treap 分裂得到 \([l, r]\) 对应的树堆,接下来我们的问题便是区间翻转了。

假设有一个树:

FHQ Treap学习笔记

因为 Treap 是一棵二叉树,而原序列建出来的 Treap 能按中序遍历输出得到原序列,我们可以考虑最后用中序遍历输出答案。若是将图中的树中序遍历,输出为 \(c-a-d-u-b\),而整个区间翻转就相当于把中序遍历给翻转了,即 \(b-u-d-a-c\),我们再将新中序遍历结果对应的树画出来:

FHQ Treap学习笔记

可以发现,翻转中序遍历结果相当于将树上每个点的左右儿子互换……

OMG,思路不就这么出来了,先用 FHQ Treap 分裂得到 \([l, r]\) 对应的树堆,然后翻转每个点的左右儿子,最后再合并回去,这样就 OK 啦!因为直接做翻转每个点的左右儿子会 TLE 穿,所以我们可以存一个 \(lazy\) 标记,多次翻转的话就每次给标记异或上一个 \(1\),在分裂、合并时下传标记即可,能够保证不会将标记传到假儿子上,时间复杂度为 \(O(m\times log_2n)\)

那么我们现在就只需要找到一种分裂方法能够把 \([l, r]\) 区间对应的树堆分裂出来就行了,因为有交换左右儿子的操作,那么原 Treap 就不满足二叉搜索树的性质了,我们也因此不能按权值大小分裂,否则就算使其满足了性质,也会将标记下传到假儿子上去。

此时,“按子树大小分裂”的分裂方法就能起到很大的作用了!

有一个显然的性质:对于序列的一个区间内的所有数,在该区间对应的树堆中所对应的节点也是两两相连的。这个性质就能够保证我们按子树大小分裂的正确性啦,我们可以先按 \(siz = l-1\) 分裂得到树堆 \(x\)\(y\),然后我们再按 \(siz = r-l+1\) 分裂树堆 \(y\) 得到树堆 \(a\)\(z\),并给树堆 \(a\) 打上标记,最后再合并回去,而在分裂、合并时我们再进行标记下传即可。

最后我们按中序遍历输出就是答案惹!

总时间复杂度为 \(O(n \times log_2n)\) 左右,可以接受,代码如下:

#include <bits/stdc++.h>
#define uLL unsigned long long
using namespace std;
int n, m, l, r, gs, rt;
uLL sd=1;
uLL rd() {
	return sd=sd*1145141ull*1145141ull;
}
struct node {
	int l, r, siz, key, lazy;
	uLL pri;
}tr[100005];
int blt(int key) {
	++gs;
	tr[gs].key=key;
	tr[gs].lazy=0;
	tr[gs].l=tr[gs].r=0; tr[gs].siz=1;
	tr[gs].pri=rd();
	return gs;
}
void upup(int now) {
	tr[now].siz=tr[tr[now].l].siz+tr[tr[now].r].siz+1;
}
void fl_key(int now, int key, int& x, int& y) {
	if(!now) {
		x=y=0;
		return ;
	}
	if(tr[now].key <= key) {
		x=now;
		fl_key(tr[now].r, key, tr[now].r, y);
	}
	else {
		y=now;
		fl_key(tr[now].l, key, x, tr[now].l);
	}
	upup(now);
}
void down(int now) {//lazy标记下传
	if(now == 0 || tr[now].lazy == 0) return ;
	swap(tr[now].l, tr[now].r);
	tr[tr[now].l].lazy^=1;
	tr[tr[now].r].lazy^=1;
	tr[now].lazy=0;
}
void fl_siz(int now, int siz, int& x, int& y) {//按siz分裂
	if(!now) {
		x=y=0;
		return ;
	}
	down(now);
	if(tr[tr[now].l].siz+1 <= siz) {
		x=now;
		fl_siz(tr[now].r, siz-tr[tr[now].l].siz-1, tr[now].r, y);
	}
	else {
		y=now;
		fl_siz(tr[now].l, siz, x, tr[now].l);
	}
	upup(now);
}
int merge(int x, int y) {//此时的merge已经不要求x的所有点的权值小于y的所有点的权值了,因为有交换操作,这个Treap已经无法满足二叉搜索树的性质,但是本题也不用这个性质
	down(x);
	down(y);
	if(x == 0 || y == 0) return x+y;
	if(tr[x].pri < tr[y].pri) {//相当于只是建了个堆
		tr[x].r=merge(tr[x].r, y);
		upup(x);
		return x;
	}
	else {
		tr[y].l=merge(x, tr[y].l);
		upup(y);
		return y;
	}
}
void insert(int key) {//新加点,此时的Treap满足二叉搜索树的性质
	int x, y;
	fl_key(rt, key, x, y);
	rt=merge(merge(x, blt(key)), y);
}
void change() {//进行区间翻转操作
	int x, y, z;
	fl_siz(rt, l-1, x, y);
	fl_siz(y, r-l+1, z, y);
	tr[z].lazy^=1;//打上标记
	rt=merge(merge(x, z), y);//从左往右merge,保持原siz顺序
}
void dfs(int now) {//中序遍历输出
	down(now); 
	if(tr[now].l != 0)
		dfs(tr[now].l);
	printf("%d ", tr[now].key);
	if(tr[now].r != 0)
		dfs(tr[now].r);
}
int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; ++i)
		insert(i);
	for(int i = 1; i <= m; ++i) {
		scanf("%d%d", &l, &r);
		change();
	}
	dfs(rt);putchar('\n');
	return 0;
}

完结撒花~可持久化以后再来补吧文章来源地址https://www.toymoban.com/news/detail-746023.html

到了这里,关于FHQ Treap学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 08-静态pod(了解即可,不重要)

    我们都知道,pod是kubelet创建的,那么创建的流程是什么呐? 此时我们需要了解我们k8s中config.yaml配置文件了; 他的存放路径:【/var/lib/kubelet/config.yaml】 [root@k8s231 ~]# vim /var/lib/kubelet/config.yaml  ...... staticPodPath: /etc/kubernetes/manifests 我们会发现,这里面的名称都是我们的k8s集群

    2024年02月21日
    浏览(20)
  • 【ESP32Arduino+MPU6050姿态解算】自制无人机学习笔记2 PLatformIO 下载即可使用

    本人之前发表过一篇关于esp32读取mpu6050数据的文章,链接:http://t.csdn.cn/AwzSZ,但其存在一些漏洞,具体表现在输出数据存在不连贯和错误,在mpu6050高速运动时存在较大误差等。仅作为参考。故在此重发作为修正。当前该篇文章中所述的模块,已经过无人机稳定性控制的测试

    2024年02月16日
    浏览(26)
  • 爬虫(一) -- 带你了解爬虫最基本概念,一文即可实践

    定义:网络爬虫,是一种按照 一定规则 , 自动 爬取互联网信息的程序和脚本。用于 模拟 人操作浏览器打开网页,获取网页中的指定数据。 1.2 爬虫种类 爬虫的种类 作用 通用爬虫 爬取网页页面 全部的 源码数据 聚焦爬虫 爬取网页页面中的 局部 数据 增量式爬虫 用来检测

    2024年02月07日
    浏览(42)
  • 12-资源注解annotations和安全行下文securityContext(了解即可)

            资源注解,annotations就是对资源进行注释;         应用场景:         给资源(例如pod资源)提供配置信息,类似于帮助信息;         早期使用比较多,很多开源组件一般都会使用; [root@k8s231 annottations]# cat pod.yaml  apiVersion: v1 kind: Pod metadata:   name: pod-01  

    2024年02月20日
    浏览(27)
  • 了解SPI总线CAN控制器 MCP2515配置 一文即可

    最近工作中遇到需要6路CAN通信的情况,单片机自带的4路已不满足实际需求,故采用了SPI总线的CAN控制器芯片MCP2515,通过SPI通信的CAN扩展芯片最高可实现 1Mbps 的遵循 CAN 2.0B 的协议通信,配置起来也比较繁琐,故写诞生了这篇文章。本篇中仅对基础功能进行测试,如有疑问可

    2024年02月06日
    浏览(33)
  • 数据结构学习笔记——二叉排序树

    查找算法中,基于树这种数据结构进行查找即为树形查找,可将其分为 二叉排序树 、 平衡二叉树 和 B树 三种树形查找方法: 二叉排序树也称为二叉查找树或二叉搜索树(注意:与二分查找的判定树不同),其中各结点值的大小关系是: 左子树根结点右子树 ,且左、右子树

    2024年02月09日
    浏览(41)
  • 【Android】底层逻辑深入了解(学习笔记)(未完)

    step by step. 目录 init启动 Zygote进程:  SystemServer处理过程 Binder: Launcher启动过程 Android系统启动流程 四大组件 Activity Service  BroadcastReceiver广播 ContentProvider内容提供者(进程内和进程间的数据共享)  Context上下文  AMS(ActivityManagerService) (在图书馆看了《Android进阶解密》,

    2024年02月15日
    浏览(28)
  • 只需几步,即可享有笔记小程序

    本示例是一个简单的外卖查看店铺点菜的外卖微信小程序,小程序后端服务使用了MemFire Cloud,其中使用到的MemFire Cloud功能包括: 其中使用到的MemFire Cloud功能包括: 云数据库:存储外卖微信小程序所有数据表的信息。 即时API:创建数据表时会自动生成 API。 对象存储:存储

    2024年04月23日
    浏览(32)
  • 数据结构(c++语言版) 邓俊辉 第五章:二叉树学习笔记

    5.1二叉树及其表示         树是由节点和边组成的。 1.有根树         树是由顶点(vertex)和边(edge)组成。树的每个顶点也叫节点(node)。 2.深度与层次         由树的连通性,每一节点与根都有一条路径相连:根据树的无环性,由根通往每个节点的路径必然唯一。  

    2024年02月13日
    浏览(33)
  • 【UnityShader入门精要学习笔记】第二章(1)了解渲染流水线

    本系列为作者学习UnityShader入门精要而作的笔记,内容将包括: 书本中句子照抄 + 个人批注 项目源码 一堆新手会犯的错误 潜在的太监断更,有始无终 总之适用于同样开始学习Shader的同学们进行有取舍的参考。 什么是流水线?书中举了一个生产洋娃娃的例子。一个洋娃娃的

    2024年01月25日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包