NOI級別的超強資料結構——Link-cut-tree(動態樹)學習小記
前言
其實LCT這種東西,我去年就接觸過並且打過,只不過一直沒調出來。最近優化了我那又醜又長的splay打法,並且用LCT切了道題。在此做一個小結。
簡介
如果有一道題,讓我們維護一棵樹,支援以下操作:
1.鏈上求和;
2.鏈上求最值;
3.鏈上修改;
4.子樹修改;
5.子樹求和;
這道題用樹鏈剖分就可以切掉了。
但如果這題是讓我們支援以下操作:
1.鏈上求和;
2.鏈上求最值;
3.鏈上修改;
4.子樹修改;
5.子樹求和;
6.換根;
7.斷開樹上一條邊;
8.連線兩個點,保證連線後仍然是一棵樹。
多了這三個操作的話,樹鏈剖分就捉襟見肘了。因為我們知道,樹鏈剖分是通過線段樹維護鏈資訊的,而線段樹是靜態的,不能加/減邊。
這時,LCT應運而生。
LCT,也就是link cut tree的縮寫。它是最常見的一種解決動態樹問題的工具。顧名思義,動態樹就是會動的樹,也即會加/減邊的樹。不過說它是樹也不準確,因為它可以是一片森林。
思想
樹鏈剖分有重鏈和輕邊。我們的LCT也一樣,分實(重)邊和虛(輕)邊。我們知道,一個節點最多連出一條向兒子的實邊,因此實邊會聚整合鏈。根據樹鏈剖分的思想,我們需要用一種資料結構來維護實邊組成的鏈。樹鏈剖分使用了線段樹來維護,但線段樹顯然很靜態。
我們思考可以使用能動態的平衡樹——splay!
至於為什麼不用treap,據說是因為LCT的時間複雜度需要勢能分析。(我不會告訴你們我不會treap
概念
Preferred Child:偏愛兒子,偏愛兒子與父親節點同在一棵Splay中,一個節點最多隻能有一個偏愛兒子(注意,LCT的偏愛兒子與樹鏈剖分的重兒子迥乎不同,後者是點數最大的兒子,而前者則是隨便的);
Preferred Edge:實邊,連線父親節點和偏愛兒子的邊;
Preferred Path:偏愛路徑,由實邊及實邊連線的節點構成的鏈;
Auxiliary Tree:輔助樹,由一條偏愛路徑上的所有節點所構成的Splay稱作這條鏈的輔助樹。每個點的鍵值為這個點的深度,即這棵Splay的中序遍歷是這條鏈從鏈頂到鏈底的所有節點構成的序列。輔助樹的根節點的父親指向鏈頂的父親節點,然而鏈頂的父親節點的兒子並不指向輔助樹的根節點。
注意:實邊連起來會組成偏愛路徑,偏愛路徑之間沒有公共點。
樹鏈剖分的重鏈是固定的,但是lct的偏愛路徑是可以改變(動態)的。
若一個不在偏愛路徑上的點也視為一條沒有實邊的偏愛路徑,那麼偏愛路徑之間是用虛邊連線的。
如圖,加粗的是重邊,1->5是一條重鏈,3->7是一條重鏈。
基礎操作:so、link、if_root
so(x)是查詢x為其父親節點的左兒子還是右兒子;link(y,x,d)表示從y向x連一條實邊,其中x會變為y的d兒子(注意,此處的link並不是簡介中的操作8,純粹只是連實邊);if_root(x)是判斷x是否為其splay上的根。
bool so(int x)
{
return son[fat[x]][1]==x;
}
void link(int f,int x,bool d)
{
son[fat[x]=f][d]=x;
}
bool if_root(int x)
{
return !fat[x]||son[fat[x]][so(x)]!=x;
}
核心操作:access
什麼是access?英文好一點可以讀懂是“訪問”。
access(x)其實就是訪問某個節點,似乎沒有太特殊的意義。
至於這個操作為什麼要命名為access,我也不知道。
access(x)的真正含義:讓x節點不含偏愛兒子,同時x到根節點所有邊均為實邊。
演算法的流程如下:
因為x節點不能含偏愛兒子,先將x旋至其所在splay的根,然後斷開右子樹(變為虛邊)。
接著我們順著偏愛路徑往上爬,每遇到一條虛邊,我們同樣把虛邊連向的節點y旋至y所在splay的根然後斷開y的右子樹(使y不含有偏愛兒子),並把x所在splay接在y的右子樹(把虛邊改為實邊)。
這就完成了access。
void access(int y)
{
int x=0;
while(y)//y不為整棵LCT的根
{
splay(y);//將y旋至其所在splay的根
link(y,x,1);//把x所在splay接在y的右子樹,這樣同時也會沖掉y原來的右子樹
x=y;
y=fat[y];
}
}
重要操作:makeroot
makeroot(x)即為將x變為整棵LCT的根。
演算法流程如下:對x進行access,然後觀察,我們發現虛邊子樹會隨著依附子樹一起選擇;而x到根的路徑則會在同一棵splay裡,且x是深度最大的點。
而換根之後改變了什麼?x到目前根節點路徑上這條偏愛路徑被反了過來!
那我們只需要打一個翻轉標記即可。
來自某Chair大佬的友情提醒:“注意打標記在點x時,x的左右兒子已經交換了,不然在一些極複雜的題可能會GG。”
容易看出,makeroot操作的複雜度與access一致。
void makeroot(int x)
{
access(x);
splay(x);
fan(x);
}
操作7和操作8:link和cut
有了access和makeroot,link(兩棵樹接在一起)和cut(斷開樹上一條邊)變得很容易操作。
link:先將x變為根,然後直接連輕邊上去
cut:假如要斷開x和x父親y間的邊,則對y進行access,然後切開x到y的輕邊
容易看出,這兩個操作複雜度與access複雜度一致。
鏈資訊維護
靈活掌握access,就能進行很好的鏈資訊維護。
樹上的任意一條路徑,在以某個節點為根後都將變成一條樹鏈。
我們用splay維護重鏈資訊,然後進行鏈資訊查詢時,例如查詢u到v,我們可以讓u作為根,然後access節點v,於是u到v的路徑此時變成了一條重鏈,那麼也就是所有點在一顆splay裡,然後這條路徑不就任你擺佈了?
我們發現,access是一個基礎,所有LCT的操作複雜度基本都與access複雜度一致!
所以,access複雜度是多少呢?
access複雜度
我們知道,splay的每次操作,均攤時間複雜度是(雖然我還不會勢能分析),那麼access估計比splay慢。但是你可以從一些大佬寫的國家隊論文得出每次access的均攤時間複雜度和splay一致。至於證明,有待理解。
對於邊權
我們知道,絕大多數樹上亂搞的題都是帶權的。但是splay不能維護邊權——splay中的邊會隨旋轉變換。那麼,這裡有一個很好的思路:將邊看作一個點,將其連向其兩端的點,然後將邊權記錄在表示邊的點那裡。這樣我們就能藐視那些帶權的樹上亂搞的題了。
正確性
學到這裡,我們知道,LCT的形態並非一成不變的。它甚至還會隨時將某些虛邊變為實邊,將某些實邊變為虛邊,將其中某棵splay整個翻轉從而改變許多點的鍵值。那麼它為什麼能保持求得的答案正確呢?
我的理解是:你無論如何虛實變換、翻轉splay,所有點的相對鍵值是一成不變的,於是如果原本x到y的路徑中沒有點z,操作完以後x到y的路徑中也不可能出現點z。
例題
1.【ZJOI2008】樹的統計
Problem
一棵樹上有n個節點,編號分別為1到n,每個節點都有一個權值w。
我們將以下面的形式來要求你對這棵樹完成一些操作:
I. CHANGE u t : 把結點u的權值改為t
II. QMAX u v: 詢問從點u到點v的路徑上的節點的最大權值
III. QSUM u v: 詢問從點u到點v的路徑上的節點的權值和
注意:從點u到點v的路徑上的節點包括u和v本身
Hint
對於100%的資料,保證1<=n<=30000,0<=q<=200000;中途操作中保證每個節點的權值w在-30000到30000之間。
Solution
這題本來是樹鏈剖分的模板題,我們把它加進例題裡面,用LCT切掉它。
由於實在水滿而溢,所以直接上程式碼:
Code
#include <cstdio>
#include <vector>
using namespace std;
#define N 30001
#define A son[x][0]
#define B son[x][1]
#define fo(i,a,b) for(i=a;i<=b;i++)
int i,n,a,b,q,u,v,ss[N],fat[N],son[N][2],d[N],ans;
char s[6];
struct node
{
int w,max,sum;
bool tag;
}f[N];
vector<int>edge[N];
void push(int x)
{
if(!f[x].tag)return;
if(A)f[A].tag=!f[A].tag,swap(son[A][0],son[A][1]);
if(B)f[B].tag=!f[B].tag,swap(son[B][0],son[B][1]);
f[x].tag=0;
}
void up(int x)
{
f[x].max=f[x].sum=f[x].w;
if(A)f[x].max=max(f[x].max,f[A].max),f[x].sum+=f[A].sum;
if(B)f[x].max=max(f[x].max,f[B].max),f[x].sum+=f[B].sum;
}
void dfs(int x)
{
int y;
for(vector<int>::iterator it=edge[x].begin();it!=edge[x].end();it++)
if((y=*it)!=fat[x])
{
fat[y]=x;
dfs(y);
}
up(x);
}
bool so(int x)
{
return son[fat[x]][1]==x;
}
void link(int f,int x,bool d)
{
son[fat[x]=f][d]=x;
}
bool if_root(int x)
{
return !fat[x]||son[fat[x]][so(x)]!=x;
}
void rotate(int x)
{
if(!x)return;
int y=fat[x],z=fat[y],k=so(x),b=son[x][!k];
link(y,b,k);
if(!if_root(y))
link(z,x,so(y));
else fat[x]=z;
link(x,y,!k);
up(y);
up(x);
}
void clear(int x)
{
d[++d[0]]=x;
while(!if_root(x))d[++d[0]]=x=fat[x];
while(d[0])push(d[d[0]--]);
}
void splay(int x)
{
clear(x);
for(int f=fat[x];!if_root(x);rotate(x),f=fat[x])
rotate(!if_root(f)?so(x)==so(f)?f:x:0);
}
void splay(int x,int y)
{
clear(x);
for(int f=fat[x];f!=y;rotate(x),f=fat[x])
rotate(fat[f]!=y?so(x)==so(f)?f:x:0);
}
void access(int y)
{
int x=0;
while(y)
{
splay(y);
link(y,x,1);
x=y;
y=fat[y];
}
}
void fan(int x)
{
f[x].tag=!f[x].tag;
swap(A,B);
}
void makeroot(int x)
{
access(x);
splay(x);
fan(x);
}
int main()
{
scanf("%d",&n);
fo(i,1,n-1)scanf("%d%d",&a,&b),edge[a].push_back(b),edge[b].push_back(a);
dfs(1);
fo(i,1,n)scanf("%d",&f[i].w),up(i);
scanf("%d",&q);
fo(i,1,q)
{
scanf("%s%d%d",&s,&u,&v);
if(s[0]=='C')
{
splay(u);
f[u].w=v;
up(u);
continue;
}
makeroot(u);
access(v);
splay(u);
if(u!=v)splay(v,u),a=son[v][!so(v)];
if(s[1]=='M')
{
ans=max(f[u].w,f[v].w);
if(u!=v&&a)ans=max(ans,f[a].max);
}
else
{
ans=f[u].w;
if(u!=v)
{
ans+=f[v].w;
if(a)ans+=f[a].sum;
}
}
printf("%d\n",ans);
}
}
2.【JZOJ3754】【NOI2014】魔法森林
Problem
給出一個n(≤50000)個節點m(≤100000)條邊的無向圖,每條邊有兩個權值ai,bi(1≤ai,bi≤50000)。求一條從點1到點n的路徑,使得經過的邊的maxai+maxbi最小。輸出這個最小值。
Solution
LCT維護最小生成樹。
鑑於有兩個權值的限制,我們就考慮消除掉ai帶來的影響。
按ai為關鍵字,將所有邊從小到大排序。我們每次列舉一個maxai,將所有可行但卻未嘗插入過的邊插進LCT裡。由於我們現在已消除了ai的限制,我們只需用LCT維護bi即可。
當然,我們知道這麼插可能會插出一個環,那就不屬於LCT可維護的範圍。
那麼,每次我們要插一條從x到y的邊時,我們就先把x變為根,access一下y,然後如果它們原本就是相連的,此刻它們就會在同一棵splay裡面,我們想怎麼搞就怎麼搞;反之,則不在同一棵splay裡面。若它們原本不相連,我們直接連邊即可;否則,我們須查詢一下x到y的maxbi,與此邊的bi比較一下:若後者更小,我們就刪掉那一條最大的邊,連上後者。
對於答案的更新,我們同上一段的方法判斷1到n是否相連,若相連則查詢1到n的maxbi,加上當前列舉的maxai與答案取min即可。
Code
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 50001
#define M 2*N
#define S N+M
#define A son[x][0]
#define B son[x][1]
#define fo(i,a,b) for(i=a;i<=b;i++)
int i,n,m,maxai,fat[S],son[S][2],d[S],x,y,b,ys,ma,mi,ans;
struct edge
{
int x,y,a,b;
}a[M];
struct node
{
int w,max,mi;
bool tag;
}f[S];
bool operator<(const edge&a,const edge&b)
{
return a.a<b.a;
}
void push(int x)
{
if(!f[x].tag)return;
if(A)f[A].tag=!f[A].tag,swap(son[A][0],son[A][1]);
if(B)f[B].tag=!f[B].tag,swap(son[B][0],son[B][1]);
f[x].tag=0;
}
void up(int x)
{
f[x].max=f[x].w;
f[x].mi=x;
if(A&&f[A].max>f[x].max)f[x].max=f[A].max,f[x].mi=f[A].mi;
if(B&&f[B].max>f[x].max)f[x].max=f[B].max,f[x].mi=f[B].mi;
}
bool so(int x)
{
return son[fat[x]][1]==x;
}
void link(int f,int x,bool d)
{
if(x)
son[fat[x]=f][d]=x;
else son[f][d]=0;
}
bool if_root(int x)
{
return !fat[x]||son[fat[x]][so(x)]!=x;
}
void rotate(int x)
{
if(!x)return;
int y=fat[x],z=fat[y],k=so(x),b=son[x][!k];
link(y,b,k);
if(!if_root(y))
link(z,x,so(y));
else fat[x]=z;
link(x,y,!k);
up(y);
up(x);
}
void clear(int x)
{
d[++d[0]]=x;
while(!if_root(x))d[++d[0]]=x=fat[x];
while(d[0])push(d[d[0]--]);
}
void splay(int x)
{
clear(x);
for(int f=fat[x];!if_root(x);rotate(x),f=fat[x])
rotate(!if_root(f)?so(x)==so(f)?f:x:0);
}
void splay(int x,int y)
{
clear(x);
for(int f=fat[x];f!=y;rotate(x),f=fat[x])
rotate(fat[f]!=y?so(x)==so(f)?f:x:0);
}
void access(int y)
{
int x=0;
while(y)
{
splay(y);
link(y,x,1);
x=y;
y=fat[y];
}
}
void fan(int x)
{
f[x].tag=!f[x].tag;
swap(A,B);
}
void makeroot(int x)
{
access(x);
splay(x);
fan(x);
}
void splay1(int x)
{
clear(x);
for(int f=fat[x];!if_root(f);rotate(x),f=fat[x])
rotate(!if_root(fat[f])?so(x)==so(f)?f:x:0);
}
void cut(int x,int y)
{
makeroot(x);
access(y);
splay(x);
splay(y,x);
son[x][so(y)]=fat[y]=0;
}
void Link(int x,int y)
{
makeroot(x);
fat[x]=y;
}
int main()
{
scanf("%d%d",&n,&m);
fo(i,1,m)
{
scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].a,&a[i].b);
if(a[i].x==a[i].y)i--,m--;
}
sort(a+1,a+m+1);
ans=1<<30;
i=0;
fo(maxai,a[1].a,a[m].a)
{
while(i<m&&a[i+1].a==maxai)
{
i++;
x=a[i].x;
y=a[i].y;
b=a[i].b;
makeroot(x);
access(y);
splay(x);
splay1(y);
if(fat[y]==x)
{
ys=son[y][!so(y)];
ma=f[ys].max;
mi=f[ys].mi;
if(ma<=b)continue;
cut(a[mi-n].x,mi);
cut(mi,a[mi-n].y);
}
f[n+i].w=b;
up(n+i);
Link(x,n+i);
Link(n+i,y);
}
makeroot(1);
access(n);
splay(1);
splay1(n);
if(fat[n]==1)ans=min(ans,f[son[n][!so(n)]].max+maxai);
}
if(ans==1<<30)ans=-1;
printf("%d",ans);
}
3.【JZOJ3766】【BJOI2014】大融合
Problem
給出N(≤100000)個點和Q(≤100000)個操作,操作有兩種:
A x y 表示在x和y之間連一條邊。保證之前x和y是不聯通的。
Q x y 表示詢問經過(x,y)這條邊的簡單路徑數。保證x和y之間有一條邊。
Solution
LCT維護子樹大小。
顯然在一棵樹中,經過(x,y)的簡單路徑數等於x那邊的子樹大小*y那邊的子樹大小。
對於插入(x,y)這條邊,我們makeroot(x和y),然後從x向y連一條虛邊。makeroot(x)是為了讓x不再有父親節點,好連;makeroot(y)是為了我們直接將size[y]+=size[x],方便更新,而不必一直往y的祖先走更新。
對於詢問答案,我們用之前的方法將x搞到LCT的根節點,將y旋至x的下方,那麼y那邊的子樹大小即為size[y],x那邊的子樹大小即為size[x]-size[y]。
而通過這題我們也可見一斑,在用LCT維護子樹資訊時,必須要連從虛邊連出去的準子節點一同記錄上。
Code
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 100010
#define A son[x][0]
#define B son[x][1]
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
int i,n,q,x,y,d[N];
char ch;
ll sx,sy;
struct Link_cut_tree
{
int size[N],fat[N],son[N][2];
bool tag[N];
void push(int x)
{
if(!tag[x])return;
if(A)tag[A]=!tag[A],swap(son[A][0],son[A][1]);
if(B)tag[B]=!tag[B],swap(son[B][0],son[B][1]);
tag[x]=0;
}
bool so(int x)
{
return son[fat[x]][1]==x;
}
void link(int f,int x,bool d)
{
if(x)
son[fat[x]=f][d]=x;
else son[f][d]=0;
}
bool if_root(int x)
{
return !fat[x]||son[fat[x]][so(x)]!=x;
}
void rotate(int x)
{
if(!x)return;
int y=fat[x],z=fat[y],k=so(x),b=son[x][!k];
link(y,b,k);
if(!if_root(y))
link(z,x,so(y));
else fat[x]=z;
link(x,y,!k);
int s=size[y]-size[x];
size[y]=s+size[b];
size[x]+=s;
}
void clear(int x)
{
d[++d[0]]=x;
while(!if_root(x))d[++d[0]]=x=fat[x];
while(d[0])push(d[d[0]--]);
}
void splay(int x)
{
clear(x);
for(int f=fat[x];!if_root(x);rotate(x),f=fat[x])
rotate(!if_root(f)?so(x)==so(f)?f:x:0);
}
void splay(int x,int y)
{
clear(x);
for(int f=fat[x];f!=y;rotate(x),f=fat[x])
rotate(fat[f]!=y?so(x)==so(f)?f:x:0);
}
void access(int y)
{
int x=0;
while(y)
{
splay(y);
link(y,x,1);
x=y;
y=fat[y];
}
}
void fan(int x)
{
tag[x]=!tag[x];
swap(A,B);
}
void makeroot(int x)
{
access(x);
splay(x);
fan(x);
}
void Link(int x,int y)
{
makeroot(x);
makeroot(y);
fat[x]=y;
size[y]+=size[x];
}
}run;
int main()
{
scanf("%d%d",&n,&q);
fo(i,1,n)run.size[i]=1;
fo(i,1,q)
{
do
scanf("%c",&ch);
while(ch=='\n');
scanf("%d%d",&x,&y);
if(ch=='A')
{
run.Link(x,y);
continue;
}
run.makeroot(x);
run.access(y);
run.splay(x);
run.splay(y,x);
sy=run.size[y];
sx=run.size[x]-sy;
printf("%lld\n",sx*sy);
}
}
相關推薦
NOI級別的超強資料結構——Link-cut-tree(動態樹)學習小記
前言 其實LCT這種東西,我去年就接觸過並且打過,只不過一直沒調出來。最近優化了我那又醜又長的splay打法,並且用LCT切了道題。在此做一個小結。 簡介 如果有一道題,讓我們維護一棵樹,支援以下操作: 1.鏈上求和; 2.鏈上求最
luoguP3690 【模板】Link Cut Tree (動態樹)[LCT]
格式 %d getch logs cstring name flag -1 處理 題目背景 動態樹 題目描述 給定N個點以及每個點的權值,要你處理接下來的M個操作。操作有4種。操作從0到3編號。點從1到N編號。 0:後接兩個整數(x,y),代表詢問從x到y的路徑上的
luogu P3690 【模板】Link Cut Tree (動態樹)
clu pda col make class getchar() root 動態樹 pan https://www.luogu.org/problemnew/show/3690 這大概還是一道模板題目 #include<cstdio> #include
luogu3690 【模板】Link Cut Tree (動態樹)
pre class HR name print () OS 模板 pushd 參考there和there #include <iostream> #include <cstdio> using namespace std; int n, m, val
【洛谷 P3690】 【模板】Link Cut Tree (動態樹)
shu root org www getch .com std void swap 題目鏈接 \(RT\)。 FlashHu巨佬的博客 #include <cstdio> #define R register int #define I inline void
洛谷P3690 【模板】Link Cut Tree (動態樹)
不開o2優化就要TLE,不知道為啥。 //#include <bits/stdc++.h> #pragma GCC optimize(2) #include<stdio.h> #include<string.h> #include&l
P3690 【模板】Link Cut Tree (動態樹)
rev access cst href pan == www wap swa P3690 【模板】Link Cut Tree (動態樹) 註意:不 要 把 $fa[x]$和$nrt(x)$ 混 在 一 起 ! #include<cstdio> void
【動態樹】【Link Cut Tree】動態樹的理解(入門)
引入 現在我們需要一個數據結構滿足支援以下的操作: 兩個節點連線(保證不出現環) 兩個節點斷開 求任意兩個節點之間的區間和 這樣是不是很像樹鏈剖分? 但是因為是動態的所以我們採用動態樹來進行維護。 樣例 現在給出一個樣例,我們一下的解釋都以當前
Link Cut Tree(無圖慎入)
clas 提取 rotate true urn find ring namespace != 類似樹鏈剖分(其實直接記住就可以了),提前放代碼 1 #include<cstdio> 2 #include<cstdlib> 3 #incl
LUOGU P3690 【模板】Link Cut Tree (lct)
傳送門 解題思路 \(lct\)就是基於實鏈剖分,用\(splay\)來維護每一條實鏈,\(lct\)的維護物件是一棵森林。\(lct\)支援很多神奇的操作: \(1、\) \(access\):這是\(lct\)的核心操作,就是將一個點與根打通,就是把路徑上的所有邊變成實邊,具體就是轉到根,換兒子
2018.10.07 洛谷P3690 【模板】Link Cut Tree (lct)
傳送門 lct模板題。 學習了新姿勢: 判斷一條邊是否已經存在的方法。 感覺其它都跟洞穴勘探差不多。 程式碼: #include<bits/stdc++.h> #define N 30000
資料結構——稀疏矩陣運算器(C語言)
資料結構——稀疏矩陣運算器(C語言) /*****************稀疏矩陣運算器 ****************/ #include<stdio.h> #include<stdlib.h> #define OK 1 #define TRUE
資料結構(Java筆記)—佇列(順序佇列)
佇列(Queue)—先進先出線性表,佇列結構具有特殊的運算規則,從資料的邏輯結構來看,佇列結構是一種線性表;從資料的儲存結構來看,佇列結構分為順序佇列結構和鏈式佇列結構; 順序佇列結構:使用一組地址連
資料結構的Java實現(十四)—— 圖
1、圖的定義 圖通常有個固定的形狀,這是由物理或抽象的問題所決定的。比如圖中節點表示城市,而邊可能表示城市間的班機航線。如下圖是美國加利福利亞簡化的高速公路網: ①、鄰接:如果兩個頂點被同一條邊連線,就稱這兩個頂點是鄰接的。如上圖 I 和 G 就是鄰接的,而
資料結構---迷宮問題題解(C語言)
資料結構—迷宮問題題解(C語言) #include<stdio.h> #include<stdlib.h> #define FALSE 0 #define TRUE 1 #define OK 1 #define M 20
郝斌資料結構入門---P35---佇列(迴圈佇列)
郝斌資料結構入門---P35---佇列 線性結構的常見應用之一:佇列(頭部刪除,尾部插入) 定義:一種可以實現“ 先進先出 ”的儲存結構,佇列類似於排隊去買票(一端入,一端出) 分類:鏈式佇列(用連結串列實現),靜態佇列(用陣列實現),靜態佇列通常都必須是迴圈佇列。
資料結構——商品貨架管理(C++實現) 原始碼
*尚品貨架可以看成一個棧,棧頂商品的生產日期最早,棧底商品的生產日期最近。 *上貨時,需要倒貨架,以保證生產日期較新的商品在較靠裡的位置 針對一種特定商品,實現上述管理過程 測試資料:由學生自己確定一組測試資料。注意測試邊界資料,如空棧。 原始碼 #inclu
資料結構作業9—佇列(判斷題)
1-1所謂“迴圈佇列”是指用單向迴圈連結串列或者迴圈陣列表示的佇列。 (1分) T F 作者: DS課程組 單位: 浙江大學 1-2不論是入佇列操作還是入棧操作,在順序儲存結構上都需要考慮"溢位"情況。 (2分) T F
資料結構之順序佇列(迴圈佇列)
由於佇列有元素出列,front就向後移動,所以佇列前面的空間就空了出來。為了更合理的利用空間,人們想了一個辦法:將佇列的首尾相連線。這樣當rear移動到LENGTH時,會再從0開始迴圈。那當什麼時候佇列滿呢?當rear等於front的時候。可是佇列為空的時候也是同樣的條件
Redis和nosql簡介,api呼叫;Redis資料功能(String型別的資料處理);List資料結構(及Java呼叫處理);Hash資料結構;Set資料結構功能;sortedSet(有序集合)數
1、Redis和nosql簡介,api呼叫14.1/ nosql介紹NoSQL:一類新出現的資料庫(not only sql),它的特點:1、 不支援SQL語法2、 儲存結構跟傳統關係型資料庫中的那種關係表完全不同,nosql中儲存的資料都是KV形式3、 NoSQL的世界中沒有一種通用的語言,每種no