1. 程式人生 > >NOI級別的超強資料結構——Link-cut-tree(動態樹)學習小記

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的每次操作,均攤時間複雜度是O(log2n)雖然我還不會勢能分析),那麼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