【SinGuLaRiTy-1010】Copyrights (c) SinGuLaRiTy 2017. All Rights Reserved.

Some Method Are Reprinted From 杨思雨-《伸展树的基本操作与应用》

引言

二叉查找树(Binary Search Tree)能够支持多种动态集合操作。因此,在信息学竞赛中,二叉排序树起着非常重要的作用,它可以被用来表示有序集合、建立索引或优先队列等。作用于二叉查找树上的基本操作的时间是与树的高度成正比的。对一个含 n各节点的完全二叉树,这些操作的最坏情况运行时间为 O(log n)。但如果树是含n 个节点的线性链,则这些操作的最坏情况运行时间为 O(n)。而有些二叉查找树的变形,其基本操作在最坏情况下性能依然很好,比如红黑树、AVL 树等等。本文将要介绍的伸展树(Splay Tree),也是对二叉查找树的一种改进,虽然它并不能保证树一直是“平衡”的,但对于伸展树的一系列操作,我们可以证明其每一步操作的平摊复杂度都是 O(log n)。所以从某种意义上说,伸展树也是一种平衡的二叉查找树。而在各种树状数据结构中,伸展树的空间要求与编程复杂度也都是很优秀的。

伸展树的基本操作

伸展树是二叉查找树的一种改进,与二叉查找树一样,伸展树也具有有序性。即伸展树中的每一个节点 x 都满足:该节点左子树中的每一个元素都小于 x,而其右子树中的每一个元素都大于 x。与普通二叉查找树不同的是,伸展树可以自我调整,这就要依靠伸展操作 Splay(x,S)。

伸展操作 Splay(x,S)

情况一:节点 x 的父节点 y 是根节点。这时,如果 x 是 y 的左孩子,我们进行一次 Zig(右旋)操作;如果 x 是 y 的右孩子,则我们进行一次 Zag(左旋)操作。经过旋转,x 成为二叉查找树 S 的根节点,调整结束。如图 1 所示

图 1

情况二:节点 x 的父节点 y 不是根节点,y 的父节点为 z,且 x 与 y 同时是各自父节点的左孩子或者同时是各自父节点的右孩子。这时,我们进行一次Zig-Zig 操作或者 Zag-Zag 操作。如图 2 所示

图 2

情况三:节点 x 的父节点 y 不是根节点,y 的父节点为 z,x 与 y 中一个是其父节点的左孩子而另一个是其父节点的右孩子。这时,我们进行一次 Zig-Zag 操作或者 Zag-Zig 操作。如图 3 所示

图 3

如图 4 所示,执行 Splay(1,S),我们将元素 1 调整到了伸展树 S 的根部。再执行 Splay(2,S),如图 5 所示,我们从直观上可以看出在经过调整后,伸展树比原来“平衡”了许多。而伸展操作的过程并不复杂,只需要根据情况进行旋转就可以了,而三种旋转都是由基本得左旋和右旋组成的,实现较为简单。

图 4 Splay(1,S)

图 5 Splay(2,S)

伸展树的基本操作

利用 Splay 操作,我们可以在伸展树 S 上进行如下运算:

(1)查找操作

Find(x,S):判断元素 x 是否在伸展树 S 表示的有序集中。首先,与在二叉查找树中的查找操作一样,在伸展树中查找元素 x。如果 x在树中,则再执行 Splay(x,S)调整伸展树。

基本查找

int find(int now,int val){//pos
if(mx[son[now][]]>=val)return find(son[now][],val);
if(key[now]==val)return now;
if(mi[son[now][]]<=val)return find(son[now][],val);
return now;
}

另一个查找(查找标号)

int rank(int now,int val){
if(key[now]==val)return size[son[now][]]+;
else if(val<key[now])return rank(son[now][],val);
else if(val>key[now])return size[son[now][]]++rank(son[now][],val);
}

查找K小值的标号

int kth(int now,int th){
if(size[son[now][]]+==th)return now;
else if(size[son[now][]]+>th)return kth(son[now][],th);
else return kth(son[now][],th-size[son[now][]]-);
}

查找前驱

int pre(int val){
int tmp=kth(son[val][],size[son[val][]]);
splay(tmp,val);
return key[tmp];
}

查找后驱

int succ(int val){
int tmp=kth(son[val][],);
splay(tmp,val);
return key[tmp];
}

(2)插入操作

也与处理普通的二叉查找树一样,将 x 插入到伸展树 S 中的相应位置上,再执行 Splay(x,S)。

首先,找到一个合适的位置

int getpos(int now,int val){
if((key[now]<val)&&(size[son[now][]]))return getpos(son[now][],val);
else if((key[now]>val)&&(size[son[now][]]))return getpos(son[now][],val);
return now;
}

但是我们并不能保证这个点是大于还是小于插入节点

void insert(int root,int pos,int val){
int sroot=father[root];
splay(root=getpos(root,val),sroot);
if(key[root]<val){
succ(root);//十分重要
rotate(root=son[root][]);
}
int tmp=son[root][];key[pos]=val;
son[root][]=pos;father[pos]=root;
son[pos][]=tmp;father[tmp]=pos;
updata(pos);updata(root);
splay(pos,);
}

我们把刚好比插入点大的点作为根,在左子树插入。如果根小于插入点,则要将根的后继变成根(!!!注意不是根的右儿子!!!)

(3)删除操作

将元素 x 从伸展树 S 所表示的有序集中删除。首先,用在二叉查找树中查找元素的方法找到 x 的位置。如果 x 没有孩子或只有一个孩子,那么直接将 x 删去,并通过 Splay 操作,将 x 节点的父节点调整到伸展树的根节点处。否则,则向下查找 x 的后继 y,用 y 替代 x 的位置,最后执行 Splay(y,S),将 y 调整为伸展树的根。

void dele(int root,int val){
int sroot=father[root];
splay(root=find(root,val),sroot);
if(son[root][])splay(succ(root),root);
son[sroot][flag(root)]=son[root][];
father[son[root][]]=sroot;
son[son[root][]][]=son[root][];
father[son[root][]]=son[root][];
updata(son[root][]);
}

(4)Join(S1,S2):

将两个伸展树 S1 与 S2 合并成为一个伸展树。其中 S1 的所有元素都小于 S2 的所有元素。

首先,我们找到伸展树 S1 中最大的一个元素 x,再通过 Splay(x,S1)将 x 调整到伸展树 S1 的根。然后再将 S2 作为 x 节点的右子树。这样,就得到了新的伸展树 S。如图 6 所示

图 6

(个人觉得这个操作运用的比较少)

(5)Split(x,S):

以 x 为界,将伸展树 S 分离为两棵伸展树 S1 和 S2,其中 S1中所有元素都小于 x,S2 中的所有元素都大于 x。首先执行 Find(x,S),将元素 x 调整为伸展树的根节点,则 x 的左子树就是S1,而右子树为 S2。如图 7 所示

图 7

(6)Splay操作

splay是基本操作,rotate是splay的基本操作。splay(now,root)把now splay到root的下面。单旋和双旋就不说了。我们可以简化操作,如果是一字型(方向相同)则先旋father,否则先旋now。然后再旋now。

可将一字型与之字形的情况合并:

void splay(int now,int root){
while(father[now]!=root){
if(father[father[now]]!=root){
if(flag(now)==flag(father[now]))rotate(father[now]);
else rotate(now);
}
rotate(now);
}
}

rotate now是指将now绕father rotate。有两个方向,表示now是father的左儿子还是右儿子。

bool flag(int now){
return son[father[now]][]==now;
}

通过方向标记,我们可以将两个方向的rotate合成一个:

void rotate(int now){
int fa=father[now],fx=flag(now);
son[fa][fx]=son[now][!fx];
if(son[now][!fx])father[son[now][!fx]]=fa;
//连接father和son[now]
son[father[fa]][flag(fa)]=now;
father[now]=father[fa];
//连接grandpa和now
son[now][!fx]=fa;
father[fa]=now;
//连接now和father
updata(fa);updata(now);
}

别忘了rotate完成后要updata:

void updata(int pos){
size[pos]=size[son[pos][]]+size[son[pos][]]+;
}

这里还有另一个标准版本的Splay操作:

Splay:

void splay(int x,int goal)
{
for(int y;(y=fa[x])!=goal;rotate(x))
{
int z;
if((z=fa[y])!=goal)
{
if((ch[z][]==y)==(ch[y][]==x))
rotate(y);
else rotate(x);
}
}
if(goal==)root=x;
}

Rotate:

void rotate(int x)
{
if(x==||fa[x]==)return;
int y=fa[x],z=fa[y];
bool flag=(ch[y][]==x);
ch[y][flag]=ch[x][!flag];
if(ch[x][!flag])fa[ch[x][!flag]]=y;
ch[x][!flag]=y;
fa[y]=x;
fa[x]=z;
if(z)ch[z][ch[z][]==y]=x;
}

Insert:

int insert(int &r,int x,int f)
{
int temp1,temp2;
if(r==)
{
sale[++tot]=x;
r=tot;
fa[r]=f;
splay(r,);
return INF;
}
if(x==sale[r])
return ;
else if(x<sale[r])
{
temp1=sale[r]-x;
temp2=insert(ch[r][],x,r);
return min(temp1,temp2);
}
else
{temp1=x-sale[r];
temp2=insert(ch[r][],x,r);
return min(temp1,temp2);
}
}

时间复杂度

由以上这些操作的实现过程可以看出,它们的时间效率完全取决于 Splay 操作的时间复杂度。下面,我们就用会计方法来分析 Splay 操作的平摊复杂度。首先,我们定义一些符号:S(x)表示以节点 x 为根的子树。|S|表示伸展树 S的节点个数。令μ(S) = [ log|S| ],μ(x)=μ(S(x))。如图 8 所示

图 8

我们用 1 元钱表示单位代价(这里我们将对于某个点访问和旋转看作一个单位时间的代价)。定义伸展树不变量:在任意时刻,伸展树中的任意节点 x 都至少有μ(x)元的存款。

在 Splay 调整过程中,费用将会用在以下两个方面:

(1)为使用的时间付费。也就是每一次单位时间的操作,我们要支付 1 元钱。

(2)当伸展树的形状调整时,我们需要加入一些钱或者重新分配原来树中每个节点的存款,以保持不变量继续成立。

下面我们给出关于 Splay 操作花费的定理:

每一次Splay(x,S)操作中,调整树的结构与保持伸展树不变量的总花费不超过3u(S)+1.

证明:用μ(x)和μ’(x)分别表示在进行一次 Zig、Zig-Zig 或 Zig-Zag 操作前后节点 x 处的存款。

下面我们分三种情况分析旋转操作的花费:

情况一:如图 9 所示

图 9

我们进行 Zig 或者 Zag 操作时,为了保持伸展树不变量继续成立,我们需要花费:

μ’(x) +μ’(y) -μ(x) -μ(y) = μ’(y) -μ(x)

≤ μ’(x) -μ(x)

≤ 3(μ’(x) -μ(x))

=3(μ(S) -μ(x))

此外我们花费另外 1 元钱用来支付访问、旋转的基本操作。因此,一次 Zig 或 Zag 操作的花费至多为 3(μ(S) -μ(x))。

情况二:如图 10 所示

图 10

我们进行 Zig-Zig 操作时,为了保持伸展树不变量,我们需要花费:

μ’(x) +μ’(y) +μ’(z) -μ(x) -μ(y) -μ(z) = μ’(y) +μ’(z) -μ(x) -μ(y)

=(μ’(y) -μ(x)) + (μ’(z) -μ(y))

≤ (μ’(x) -μ(x)) + (μ’(x) -μ(x))

=2 (μ’(x) -μ(x))

与上种情况一样,我们也需要花费另外的 1 元钱来支付单位时间的操作。

当μ’(x) <μ(x) 时,显然 2 (μ’(x) -μ(x)) +1 ≤ 3 (μ’(x) -μ(x))。也就是进行Zig-Zig 操作的花费不超过 3 (μ’(x) -μ(x))。

当μ’(x) =μ(x) 时,我们可以证明μ’(x) +μ’(y) + μ’(z) <μ(x) +μ(y) +μ(z),也就是说我们不需要任何花费保持伸展树不变量,并且可以得到退回来的钱,用其中的 1 元支付访问、旋转等操作的费用。为了证明这一点,我们假设μ’(x) +μ’(y)+ μ’(z) >μ(x) +μ(y) +μ(z)。

联系图 9,我们有μ(x) =μ’(x) =μ(z)。那么,显然μ(x) =μ(y) =μ(z)。于是,可以得出μ(x) =μ’(z) =μ(z)。令 a = 1 + |A| + |B|,b = 1 + |C| + |D|,那么就有

[log a] = [log b] = [log (a+b+1)]  ①

我们不妨设 b≥a,则有

[log (a+b+1)] ≥ [log (2a)]

= 1+[log a]

> [log a]   ②

①与②矛盾,所以我们可以得到μ’(x) =μ(x) 时,Zig-Zig 操作不需要任何花费,显然也不超过 3 (μ’(x) -μ(x))。

情况三:与情况二类似,我们可以证明,每次 Zig-Zag 操作的花费也不超过3 (μ’(x) -μ(x))。

以上三种情况说明,Zig 操作花费最多为 3(μ(S)-μ(x))+1,Zig-Zig 或 Zig-Zag操作最多花费 3(μ’(x)-μ(x))。那么将旋转操作的花费依次累加,则一次 Splay(x,S)操作的费用就不会超过 3μ(S)+1。也就是说对于伸展树的各种以 Splay 操作为基础的基本操作的平摊复杂度,都是 O(log n)。所以说,伸展树是一种时间效率非常优秀的数据结构。

总结

由上面的分析介绍,我们可以发现伸展树有以下几个优点:

(1)时间复杂度低,伸展树的各种基本操作的平摊复杂度都是 O(log n)的。在树状数据结构中,无疑是非常优秀的。

(2)空间要求不高。与红黑树需要记录每个节点的颜色、AVL 树需要记录平衡因子不同,伸展树不需要记录任何信息以保持树的平衡。

(3)算法简单,编程容易。伸展树的基本操作都是以 Splay 操作为基础的,而Splay 操作中只需根据当前节点的位置进行旋转操作即可。虽然伸展树算法与 AVL 树在时间复杂度上相差不多,甚至有时候会比 AVL树慢一些,但伸展树的编程复杂度大大低于 AVL 树。在竞赛中,使用伸展树在编程和调试中都更有优势。

  顺序查找 线段树 AVL树 伸展树
时间复杂度 O(n^2) O(nlog a) O(nlog n) O(nlog n)
空间复杂度 O(n) O(a) O(n) O(n)
编程复杂度 简单 较简单 复杂 较简单

相关题目

都是一些模板题,适合练练手。

Play with Chain

Problem Description

YaoYao is fond of playing his chains. He has a chain containing n diamonds on it. Diamonds are numbered from 1 to n.
At first, the diamonds on the chain is a sequence: 1, 2, 3, …, n.
He will perform two types of operations:
CUT a b c: He will first cut down the chain from the ath diamond to the bth diamond. And then insert it after the cth diamond on the remaining chain.
For example, if n=8, the chain is: 1 2 3 4 5 6 7 8; We perform “CUT 3 5 4”, Then we first cut down 3 4 5, and the remaining chain would be: 1 2 6 7 8. Then we insert “3 4 5” into the chain before 5th diamond, the chain turns out to be: 1 2 6 7 3 4 5 8.
FLIP a b: We first cut down the chain from the ath diamond to the bth diamond. Then reverse the chain and put them back to the original position.
For example, if we perform “FLIP 2 6” on the chain: 1 2 6 7 3 4 5 8. The chain will turn out to be: 1 4 3 7 6 2 5 8
He wants to know what the chain looks like after perform m operations. Could you help him? 

Input

There will be multiple test cases in a test data. 
For each test case, the first line contains two numbers: n and m (1≤n, m≤3*100000), indicating the total number of diamonds on the chain and the number of operations respectively.
Then m lines follow, each line contains one operation. The command is like this:
CUT a b c // Means a CUT operation, 1 ≤ a ≤ b ≤ n, 0≤ c ≤ n-(b-a+1).
FLIP a b    // Means a FLIP operation, 1 ≤ a < b ≤ n.
The input ends up with two negative numbers, which should not be processed as a case.

Output

For each test case, you should print a line with n numbers. The ith number is the number of the ith diamond on the chain.

Sample Input

8 2
CUT 3 5 4
FLIP 2 6
-1 -1

Sample Output

1 4 3 7 6 2 5 8

STD Code

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=3e5+;
int data[N],num[N],t[N][],id,fa[N];
int flip[N],root,f;
void pushup(int x)
{
num[x]=num[t[x][]]+num[t[x][]]+;
}
void pushdown(int x)
{
if(flip[x])
{
flip[x]=;
flip[t[x][]]^=;
flip[t[x][]]^=;
swap(t[x][],t[x][]);
}
}
void Rotate(int x,int w)
{
int y=fa[x];
int z=fa[y];
pushdown(y);
t[y][!w]=t[x][w];
fa[t[x][w]]=y;
t[z][t[z][]==y]=x;
fa[x]=z;
t[x][w]=y;
fa[y]=x;
pushup(y);
}
void newnode(int &x,int y,int v)
{
x=++id;
t[x][]=t[x][]=;
fa[x]=y;
data[x]=v,flip[x]=;
num[x]=;
}
void build(int &x,int y,int l,int r)
{
if(l<=r)
{
int mid=(l+r)>>;
newnode(x,y,mid);
build(t[x][],x,l,mid-);
build(t[x][],x,mid+,r);
pushup(x);
}
}
void init(int n)
{
f=id=root=;
t[][]=t[][]=num[]=data[]=flip[]=fa[]=;
newnode(root,,-);
newnode(t[][],root,-);
build(t[t[][]][],t[][],,n);
pushup(t[][]);
pushup();
}
void Splay(int x,int y)
{
if(x!=y)
{
pushdown(x);
while(fa[x]!=y)
{
if(t[fa[x]][]==x)
Rotate(x,);
else
Rotate(x,);
}
pushup(x);
if(!y)
root=x;
}
}
int Kth(int k)
{
int x=root;
pushdown(root);
for(;num[t[x][]]+!=k;)
{
if(num[t[x][]]+>k)
x=t[x][];
else
k-=num[t[x][]]+,x=t[x][];
pushdown(x);
}
return x;
}
void Flip(int a,int b)
{
a=Kth(a);
b=Kth(b+);
Splay(a,);
Splay(b,a);
flip[t[b][]]^=;
}
void Cut(int a,int b,int c)
{
int tmp,d; a=Kth(a);b=Kth(b+);
Splay(a,);Splay(b,a);
tmp=t[b][];t[b][]=;
pushup(b);pushup(a); d=Kth(c+);c=Kth(c+);
Splay(c,);Splay(d,c);
t[d][]=tmp;fa[tmp]=d;
pushup(d);pushup(c);
}
void Inorder(int x)
{
if(x)
{
pushdown(x);
Inorder(t[x][]);
if(data[x]>)
{
if(f)
putchar(' ');
else
f=;
printf("%d",data[x]);
}
Inorder(t[x][]);
}
}
int main()
{
int n,m;
char op[];
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==-)
break;
init(n);
int a,b,c;
while(m--){
scanf("%s",op);
if(op[]=='F')
{
scanf("%d%d",&a,&b);
Flip(a,b);
}
else
{
scanf("%d%d%d",&a,&b,&c);
Cut(a,b,c);
}
}
Inorder(root);
puts("");
}
return ;
}

营业额统计

题目描述

Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。
Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况:
该天的最小波动值 = min {该天以前某一天的营业额 - 该天营业额}
当最小波动值越大时,就说明营业情况越不稳定。
而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序帮助Tiger来计算这一个值。
第一天的最小波动值为第一天的营业额。
天数小于100000.

输入

第一行为正整数 ,表示该公司从成立一直到现在的天数

接下来的n行每行有一个整数(一定有数据小于〇) ,表示第i天公司的营业额。

输出

输出文件仅有一个正整数,即每一天的最小波动值之和。答案保证在int范围内。

样例输入

6
5
1
2
5
4
6

样例输出

12

提示

结果说明:5+|1-5|+|2-1|+|5-5|+|4-5|+|6-5|=5+4+1+0+1+1=12

STD Code

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 100005
#define INF 10000000
int ch[MAXN][],fa[MAXN],sale[MAXN];
int ans,tot,root;
#define min(a,b) ((a)>(b)?(b):(a))
void rotato(int x)
{
if(x==||fa[x]==)return;
int y=fa[x],z=fa[y];
bool flag=(ch[y][]==x);
ch[y][flag]=ch[x][!flag];
if(ch[x][!flag])fa[ch[x][!flag]]=y;
ch[x][!flag]=y;
fa[y]=x;
fa[x]=z;
if(z)ch[z][ch[z][]==y]=x;
}
void splay(int x,int goal)
{
for(int y;(y=fa[x])!=goal;rotato(x))
{
int z;
if((z=fa[y])!=goal)
{
if((ch[z][]==y)==(ch[y][]==x))
rotato(y);
else rotato(x);
}
}
if(goal==)root=x;
}
int insert(int &r,int x,int f)
{
int temp1,temp2;
if(r==)
{
sale[++tot]=x;
r=tot;
fa[r]=f;
splay(r,);
return INF;
}
if(x==sale[r])
return ;
else if(x<sale[r])
{
temp1=sale[r]-x;
temp2=insert(ch[r][],x,r);
return min(temp1,temp2);
}
else
{temp1=x-sale[r];
temp2=insert(ch[r][],x,r);
return min(temp1,temp2);
}
}
int main()
{
int n,t;
scanf("%d",&n);
for(int i=;i<n;i++)
{
scanf("%d",&t);
if(i)ans+=insert(root,t,);
else {ans=t;
insert(root,t,);
}
}
printf("%d\n",ans);
}

Time: 2017-03-22

[SinGuLaRiTy] SplayTree 伸展树的更多相关文章

  1. SplayTree伸展树的非递归实现(自底向上)

    Splay Tree 是二叉查找树的一种,它与平衡二叉树.红黑树不同的是,Splay Tree从不强制地保持自身的平衡,每当查找到某个节点n的时候,在返回节点n的同时,Splay Tree会将节点n旋 ...

  2. 伸展树(一)之 图文解析 和 C语言的实现

    概要 本章介绍伸展树.它和"二叉查找树"和"AVL树"一样,都是特殊的二叉树.在了解了"二叉查找树"和"AVL树"之后, ...

  3. 伸展树--java

    文字转载自:http://www.cnblogs.com/vamei 代码转载自:http://www.blogjava.net/javacap/archive/2007/12/19/168627.h ...

  4. wikioi 1396 伸展树(两个模板)

    题目描写叙述 Description Tiger近期被公司升任为营业部经理.他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司的账本,账本上记录了公司成立以来 ...

  5. 伸展树(SplayTree)的实现

    优点:伸展树(splay tree)是一种能自我调整的二叉搜索树(BST).虽然某一次的访问操作所花费的时间比较长,但是平摊(amortized) 之后的访问操作(例如旋转)时间能达到O(logn)的 ...

  6. 伸展树(二)之 C++的实现

    概要 上一章介绍了伸展树的基本概念,并通过C语言实现了伸展树.本章是伸展树的C++实现,后续再给出Java版本.还是那句老话,它们的原理都一样,择其一了解即可. 目录1. 伸展树的介绍2. 伸展树的C ...

  7. 伸展树(三)之 Java的实现

    概要 前面分别通过C和C++实现了伸展树,本章给出伸展树的Java版本.基本算法和原理都与前两章一样.1. 伸展树的介绍2. 伸展树的Java实现(完整源码)3. 伸展树的Java测试程序 转载请注明 ...

  8. PHP算法 《树形结构》 之 伸展树(1) - 基本概念

    伸展树的介绍 1.出处:http://dongxicheng.org/structure/splay-tree/ A. 概述 二叉查找树(Binary Search Tree,也叫二叉排序树,即Bin ...

  9. [置顶] hdu 1890 伸展树区间翻转

    题意: 给你n个数,每次先输出第i大的数的位置(如果有多个,选下标小的那个),然后每次将第i个位置到第i大的数所在位置之间的数进行翻转. 思路:输入的数组可能有多个相同的值,我们可以进行两次排序把数组 ...

随机推荐

  1. js五种设计模式

    1.js工厂模式 var lev=function(){ return "嘿哈"; }; function Parent(){ var Child = new object(); ...

  2. UIView和layer的关系

    UIView和layer的关系 UIView是layer的代理 View的根layer ---也就是说view本身自带一个layer, 这个layer 我们叫它根layer 所有视图显示是因为继承UI ...

  3. gstunnel---一个网络安全管道

    项目简介: gstunnel 是 基于go 语言开发的一个安全网络管道,支持tcp协议. gstunnel分为client和server两部分. gstunnel 基于aes进行数据加密. 流程示意: ...

  4. iSCS协议介绍

    1.iSCSI 协议说明 一种在 TCP/IP上进行数据块传输的标准,由Cisco 和 IBM 两家发起,并且得到了各大存储厂商的大力支持.iSCSI 可以实现在 IP 网络上运行SCSI协议,使其能 ...

  5. C#自动弹出窗口并定时自动关闭

    最近做个小项目,用到一个小功能:后台线程定时查询数据库,不符合条件的记录弹出消息提醒(在窗口最前面),并且过几秒钟再自动关闭弹出的窗口. 所以从网上找来资料,如下: WinForm 下实现一个自动关闭 ...

  6. 成为一名合格的ERP实施顾问应该具备哪些修为

    要想成为一个合格ERP实施顾问,究竟需要点什么素质.请注意,这里的素质与技能是两码事,素质特别强调的是某种修养,技能可以速成,修养必须积累沉淀. 快速切入客户业务的能力 作为一个合格的ERP实施人员, ...

  7. 【Zookeeper】源码分析之服务器(四)

    一.前言 前面分析了LeaderZooKeeperServer,接着分析FollowerZooKeeperServer. 二.FollowerZooKeeperServer源码分析 2.1 类的继承关 ...

  8. jquery 的基础知识,以及和Javascript的区别

    想到之前所学的javascript 我们会想到这几个方面:找元素: 操作内容: 操作属性:操作样式:统一操作元素: jquery 也是从这几个方面来学习的. <head> <meta ...

  9. C#:求1到100的和

    using System;public class Program    {        public static void Main()            {                ...

  10. CoreGraphics--画线/圆/矩形

    - (void)drawRect:(CGRect)rect { // Drawing code NSLog(@"drawRect自动调用"); //画图步骤 //获取上下文(/画笔 ...