【數據結構】Splay學習筆記

昨天這個時候到現在終於把Splay給搞明白了,還A了一道鬱悶的出納員;剛學完的感受:我再也不碰這東西了;做完鬱悶的出納員的感受:我發誓這輩子不當出納員(雖然這確實只是個入門題……) 
於是來講一講這個噁心的東西吧……(全程不用指針,請做好心理準備……) 
學習前請先學習下二叉搜索樹,裏面可能直接用到這個東西 

首先,Splay是一個數據結構,爲了突出它是一個數據結構,所以給他開個結構體……

struct tree
{
    int val,sz,cnt;//val爲值 sz爲子樹大小,cnt爲"有多少這個值"
    //cnt:假如出現了兩個一模一樣的值,只需要讓cnt+1就可以了,cnt是數目
    int s[2],f;//s[0]爲左兒子 s[1]爲右兒子 f爲父親
};

爲了之後的操作,我們再寫三個函數(如果認爲沒用可以先跳過,過一會用到了再回來看)

bool son(int x) //返回x是左兒子還是右兒子,如果爲左兒子,返回0,右兒子則返回1
{
    return a[a[x].f].s[1] == x;
}

void rejs(int x)    //重新計算以x爲根,子樹的大小,子樹大小等於左子樹大小加右子樹大小(好吧我承認我英語拙計)
{
    a[x].sz = a[a[x].s[0]].sz + a[a[x].s[1]].sz + a[x].cnt;
}

void point(int x,int y,bool z)  //在x下面插入y,y是x的z兒子(z爲0則左兒子,z爲1爲右兒子)
{
    a[x].s[z] = y;
    a[y].f = x;
}

寫完這幾個樹的基本函數,我們要開始講splay啦(似乎我之前好像真的沒有開始講splay) 
Splay最重要的一個操作就是旋轉(rotate),如下,(圖片爲1600x900,可直接作爲桌面): 
這裏寫圖片描述 
我們的目的是將紅色節點旋轉到它父親的位置,即黃色節點的位置 
我們應當怎麼辦呢?不要急,看組圖 
這裏寫圖片描述 
bool型變量son(黃)代表黃色節點是左兒子還是右兒子 
將紅色節點接到黃色節點的父親(也就是綠色節點)的下面,是綠色節點的son(黃)兒子,也就是說代替了黃色節點; 
黃色節點被代替了怎麼辦?再開個變量記錄一下即可 
這裏寫圖片描述 
bool型變量son(紅)代表紅色節點是左兒子還是右兒子 
把紅色節點的son(紅)&1兒子(圖中的藍色節點,藍色節點和父親關係和紅色節點和父親關係正好相反)接到黃色節點下面,是黃色節點是son(紅)兒子,代替了紅色節點; 
這裏寫圖片描述 
最後把黃色節點的父親改爲紅色節點,旋轉完成! 
旋轉完成後: 
這裏寫圖片描述 
(我再也不用PS畫這玩意了……)

void rot(int x)
{
    int p = a[x].f;
    bool d = son(x);
    point(a[p].f,x,son(p));
    point(p,a[x].s[d^1],d);
    point(x,p,d^1);
    rejs(p);
    rejs(x);
    if(a[x].f == 0)
        root = x;//root是根節點的編號
}

這就是Splay的核心操作——rotate,它的時間複雜度是——O(1) 
然而學會了有什麼用呢= =,下面來講Splay的其他操作及如何平衡 
首先是Splay的插入操作,Splay每插入一個新節點,就把這個節點強制旋轉到根節點,如何強制旋轉到根節點呢?如果while(不是根節點)rotate(x)的話就有可能退化成一條鏈……於是Splay“貼心”的爲我們準備了splay操作…… 
splay操作的目的是將一個點旋轉到根節點,這個操作是這樣的: 
1.如果這個點是根節點,那麼你可以直接退出了…… 


2.如果這個點的父親是根節點,那麼就直接把這個點rotate上去…… 


3.如果上述兩條均不滿足,那麼分類討論: 


(1)設son(x)爲這個和父親節點的關係,son(f)爲父親節點和爺爺節點的關係 


(2)如果兩個關係相同(均爲左兒子或均爲右兒子),那麼就先rotate父親節點,然後rotate這個節點 


(3)如果兩個關係不同(一個是左兒子一個是右兒子),那麼就把這個節點連續rotate兩次

void splay(int x)
{
    while(a[x].f != 0){
        if(a[a[x].f].f == 0)
            rot(x);
        else
        {
            if(son(x) == son(a[x].f)){
                rot(a[x].f);
                rot(x);
            }
            else{
                rot(x);
                rot(x);
            }
        }
    }
}

這樣就可以寫出插入操作了,還記得插入操作怎麼做嗎?插入一個節點然後splay到根節點

void ins(int x)
{
    int w = root,f = 0;
    int p = findn(x);
    if(p)//如果這個值已經存在,那麼就直接給這個節點+1吧
    {
        a[p].cnt ++;
        while(p != root)
        {
            rejs(p);
            p = a[p].f;
        }
        rejs(root);
        return ;
    }
    while(w)
    {
        f = w;
        if(x < a[w].val)
            w = a[w].s[0];
        else
            w = a[w].s[1];
    }
    //這是前半部分,和普通的二叉查找樹插入方法一樣,我承認我打的很醜……
    a[++tot].val = x;
    a[tot].cnt = 1;//新建節點,標號爲tot
    if(f == 0)//如果這個點是根節點的話,直接插入即可……
    {
        root = tot;
        rejs(root);
        return ;
    }
    //否則插入這個節點並spaly到根節點
    if(x < a[f].val)
        point(f,tot,0);
    else
        point(f,tot,1);
    splay(tot);
}
然後是刪除操作,Splay的刪除操作比較喪病,我只講一下我的寫法吧…… 
設要刪除的節點爲x,那麼,首先splay一下x的前驅,然後splay一下x的後繼,啥?你前驅和後繼還不會寫???好吧好吧,我講…… 
x的前驅是指小於x且最大的節點,後繼就是大於x且最小的節點 
前驅就是在x的左子樹上一直往右跑,如果沒有左子樹那就往上找 
後繼就是在x的右子樹上一直往左跑,如果沒有右子樹那也往上找 
int near(int x,bool d)
{
    if(a[x].s[d] != 0){
        int p = a[x].s[d];
        while(a[p].s[d^1])
            p = a[p].s[d^1];
        return p;
    }
    else if(son(x) == d^1)
        return a[x].f;
    else{
        int p = a[x].f;
        while(son(p) == d){
            if(p == root)
                return 0;
            p = a[p].f;
        }
        return a[p].f;
    }
}
接着繼續我們的刪除操作,刪除操作就是上面講的:splay(x的前驅),splay(x的後繼),然後x的後繼變爲根節點,x的前驅變爲根節點的左兒子,x到哪裏了呢? 
x成爲了x前驅的右子樹!直接砍掉這個右子樹就ok啦~(如果刪除區間也可以這樣做,splay區間左邊界的前驅,splay區間右邊界的後繼,整個區間就變成了區間左界前驅的右子樹~(≧▽≦)/~)
void cle(int x)//cle操作是清除一個點,沒有也無所謂吧
{
    a[a[x].f].s[son(x)] = 0;
    a[x].val = 0;
    a[x].sz = 0;
    rejs(a[x].f);
    a[x].f = 0;
}

void del(int x)
{
    int p = near(x,0);
    int q = near(x,1);
    if(!p || !q)
    {
        splay(x);
        if(p == 0)
        {
            root = a[x].s[1];
            a[a[x].s[0]].f = 0;
        }
        else
        {
            root = a[x].s[0];
            a[a[x].s[0]].f = 0;
        }
        return ;
    }
    splay(p);
    splay(q);
    rot(p);
    cle(x);
}
至此你終於獲得了一棵可用的splay,拿去亂搞吧!


轉自:http://blog.csdn.net/farestorm/article/details/49153565

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章