0x46 二叉查找树与平衡树初步

0x46 二叉查找树与平衡树初步

在二叉树中,有两组非常重要的条件,分别是两类数据结构的基础性质。其一是“堆性质”,我们曾在0x17节中提及。二叉堆以及高级数据结构中的所有可合并堆,都满足“堆性质”。其二就是本节即将探讨的“BST结构”,它是二叉查找树(Binary Search Tree)以及所有平衡树的基础。

1.BST

给定一棵二叉树,树上的每个节点都带有一个数值,称为节点的“关键码”。所谓“BST性质”是指,对于树上的任意一个节点:

1.该节点的关键码不小于它的左子树的任意节点的关键码;

2.该节点的关键码不大于它的右子树的任意节点的关键码。

满足上述性质的二叉树就是一颗“二叉查找树”(BST。显然,二叉查找树的中序遍历是一个关键码单调递增的节点序列。

BST的建立

为了避免越界,减少边界情况的特殊判断,我们一般在BST中额外插入一个关键码为正无穷(一个很大的整数)和一个关键码为负无穷的节点。仅由这两个节点构成的BST就是一棵初始的空BST。如图所示。

在这里插入图片描述

简便起见,在接下来操作中,我们假设BST不会含有关键码相同的节点

struct BST{int l,r;//左右子节点在数组中的下标int val;//节点关键码
}a[SIZE];//数组模拟链表
int tot,root,INF=1<<30;int New(int val)
{a[++tot].val=val;return tot;
}
void Build()
{New(-INF),New(INF);root=1,a[1].r=2;
}

BST的检索

BST中检索是否存在关键码为 v a l val val的节点。

设变量 p p p等于根节点 r o o t root root,执行以下过程:

1.若 p p p的关键码等于 v a l val val,则已经找到。

2.若 p p p的关键码大于 v a l val val

(1)若 p p p的左子节点为空,则说明不存在 v a l val val

(2)若 p p p的左子节点不为空,在 p p p的左子树中递归进行检索。

3.若 p p p的关键码小于 v a l val val

(1)若 p p p的右子节点为空,则说明不存在 v a l val val

(2)若 p p p的右子节点不为空,在 p p p的右子树中递归进行检索。

在这里插入图片描述

int Get(int p,int val)
{if(p==0) return 0; //检索失败if(val==a[p].val) return p; //检索成功return val<a[p].val?Get(a[p].l,val):Get(a[p].r,val);
}

BST的插入

BST中插入一个新的值 v a l val val(假设目前BST中不存在关键码为 v a l val val的节点)。

BST的检索过程类似。

在发现要走向的 p p p的子节点为空,说明 v a l val val不存在时,直接建立关键码为 v a l val val的新节点作为 p p p的子节点。

在这里插入图片描述

void Insert(int &p,int val)
{if(p==0){p=New(val); //注意p是引用,其父节点的l或r值会被同时更新return;}if(val==a[p].val) return; //原本BST中已经含有这个关键码if(val<a[p].val) Insert(a[p].l,val);else Insert(a[p].r,val);
}

BST求前驱/后继

以“后继”为例。 v a l val val的“后继”指的是在BST中关键码大于 v a l val val的前提下,关键码最小的节点。

初始化 a n s ans ans为具有正无穷关键码的那个节点的编号。然后,在BST中检索 v a l val val。在检索过程中,每经过一个节点,都检查该节点的关键码,判断能否更新所求的后继 a n s ans ans

检索完成后,有三种可能的结果:

1.没有找到 v a l val val

此时 v a l val val的后继就在已经经过的节点中, a n s ans ans即为所求。

2.找到了关键码为 v a l val val的节点 p p p,且 p p p没有右子树。

与上一种情况相同, a n s ans ans即为所求。

3.找到了关键码为 v a l val val的节点 p p p,且 p p p有右子树。

p p p的右子节点出发,一直向左走,就找到了 v a l val val的后继。

在这里插入图片描述

int GetNext(int val)
{int ans=2;// a[2].val=INFint p=root;while(p){if(val==a[p].val){if(a[p].r>0){p=a[p].r;//右子树一直往左走while(a[p].l>0) p=a[p].l;ans=p;}break;}//每经过一个节点,都尝试更新后继if(a[p].val>val&&a[p].val<a[ans].val) ans=p;p=val<a[p].val?a[p].l:a[p].r;}return ans;
}int GetPre(int val)
{int ans=1;// a[1]=-INFint p=root;while(p){if(val==a[p].val){if(a[p].l>0){p=a[p].l;//左子树一直往右走while(a[p].r>0) p=a[p].r;ans=p;}break;}//每经过一个节点,都尝试更新前驱if(a[p].val<val&&a[p].val>a[ans].val) ans=p;p=val<a[p].val?a[p].l:a[p].r;}return ans;
}

BST的节点删除

BST中删除关键码为 v a l val val的节点。

首先,在BST中检索 v a l val val,得到节点 p p p

p p p的子节点个数小于2,直接删除 p p p,并令 p p p的子节点代替 p p p的位置,与 p p p的父节点相连。

p p p既有左子树又有右子树,则在BST中求出 v a l val val的后继节点 n e x t next next因为 n e x t next next没有左子树(若 n e x t next next有左子树,则说明 n e x t next next v a l val val之间还有其他值, n e x t next next不是 v a l val val的后继),所以可以直接删除 n e x t next next,并令 n e x t next next的右子树代替 n e x t next next的位置。最后,再让 n e x t next next节点代替 p p p节点,删除 p p p即可。如图所示。

在这里插入图片描述

void Remove(int &p,int val)
{//从子树p中删除值为val的节点if(p==0) return;if(val==a[p].val) //已经检索到值为val的节点{if(a[p].l==0) p=a[p].r; //右子树代替p的位置,注意p是引用else if(a[p].r==0) p=a[p].l; //左子树代替p的位置,注意p是引用else{int next=a[p].r;while(a[next].l>0) next=a[next].l;//next一定没有左子树,直接删除Remove(a[p].r,a[next].val); //这里必须填a[p].r,这样引用会修改右子树的值//令节点next代替节点p的位置a[next].l=a[p].l,a[next].r=a[p].r;p=next; //注意p是引用,root值也会随之修改}return;}if(val<a[p].val) Remove(a[p].l,val);else Remove(a[p].r,val);
}

在随机数据中,BST一次操作的期望复杂度为 O ( l o g N ) O(logN) O(logN)。然而,BST很容易退化,例如在BST中一次插入一个有序序列,将会得到一条链,平均每次操作的复杂度为 O ( N ) O(N) O(N)。我们称这种左右子树大小相差很大的BST是“不平衡”的。有很多种方法可以维持BST的平衡,从而产生了各种平衡树。

常见的平衡二叉树有TreapSplay、红黑树、AVLSBT、替罪羊树等。其中C++STL中的mapset等就采用了效率很高的红黑树的一种变体。不过,大多数平衡树因为实现比较复杂,或者应用范围能被其他平衡树替代,在算法竞赛等短时间程序设计中并不常用。

2.Treap

满足BST性质且中序遍历为相同序列的二叉查找树是不唯一的。这些二叉查找树是等价的,它们维护的是相同的一组数值。在这些二叉查找树上执行同样的操作,将得到相同的结果。因此,我们可以在维持BST性质的基础上,通过改变二叉查找树的形态,使得树上每个节点的左右子树大小达到平衡,从而使整棵树的深度保持在 O ( l o g N ) O(logN) O(logN)级别。

改变形态并保持BST性质的方法就是“旋转”。最基本的旋转操作称为“单旋转”,它又分为“左旋”和“右旋”。如下图所示。

在这里插入图片描述

注意:有的时候把左、右旋操作定义为一个节点绕其父节点向左或右旋转。我们这里讲解的Treap代码仅记录左右子节点,没有记录父节点,为了方便起见,统一以“旋转前处于父节点位置”(旋转后处于子节点位置)的节点作为左、右旋的作用对象(函数参数)。

以右旋为例。在初始情况下, x x x y y y的左子节点, A A A B B B分别是 x x x的左右子树, C C C y y y的右子树。

“右旋”操作在保持BST性质的基础上,把 x x x变为 y y y的父节点。因为 x x x的关键码小于 y y y的关键码,所以 y y y应该作为 x x x的右子节点。

x x x变成 y y y的父节点后, y y y的左子树就空了出来,于是 x x x原来的右子树 B B B就恰好作为 y y y的左子树。

右旋操作代码如下, z i g ( p ) zig(p) zig(p)可以理解成把 p p p的左子节点绕着 p p p向右旋转

void zig(int &p)
{int q=a[p].l;a[p].l=a[q].r,a[q].r=p;p=q;
}

左旋操作代码如下, z a g ( p ) zag(p) zag(p)可以理解成把 p p p的右子节点绕着 p p p向左旋转

void zag(int &p)
{int q=a[p].r;a[p].r=a[q].l,a[q].l=p;p=q;
}

合理的旋转可使BST变得更“平衡”。如下图所示,对形态为一条链的BST进行一系列单旋操作后,这棵BST变得比较平衡了。

在这里插入图片描述

现在,我们的问题是,怎样才算“合理”的旋转操作呢?我们发现,在随机数据下,普通的BST就是趋于平衡的。Treap的思想就是利用“随机”来创造平衡条件。因为在旋转过程中必须维持BST性质,所以Treap就把“随机”作用在堆性质上

Treap是英文TreeHeap的合成词。Treap在插入每个新节点时,给该点随机生成一个额外的权值。然后像二叉堆的插入过程一样,自底向上依次检查,当某个节点不满足大根堆的性质时,就执行单旋转,使其父节点的关系发生对换。

特别地,对于删除操作,因为Treap支持旋转,我们可以直接找到需要删除的节点,并把它向下旋转成叶节点,最后直接删除。这样就避免了采用类似普通BST的删除方法可能导致的节点信息更新、堆性质维护等复杂问题。

总而言之,Treap通过适当的单旋转,在维持节点关键码满足BST性质的同时,还使得每个节点上随机生成的额外权值满足大根堆性质。Treap是一种平衡二叉查找树,检索、插入、求前驱后继以及删除节点的时间复杂度都是 O ( l o g N ) O(logN) O(logN)

您需要写一种数据结构,来维护一些数,其中需要提供以下操作:

  1. 插入数值 x x x
  2. 删除数值 x x x(若有多个相同的数,应只删除一个)。
  3. 查询数值 x x x 的排名(若有多个相同的数,应输出最小的排名)。
  4. 查询排名为 x x x 的数值。
  5. 求数值 x x x 的前驱(前驱定义为小于 x x x 的最大的数)。
  6. 求数值 x x x 的后继(后继定义为大于 x x x 的最小的数)。

这是一道平衡树的模板题,我们直接用Treap实现即可。

根据题意,数据中可能有相同的数值,我们可以给每个节点增加一个域 c n t cnt cnt,记录该节点的“副本数”,初始为1。若插入已经存在的数值,就直接把“副本数”加1。这样可以比较容易地处理关键码相同的问题。

题目还要求查询排名,我们可以给每个节点增加一个域 s i z e size size,记录以该节点为根的子树中所有节点的“副本数”之和。当不存在重复数值时, s i z e size size其实就是子树大小。

与线段树一样。我们需要在插入或删除时从下往上更新 s i z e size size信息。另外,在发生旋转操作时,也需要同时修改 s i z e size size。最后在BST检索的基础上,通过判断左右子树 s i z e size size的大小,选择适当的一侧递归,就很容易查询排名了。

因为在插入和删除操作时,Treap的形态会发生变化,所以我们一般使用递归实现,以便于在回溯时更新Treap上存储的 s i z e size size等信息。

#include <bits/stdc++.h>
#include <ctime>
#include <stdlib.h>
using namespace std;const int SIZE=1e5+5;
struct Treap{int l,r;int val,dat;//节点关键码、权值int cnt,size;//副本数、子树大小
}a[SIZE];
int tot,root,n,INF=0x7fffffff;int New(int val)
{a[++tot].val=val;a[tot].dat=rand();a[tot].cnt=a[tot].size=1;return tot;
}void Update(int p)
{a[p].size=a[a[p].l].size+a[a[p].r].size+a[p].cnt;
}void Build()
{New(-INF),New(INF);root=1,a[1].r=2;Update(root);
}int GetRankByVal(int p,int val)
{if(p==0) return 0;if(val==a[p].val) return a[a[p].l].size+1;if(val<a[p].val) return GetRankByVal(a[p].l,val);return GetRankByVal(a[p].r,val)+a[a[p].l].size+a[p].cnt;
}int GetValByRank(int p,int rank)
{if(p==0) return INF;if(a[a[p].l].size>=rank) return GetValByRank(a[p].l,rank);if(a[a[p].l].size+a[p].cnt>=rank) return a[p].val;return GetValByRank(a[p].r,rank-a[a[p].l].size-a[p].cnt); 
}void zig(int &p)
{int q=a[p].l;a[p].l=a[q].r,a[q].r=p,p=q;Update(a[p].r),Update(p);
}void zag(int &p)
{int q=a[p].r;a[p].r=a[q].l,a[q].l=p,p=q;Update(a[p].l),Update(p);
}void Insert(int &p,int val)
{if(p==0){p=New(val);return;}if(val==a[p].val){a[p].cnt++,Update(p);return;}if(val<a[p].val){Insert(a[p].l,val);if(a[p].dat<a[a[p].l].dat) zig(p); //不满足堆性质,右旋}else{Insert(a[p].r,val);if(a[p].dat<a[a[p].r].dat) zag(p); //不满足堆性质,左旋}Update(p);
}int GetPre(int val)
{int ans=1;int p=root;while(p){if(val==a[p].val){if(a[p].l>0){p=a[p].l;while(a[p].r>0) p=a[p].r;ans=p;}break;}if(a[p].val<val&&a[p].val>a[ans].val) ans=p;p=val<a[p].val?a[p].l:a[p].r;}return a[ans].val;
}int GetNext(int val)
{int ans=2;int p=root;while(p){if(val==a[p].val){if(a[p].r>0){p=a[p].r;while(a[p].l>0) p=a[p].l;ans=p;}break;}if(a[p].val>val&&a[p].val<a[ans].val) ans=p;p=val<a[p].val?a[p].l:a[p].r;}return a[ans].val;
}void Remove(int &p,int val)
{if(p==0) return;if(val==a[p].val){if(a[p].cnt>1){a[p].cnt--,Update(p);return;}if(a[p].l||a[p].r){if(a[p].r==0||a[a[p].l].dat>a[a[p].r].dat)zig(p),Remove(a[p].r,val);elsezag(p),Remove(a[p].l,val);Update(p);}else p=0;return;}val<a[p].val?Remove(a[p].l,val):Remove(a[p].r,val);Update(p);
}int main()
{srand((unsigned)time(0));scanf("%d",&n);Build();while(n--){int opt,x;scanf("%d%d",&opt,&x);if(opt==1) Insert(root,x);else if(opt==2) Remove(root,x);else if(opt==3) printf("%d\n",GetRankByVal(root,x)-1); //起始多一个负无穷else if(opt==4) printf("%d\n",GetValByRank(root,x+1));else if(opt==5) printf("%d\n",GetPre(x));else if(opt==6) printf("%d\n",GetNext(x));}return 0;
}

3.Splay

Splay(伸展树)灵活多变,应用广泛,能够很方便地支持各种动态的区间操作,是用于解决复杂问题的一个重要的高级数据结构。

Splay的核心操作是 s p l a y splay splay(伸展)。一次 s p l a y splay splay操作,其实就是两次旋转,称之为双旋。但这两次旋转在不同情况下,顺序、种类都是不同的。在 Splay 所规定的双旋操作下,可以尽可能维持树的平衡。Splay 通过把每次操作的点,按照它所规定的旋转方式旋转到根节点,以维持平衡。

s p l a y splay splay操作规定:每访问一个节点 x x x后都要强制将其旋转到根节点。定义 p p p x x x的父节点。 s p l a y splay splay操作步骤有三种,具体分为六种情况:

1.zig:在 p p p是根节点时操作。Splay树会根据 x x x p p p间的边旋转。这时只需要旋转一次就可以将 x x x旋转到根节点。

在这里插入图片描述

在这里插入图片描述

2.zig-zig:在 p p p不是根节点且 x x x p p p都是右侧子节点或都是左侧子节点时操作。下方例图显示了 x x x p p p都是左侧子节点时的情况。Splay树首先按照连接 p p p与其父节点 g g g边旋转,然后按照连接 x x x p p p的边旋转。

在这里插入图片描述

即首先将 g g g左旋或右旋,然后将 x x x右旋或左旋。

在这里插入图片描述

3.zig-zag:在 p p p不是根节点且 x x x p p p一个是右侧子节点一个是左侧子节点时操作。Splay树首先按 p p p x x x之间的边旋转,然后按 x x x g g g新生成的结果边旋转。

在这里插入图片描述

即将 x x x先左旋再右旋、或先右旋再左旋。

在这里插入图片描述

具体而言,对于三个节点,其排列方式可能是共线也可能非共线。对于共线的,我们要先将父亲向上旋转,再将要旋转的节点向上旋转。对于非共线的,我们直接把要旋转的节点向上旋转两次即可。

特别地,当Splay执行删除操作时:

1.首先把要删除的点伸展到根节点。

2.如果其个数标记大于1,对个数进行修改即可。

3.个数标记等于1。

(1)如果没有子节点直接把这个点删除,但是,理论上,如果我们进行了类似于 Treap 中的 b u i l d build build 操作,插入了两个无穷节点,这个步骤可以省略。

(2)如果没有左子树或者右子树,直接让唯一的节点成为根节点。否则,即左右子节点都存在,那么可以找到它的前驱,把前驱 s p l a y splay splay 到根节点,随后修改相关指针。 值得注意的是,前驱 s p l a y splay splay到根节点后,其右儿子必定为我们要删除的节点,并且我们要删除的节点必定没有左儿子。这一性质有助于简化代码。

此外,如果我们先写好了前驱函数 G e t P r e GetPre GetPre,其结尾必定会把找到的节点 s p l a y splay splay 到根节点。那么,我们在删除操作的时候直接调用 G e t P r e GetPre GetPre 函数就可以直接把后继 s p l a y splay splay 到根节点了。这也有助于简化代码。

其他操作没有什么特别之处,需要牢记的是,所有操作结束后,均需把操作的点 s p l a y splay splay 到根节点。

#include <bits/stdc++.h>
using namespace std;const int SIZE=1e5+5,INF=0x7fffffff;
int n;
struct Splay{int rt,tot;int f[SIZE],ch[SIZE][2]; //父节点,左右子节点int val[SIZE],cnt[SIZE],size[SIZE];int New(int v){val[++tot]=v;cnt[tot]=size[tot]=1;return tot;}void Update(int p){size[p]=size[ch[p][0]]+size[ch[p][1]]+cnt[p];}bool get(int p){return p==ch[f[p]][1]; //判断是左儿子还是右儿子}void Build(){New(-INF),New(INF);rt=1,ch[rt][1]=2,f[2]=rt;Update(rt);}void rot(int p) //不同于Treap中的向下旋转,这里是向上旋转{int x=f[p],y=f[x],u=get(p),v=get(x);f[ch[p][u^1]]=x,ch[x][u]=ch[p][u^1];f[x]=p,ch[p][u^1]=x;Update(x),Update(p);f[p]=y;if(y) ch[y][v]=p;}void splay(int p) {while(f[p]){int x=f[p],y=f[x];if(y) rot(get(p)==get(x)?x:p);rot(p);}rt=p;}void Insert(int v){int x=rt,y=0;while(1){if(val[x]==v){cnt[x]++,size[x]++;Update(y),splay(x);break;}y=x,x=ch[y][val[y]<v];if(x==0){New(v);f[tot]=y,ch[y][val[y]<v]=tot,Update(y);splay(tot);break;}}}int GetValByRank(int rank){int p=rt;while(1){if(rank<=size[ch[p][0]]) p=ch[p][0];else if(rank<=size[ch[p][0]]+cnt[p]) break;else rank-=size[ch[p][0]]+cnt[p],p=ch[p][1];}splay(p);return val[p];}int GetRankByVal(int v){int p=rt,res=0;while(1){if(v<val[p]){if(ch[p][0]==0){res++;break;}p=ch[p][0];}else if(v==val[p]){res+=size[ch[p][0]]+1;break;}else{res+=size[ch[p][0]]+cnt[p];if(ch[p][1]==0){res++;break;}p=ch[p][1];}}splay(p);return res;}int GetPre(int v){return GetValByRank(GetRankByVal(v)-1);}int GetNext(int v){return GetValByRank(GetRankByVal(v+1));}void Remove(int v){GetRankByVal(v); //把第一个大于等于v的数splay到根节点if(v!=val[rt]) return;if(cnt[rt]>1){cnt[rt]--,size[rt]--;return;}if(ch[rt][0]==0||ch[rt][1]==0){rt=ch[rt][0]+ch[rt][1];f[rt]=0;return;}int p=rt;GetPre(v);//v的前驱到了rt节点,此时要删除的p必定是现在的根的右儿子,且必定没有左儿子f[ch[p][1]]=rt,ch[rt][1]=ch[p][1];size[rt]--;return;} 
}tree;int main()
{scanf("%d",&n);tree.Build();while(n--){int opt,x;scanf("%d%d",&opt,&x);if(opt==1) tree.Insert(x);else if(opt==2) tree.Remove(x);else if(opt==3) printf("%d\n",tree.GetRankByVal(x)-1);else if(opt==4) printf("%d\n",tree.GetValByRank(x+1));else if(opt==5) printf("%d\n",tree.GetPre(x));else if(opt==6) printf("%d\n",tree.GetNext(x));}return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++杂货铺】C++11新特性——lambda

文章目录 一、C98中的排序二、先来看看 lambda 表达式长什么样三、lambda表达式语法3.1 捕捉列表的使用细节 四、lambda 的底层原理五、结语 一、C98中的排序 在 C98 中&#xff0c;如果要对一个数据集合中的元素进行排序&#xff0c;可以使用 std::sort 方法&#xff0c;下面…

计算机网络复习6

应用层 文章目录 应用层网络应用模型域名系统DNS文件传输协议FTP电子邮件万维网 网络应用模型 客户/服务器模型 客户/服务器&#xff08;Client/Server&#xff0c;C/S)模型中&#xff0c;有一个总是打开的主机称为服务器&#xff0c;它服务于许多来自其他称为客户机的主机请求…

记二开金蝶云星空动态表单艰辛历程

需求描述 需要在云星空系统的的其他出库中选择且业务系统的数据&#xff0c;并且填入选择的请示单单号。以下是实现的效果图。 首先要解决点击文本弹出单据 因为没有实现过这样的功能&#xff0c;于是提了工单。经过漫长的等待&#xff0c;终于金蝶的研发老师打联系过来了&a…

QML 怎么调用 C++ 中的内容?

以下内容为本人的学习笔记&#xff0c;如需要转载&#xff0c;请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/z_JlmNe6cYldNf11Oad_JQ 先说明一下测试环境 编译器&#xff1a;vs2017x64 开发环境&#xff1a;Qt5.12 这里主要是总结一下&#xff0c;怎么在…

Sensor Demosaic IP 手册PG286笔记

《 UG1449 Multimedia User Guide》中包含了大量的多媒体IP简介。 本IP 用于对bayer RGB&#xff08;每个pixel只有单个R/G/B&#xff09;做去马赛克处理&#xff0c;恢复成每个pixel点都有完整的RGB值。通过axi接口配置IP内部erg。 1、算法手册中的描述 提到了几种插值算法&…

GBASE南大通用-GBase 8s分片表操作 提升大数据处理性能

目录 一、GBase 8s分片表的优势 二、六种分片方法 轮转 1.轮转法 基于表达式分片 2.基本表达式 3.Mod运算表达式 4.Remainder关键字方式 5.List方式 6.interval 固定间隔 三、分片表的索引 1.创建索引的注意事项 2.detach索引替代delete功能展现 3.在现有分片表上增加一个新…

新火种AI|AI正在让汽车成为“消费电子产品”

作者&#xff1a;一号 编辑&#xff1a;小迪 AI正在让汽车产品消费电子化 12月28日&#xff0c;铺垫许久的小米汽车首款产品——小米SU7正式在北京亮相。命里注定要造“电车”的雷军&#xff0c;在台上重磅发布了小米的五大自研核心技术。在车型设计、新能源技术以及智能科技…

力扣题目学习笔记(OC + Swift)206. 反转链表

206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 方法一、迭代 在遍历链表时&#xff0c;将当前节点的 next\textit{next}next 指针改为指向前一个节点。由于节点没有引用其前一个节点&#xff0c;因此必须事先存储其…

腾讯云4核8G服务器选择轻量还是标准型S5服务器?

腾讯云4核8G服务器优惠价格表&#xff0c;云服务器CVM标准型S5实例4核8G配置价格15个月1437.3元&#xff0c;5年6490.44元&#xff0c;轻量应用服务器4核8G12M带宽一年446元、529元15个月&#xff0c;阿腾云atengyun.com分享腾讯云4核8G服务器详细配置、优惠价格及限制条件&…

模式识别与机器学习-判别式分类器

模式识别与机器学习-判别式分类器 生成式模型和判别式模型的区别线性判别函数多分类情况多分类情况1多分类情况2多分类情况3 例题 广义线性判别函数实例 分段线性判别函数Fisher线性判别感知机算法例&#xff1a;感知机多类别分类 谨以此博客作为学习期间的记录 生成式模型和判…

【嵌入式开发学习必备专栏】

文章目录 嵌入式开发学习必备专栏1.1 ARM Coresight SoC-400/SoC-600 专栏导读目录1.1.1 Performance Profiling1.1.2 ARM Coresight Debug 工具系列1.1.2.1 ARM DS5 系列1.1.2.2 劳特巴赫 Trace32 系列1.1.2.3 JTAG OpenOCD 系列 1.2 ARM Cache 专栏1.3 ARM AMBA Bus 专栏1.3.…

【Linux操作系统】探秘Linux奥秘:操作系统的入门与实战

&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《操作系统实验室》&#x1f516;诗赋清音&#xff1a;柳垂轻絮拂人衣&#xff0c;心随风舞梦飞。 山川湖海皆可涉&#xff0c;勇者征途逐星辉。 目录 &#x1fa90;1 初识Linux OS …