Splay study notes (nanny level)

Record my learning and understanding of Splay.

First introduce Splay

Splay Tree, also called split tree, is a binary sorting tree that can insert, search and delete in O (log n). It was invented in 1985 by Daniel Sleator and Robert Endre Tarjan. 

The general operations on the stretch tree are based on the stretch operation: suppose you want to perform a series of search operations on a binary search tree. In order to make the entire search time smaller, those entries that are checked frequently should always be near the root of the tree s position. So I thought of designing a simple method to reconstruct the tree after each search and move the searched items closer to the root of the tree. The stretch tree came into being. The stretch tree is a self-adjusting binary search tree. It will follow a path from a node to the root of the tree and move the node to the root of the tree through a series of rotations.

Its advantage is that there is no need to record redundant information for balancing the tree. (From Baidu Encyclopedia)

Well, the best understanding is to draw a picture to represent

 

 This is a simple balanced tree that satisfies that the left son is smaller than the father and the right son is larger than the father, and any subtree is also a balanced tree. But this balance tree may appear to be a chain in extreme cases, namely 2 3 4 5 6, in order to solve this problem we can use Splay. The specific principle is that we can avoid this situation by modifying the order of the nodes. For example, the above picture can be rotated node 2 to become:

This is to rotate 2 (rotate). We can find that it is still a balanced tree after rotation, then we rotate the node 2 again

 

 

Through these three pictures we can find some laws of rotation:

1. The position of the rotating node changes to the position of its parent node, and the parent node to the original relative position of the node (that is, the left node is the parent node to the position of the right son, and vice versa).

2. The other son of the parent node remains unchanged, the position of the original rotating node becomes the relative son of the rotating node (that is, it is the left son, then its right son becomes the left son of the parent node) .

It will be more intuitive to express in code. (Root records the root node, sum records the number of nodes)

 

struct node
{
    int ch [ 2 ], ff, val, size, cnt; // Number of the size of the tree of the size of the left and right son parent node val 
} tree [N]; 
int root, sum;
void pushup ( int x) { tree [x] .size = tree [tree [x] .ch [ 0 ]]. size + tree [tree [x] .ch [ 1 ]]. size + tree [x] .cnt; // size is the left son Size plus right son size plus cnt } inline void rotate ( int x) // x is the node to be rotated { int y = tree [x] .ff, z = tree [y] .ff; // y is the parent node of x and z is the grandfather of x Node int k = tree [y] .ch [ 1 ] == x; // k represents which son of x is y 0 is the left son 1 is the right son tree [y] .ff = x; // father of y Becomes x tree [x] .ff = z; // the father of x becomes z tree [tree [x] .ch [! K]]. Ff = y; // the original of x is relative to the position of x The son's father becomes y tree [z] .ch [tree [z] .ch [ 1 ] == y] = x; // the original y position of z becomes x tree [y] .ch [k ] = tree [x] .ch [! k]; // y's original x position becomes x's son opposite x becomes y's son (what am I talking about) tree [x] .ch [! k] = y; // The original son of x opposite to the position of x becomes y pushup (x), pushup (y); // update }

 

 

This is a basic rotation operation, is it very simple (?).

Next, you can look at some problems about rotation. If we only rotate one point, we will find that a long chain will always exist. How to solve it? We can rotate Y, but how to rotate, there are many situations that need to be discussed at this time, but after reading a big man's explanation, there is a simpler way of writing.

 

void Splay ( int x, int goal) // rotate x to the goal ’s son, when goal is 0, turn x to the root node 
{
     int y, z;
     while (tree [x] .ff! = goal)
    {
        y = tree [x] .ff; // parent node 
        z = tree [y] .ff; // grandfather node 
        if (z! = goal)
        {
            (tree [z] .ch [ 0 ] == y) ^ (tree [y] .ch [ 0 ] == x)? rotate (x): rotate (y); // If x and y are respectively y and The same son of z rotates y otherwise rotates x 
        }
        rotate (x); // Finally, you must rotate x 
    }
     if (goal == 0 ) // That is, x is the root node 
    {
        root = x;
    }
}

 

It ’s very concise to write like this (thank you guys)

Then there is the Find operation. If you understand the balance tree, you should be able to write find independently, that is, use the nature of the left son and the right son to be similar to binary search. We can rotate the number to be found to the root node, so that the operation will be more convenient, and we can also directly return the found position. In order to facilitate the understanding of the following code, it will be more complicated to write (by the way, to find out the ranking of x, you can use the size of the subtree after finding)

inline void find ( int x) // x is the value to find 
{
     int u = root; // u is the root node 
    while ( true )
    {
        if (tree [u] .ch [x> tree [u] .val]) // Avoid x 
        {
             break ;
        }
        if (tree [u] .val> x) // If val is greater than x, then x is the left son of u 
        {
            u = tree[u].ch[0];
        }
        if (tree [u] .val <x) // If val is less than x then x is in u's right son 
        {
            u = tree[u].ch[1];
        }
        if (tree [u] .val == x) // find x 
        {
             break ;
        }
    }
    Splay (u, 0 ); // rotate the found position to the root node 
}

Next is insert, which is similar to the find operation, but different from find is that if there is no son after the parent node is found, a new son can be generated.

inline void insert ( int x) // Insert x 
{
     int u = root, ff = 0 ;
     while (tree [u] .val! = x && u) // If u is 0, it means there is no 
    {
        ff = u; // Record parent node 
        u = tree [u] .ch [tree [u] .val < x];
    }
    if (u! = 0 ) // That is, there is already u 
    {
        tree[u].cnt++;//数量加一
    }
    else
    {
        sum++;
        if (ff)//假如ff不为0
        {
            tree[ff].ch[tree[ff].val < x] = sum;
        }
        tree[sum].ff = ff;
        tree[sum].cnt = 1;
        tree[sum].size = 1;
        tree[sum].val = x;
    }
    Splay(sum, 0);//把该点旋转为根结点,同时能pushup
}

 写完insert之后我们可以再了解一下前驱和后继,利用find将x移动到根节点上,前驱即在x的左子树,后继在x的右子树,还需考虑到x不存在的情况。具体可见代码注释(最后的if else可以简化在一起,为方便理解分开写)

inline int Next(int x, int k)//k=0代表前驱,k=1代表后继
{
    find(x);
    if (tree[root].val > x&& k == 1)//假如x不存在且要找的是后继
    {
        return root;//此时根结点即满足要求
    }
    if (tree[root].val < x && k == 0)//假如x不存在且要找的是前驱
    {
        return root;//此时根结点即满足要求
    }
    if (k)//找后继
    {
        int u = tree[root].ch[1];//令u为x的右子树,即大于x
        while (tree[u].ch[0])//要找的大于x的最小的数即找出左子树的值
        {
            u = tree[u].ch[0];//左子树一定小于右子树
        }
        return u;
    }
    else//找前驱
    {
        int u = tree[root].ch[0];//同理先令u为x的左子树
        while (tree[u].ch[1])//找最大值在右子树找
        {
            u = tree[u].ch[1];//右子树一定大于左子树
        }
        return u;
    }
}

在写出Next之后我们可以拓展一下Next的应用,假如我们要删除x,find(x)再删除的话是很麻烦的,因为还会牵连到x的子树,但我们有没有办法将x的子树全都去掉呢?利用Next和平衡树的性质是可以的,假若我们将x的后继变为根结点,再将x的前驱变为x的后继的左儿子的话,此时x就为x的前驱的右儿子且绝对没有儿子。此时我们就可以直接将其删除(同理也可以将x的前驱变为根结点,可以自己画一下)。注意考虑到x的数量,具体也可以根据问题来修改。具体见代码:

inline void Delete(int x)
{
    int last = Next(x, 0);//找到x的前驱
    int next = Next(x, 1);//找到x的后继
    Splay(next, 0);//将x的后继变为根结点
    Splay(last, next);//将x的前驱变为x的后继的左儿子
    int del = tree[last].ch[1];//del为x的位置
    if (tree[del].cnt > 1)//如果x的数量大于一
    {
        tree[del].cnt--;
        Splay(del, 0);//将del移动到根结点同时可以更新树
    }
    else
    {
        tree[last].ch[1] = 0;//直接删除
        Splay(last, 0);//将last移动到根结点同时更新树
    }
}

最后我们可以尝试求出kth,注意是求出第k小的而不是第k大= =,kth可以利用size来求出,根据排名和size可以判断x是在左子树还是右子树或者结点上,一路循环即可

inline int kth(int x)
{
    int u = root;
    if (tree[u].size < x)//即x大于树的大小此时不存在
    {
        return 0;
    }
    while (true)
    {
        if (tree[tree[u].ch[0]].size + tree[u].cnt >= x)//假如x在左子树或根结点上
        {
            if (tree[tree[u].ch[0]].size >= x)//假如左子树的大小大于x
            {
                u = tree[u].ch[0];//即x在左子树里面找
            }
            else//即x在根结点上
            {
                return tree[u].val;
            }
        }
        else//x在右子树上
        {
            x -= tree[tree[u].ch[0]].size + tree[u].cnt;
            u = tree[u].ch[1];
        }
    }
}

这些是根据洛谷P3369学习的一些基本操作,还有一些操作后续填坑。

Guess you like

Origin www.cnblogs.com/Load-Star/p/12687227.html