数据结构-FHQ Treap(也许是全网唯一一个认真给你画图解的)

平衡树-FHQTreap

FHQTreap,又称非旋转Treap,顾名思义,是FHQ发明的不用旋转来维护随机key值的小根堆性质的平衡树。

树的结点

为了方便树的各种操作,本蒟蒻的FHQ每个节点只存一个数而不是相等的所有数,保证LC<now<=RC,代码是这样的

 1 struct Node{
 2     Node *child[2];
 3     int val, key, size;
 4 
 5     Node(int val):val(val), size(1), key(Rand()) {
 6         child[0] = child[1] = NULL;
 7     }
 8 
 9     void Update() {
10         size = 1;
11         if (child[0]) size += child[0]->size;
12         if (child[1]) size += child[1]->size;
13     }
14 }

其中key是随机值

基本操作(一)——分裂Split

功能

Split可以把以now为根的子树分成两个部分,tree1为前k小的值,tree2为剩余部分,返回两颗新树的树根

拆解步骤

  1. 边界:
    (1) 如果now为空树,两新树都为空树
    (2) 如果k为0,tree1为空,tree2为原树
    (3) 如果k>=原树的节点数,tree1为原树,tree2为空
  2. 记录原树左子树的size
  3. 如果k <= lsize,递归拆左子树,否则递归拆右子树
  4. 按需要拼回两个子树,return

是不是没听懂3、4步?行,还有图解。

情况一

假设k <= lc,我们需要递归拆左子树,我们来画一张图

如上图,我们分为四部分看:①原根,②右子树,③左子树k内部分,④左子树k外部分

 

我们对左子树调用split,把③④分开

 

 

最后,我们把原根①的信息复制到一个new结点里,delete掉①

新结点左面挂递归掰出来的④,右面挂②也就是原来的右子树,这样形成了t2

递归出来的③也就成为了t1

情况二,与前一种相反

k > ls,此时我们仍然来把它分成四部分

k包括①②③三部分,④为右子树的剩余部分。不难看出,③的size是k - ls - 1(划重点)。

 知道了③的size,那么我们也可以对右子树使用split,把③和④掰开。

 

 然后就跟刚才一样了,复制①挂②③作为t1,④作为t2

接下来是喜闻乐见的代码

如果使用STL的pair来返回两个根,那么就可以特别快乐的这样搞定

 1 pair<Node*, Node*> Split(Node* now, int k) {
 2     Node* null = NULL;
 3     if (!now) return make_pair(null, null);
 4     if (!k) return make_pair(null, now);
 5     if (k >= now->size) return make_pair(now, null);
 6 //------以上为判定边界--------
 7     int ls = now->child[0] ? now->child[0]->size : 0;//记录ls
 8     if (ls >= k) {//情况二
 9         pair<Node*, Node*> temp = Split(now->child[0], k);//拆左子树
10         Node* b = new Node(now->val);//建新根,挂新子树
11         b->key = now->key;
12         b->child[0] = temp.second;
13         b->child[1] = now->child[1];
14         b->Update();
15         delete now;
16         return make_pair(temp.first, b);
17     } else {//情况一
18         pair<Node*, Node*> temp = Split(now->child[1], k - ls - 1);
19         Node* a = new Node(now->val);//建新根,挂新子树
20         a->key = now->key;
21         a->child[1] = temp.first;
22         a->child[0] = now->child[0];
23         a->Update();
24         delete now;    
25         return make_pair(a, temp.second);
26     }
27 }    

重点:NULL不能直接存在pair里,需要新建一个null指针代替NULL

如果不想使用pair或者是建一个null,也可以在调用前临时建两个指针来存拆出来的两个子树,然后在函数中传址调用来让它们指向两棵新树,代码如下

 

 1 void Split(Node *now, int k, Node *&t1, Node *&t2) {
 2     if (!now) {
 3         t1 = t2 = NULL; return;
 4     } 
 5     if (!k) {
 6         t1 = NULL; t2 = now; return;
 7     }
 8     if (k >= now->size) {
 9         t1 = now; t2 = NULL; return;
10     }
11     int ls = now->child[0] ? now->child[0]->size : 0;
12     if (ls >= k) {
13         Node *temp;
14         Split(now->child[0], k, t1, temp);
15         t2 = now; t2->child[0] = temp; 
16         t2->Update(); return;
17     } else {
18         Node *temp;
19         Split(now->child[1], k - ls - 1, temp, t2);
20         t1 = now; t1->child[1] = temp;
21         t1->Update(); return;
22     }
23 }

 

 

这样也很清晰明了

情况(二)——合并Merge

这个函数就比较简单了,把a, b两棵树捏到一起,返回新根。流程如下

  1. 判断边界:如果a空返回b,如果b空返回a
  2. 如果akey < bkey,调用Merge,把b和a的右子树捏到一起作为a的右子树
  3. 如果akey >= bkey,调用Merge,把a和b的左子树捏到一起作为b的左子树

应该还好理解,放代码

 1 Node* Merge(Node* a, Node* b) {
 2     if (!a) return b;
 3     if (!b) return a;//判断边界
 4     if (a->key < b->key) {
 5         a->child[1] = Merge(a->child[1], b);//递归调用
 6         a->Update();//记得更新
 7         return a;//返回新根
 8     } else {
 9         b->child[0] = Merge(a, b->child[0]);//递归调用
10         b->Update();//记得更新
11         return b;//返回新根
12     }
13 }

注意:调用时保证b根的值>=a根的值

其他操作:查Rank,前驱,后继

就是普通的二叉搜索树操作,直接放代码了

 1 int Rank(Node* now, int k) {
 2     if (!now) return 0;
 3     int ls = now->child[0] ? now->child[0]->size : 0;
 4     if (now->val <= k)
 5         return ls + 1 + Rank(now->child[1], k);
 6     else
 7         return Rank(now->child[0], k);
 8 }
 9 
10 int GetPre(Node *now, int k) {
11     int ans = -INF;
12     while (now) {
13         if (now->val < k) {
14             ans = max(ans, now->val);
15             now = now->child[1];
16         } else {
17             now = now->child[0];
18         }
19     }
20     return ans;
21 }
22 
23 int GetSuc(Node *now, int k) {
24     int ans = INF;
25     while (now) {
26         if (now->val > k) {
27             ans = min(ans ,now->val);
28             now = now->child[0];
29         } else {
30             now = now->child[1];
31         }
32     }
33     return ans;
34 }

基于Split和Merge的操作:插入、删除、查找第k小

插入:查一下新数的rank,然后拆成 <rank 和 >=rank 两部分,然后把新结点与它们合并

pair版

1 void Insert(int val) {
2     int rank = Rank(root, val);
3     pair<Node*, Node*> temp = Split(root, rank);
4     Node* nod = new Node(val);
5     root = Merge(temp.first, nod);
6     root = Merge(root, temp.second);
7 }

普通版

1 void Insert(int val) {
2     int rank = Rank(root, val);
3     Node *temp1, *temp2;
4     Split(root, rank, temp1, temp2);
5     Node *nod = new Node(val);
6     root = Merge(temp1, nod);
7     root = Merge(root, temp2);
8 }

删除:查rank,拆出rank个,t1中拆出rank - 1个,把中间那个删掉,剩下的拼回去

pair版

1 void Remove(int val) {
2     int rank = Rank(root, val);
3     pair<Node*, Node*> t1 = Split(root, rank), t2 = Split(t1.first, rank - 1);
4     root = Merge(t2.first, t1.second); delete t2.second;
5 }

普通版

1 void Remove(int val) {
2     int rank = Rank(root, val);
3     Node *t11, *t12, *t21, *t22;
4     Split(root, rank, t11, t12);
5     Split(t11, rank - 1, t21, t22);
6     root = Merge(t21, t12);
7     delete t22;
8 }

查第k小:拆出k - 1个,t2中拆出1个,返回中间那个结点的值,最后拼回去

pair版

1 int FindKth(int k) {
2     pair<Node*, Node*> x = Split(root, k - 1);
3     pair<Node*, Node*> y = Split(x.second, 1);
4     Node* now = y.first;
5     root = Merge(x.first, Merge(now, y.second));
6     return now ? now->val : 0;
7 }

普通版

1 int FindKth(int k) {
2     Node *x1, *x2, *y1, *y2;
3     Split(root, k - 1, x1, x2);
4     Split(x2, 1, y1, y2);
5     Node *now = y1;
6     root = Merge(x1, Merge(now, y2));
7     return now ? now->val : 0;
8 }

最后,模板题完整代码

pair版

 

#include <cstdio>
#include <algorithm>

using std::pair;
using std::make_pair;
using std::min;
using std::max;

const int INF = 2147483647;

struct Node{
    Node* child[2];
    int val, key, size;

    Node(int val):val(val), size(1), key(rand()) {
        child[0] = NULL; child[1] = NULL;
    }

    void Update() {
        size = 1;
        if (child[0]) size += child[0]->size;
        if (child[1]) size += child[1]->size;
    }
};

Node *root;

Node* Merge(Node* a, Node* b) {
    if (!a) return b;
    if (!b) return a;
    if (a->key < b->key) {
        a->child[1] = Merge(a->child[1], b);
        a->Update();
        return a;
    } else {
        b->child[0] = Merge(a, b->child[0]);
        b->Update();
        return b;
    }
}

pair<Node*, Node*> Split(Node* now, int k) {
    Node* null = NULL;
    if (!now) return make_pair(null, null);
    if (!k) return make_pair(null, now);
    if (k >= now->size) return make_pair(now, null);
    int ls = now->child[0] ? now->child[0]->size : 0;
    if (ls >= k) {
        pair<Node*, Node*> temp = Split(now->child[0], k);
        Node* b = new Node(now->val);
        b->key = now->key;
        b->child[0] = temp.second;
        b->child[1] = now->child[1];
        b->Update();
        delete now;
        return make_pair(temp.first, b);
    } else {
        pair<Node*, Node*> temp = Split(now->child[1], k - ls - 1);
        Node* a = new Node(now->val);
        a->key = now->key;
        a->child[1] = temp.first;
        a->child[0] = now->child[0];
        a->Update();
        delete now;
        return make_pair(a, temp.second);
    }
}

int Rank(Node* now, int k) {
    if (!now) return 0;
    int ls = now->child[0] ? now->child[0]->size : 0;
    if (now->val <= k)
        return ls + 1 + Rank(now->child[1], k);
    else
        return Rank(now->child[0], k);
}

int GetPre(Node* now, int k) {
    int ans = -INF;
    while (now) {
        if (now->val < k) {
            ans = max(ans, now->val);
            now = now->child[1];
        } else {
            now = now->child[0];
        }
    }
    return ans;
}

int GetSuc(Node* now, int k) {
    int ans = INF;
    while (now) {
        if (now->val > k) {
            ans = min(ans, now->val);
            now = now->child[0];
        } else {
            now = now->child[1];
        }
    }
    return ans;
}

int FindKth(int k) {
    pair<Node*, Node*> x = Split(root, k - 1);
    pair<Node*, Node*> y = Split(x.second, 1);
    Node* now = y.first;
    root = Merge(x.first, Merge(now, y.second));
    return now ? now->val : 0;
}

void Insert(int val) {
    int rank = Rank(root, val);
    pair<Node*, Node*> temp = Split(root, rank);
    Node* nod = new Node(val);
    root = Merge(temp.first, nod);
    root = Merge(root, temp.second);
}

void Remove(int val) {
    int rank = Rank(root, val);
    pair<Node*, Node*> t1 = Split(root, rank), t2 = Split(t1.first, rank - 1);
    root = Merge(t2.first, t1.second);
    delete t2.second;
}

int n;

int main() {
    scanf("%d", &n);
    for (int i = 1, opt, x; i <= n; i++) {
        scanf("%d %d", &opt, &x);
        switch(opt) {
            case 1: Insert(x); break;
            case 2: Remove(x); break;
            case 3: printf("%d\n", Rank(root, x - 1) + 1); break;
            case 4: printf("%d\n", FindKth(x)); break;
            case 5: printf("%d\n", GetPre(root, x)); break;
            case 6: printf("%d\n", GetSuc(root, x)); break;
        }
    }
    return 0;
}

  

普通版

// luogu-judger-enable-o2
#include <cstdio>
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))

const int INF = 2147483647;

int Rand() {
    static int seed = 39444;
    return seed = (((seed ^ 1433223) + 810872ll) * 19260817ll) % 2147483647;
}

struct Node{
    Node *child[2];
    int val, key, size;
    
    Node(int val):val(val), size(1), key(Rand()) {
        child[0] = NULL; child[1] = NULL;
    }
    
    void Update() {
        size = 1;
        if (child[0]) size += child[0]->size;
        if (child[1]) size += child[1]->size;
    }
};

Node *root = NULL;

Node* Merge(Node* a, Node* b) {
    if (!b) return a;
    if (!a) return b;
    if (a->key < b->key) {
        a->child[1] = Merge(a->child[1], b);
        a->Update();
        return a;
    } else {
        b->child[0] = Merge(a, b->child[0]);
        b->Update();
        return b;
    }
}

void Split(Node *now, int k, Node *&t1, Node *&t2) {
    if (!now) {
        t1 = t2 = NULL; return;
    } 
    if (!k) {
        t1 = NULL; t2 = now; return;
    }
    if (k >= now->size) {
        t1 = now; t2 = NULL; return;
    }
    int ls = now->child[0] ? now->child[0]->size : 0;
    if (ls >= k) {
        Node *temp;
        Split(now->child[0], k, t1, temp);
        t2 = now; t2->child[0] = temp; 
        t2->Update(); return;
    } else {
        Node *temp;
        Split(now->child[1], k - ls - 1, temp, t2);
        t1 = now; t1->child[1] = temp;
        t1->Update(); return;
    }
}

int Rank(Node *now, int k) {
    if (!now) return 0;
    int ls = now->child[0] ? now->child[0]->size : 0;
    if (now->val <= k) return ls + 1 + Rank(now->child[1], k);
    else return Rank(now->child[0], k);
}

int GetPre(Node *now, int k) {
    int ans = -INF;
    while (now) {
        if (now->val < k) {
            ans = max(ans, now->val);
            now = now->child[1];
        } else {
            now = now->child[0];
        }
    }
    return ans;
}

int GetSuc(Node *now, int k) {
    int ans = INF;
    while (now) {
        if (now->val > k) {
            ans = min(ans ,now->val);
            now = now->child[0];
        } else {
            now = now->child[1];
        }
    }
    return ans;
}

int FindKth(int k) {
    Node *x1, *x2, *y1, *y2;
    Split(root, k - 1, x1, x2);
    Split(x2, 1, y1, y2);
    Node *now = y1;
    root = Merge(x1, Merge(now, y2));
    return now ? now->val : 0;
}

void Insert(int val) {
    int rank = Rank(root, val);
    Node *temp1, *temp2;
    Split(root, rank, temp1, temp2);
    Node *nod = new Node(val);
    root = Merge(temp1, nod);
    root = Merge(root, temp2);
}

void Remove(int val) {
    int rank = Rank(root, val);
    Node *t11, *t12, *t21, *t22;
    Split(root, rank, t11, t12);
    Split(t11, rank - 1, t21, t22);
    root = Merge(t21, t12);
    delete t22;
}

int n;

int main() {
    scanf("%d", &n);
    for (int i = 1, opt, x; i <= n; i++) {
        scanf("%d %d", &opt, &x);
        switch(opt) {
            case 1: Insert(x); break;
            case 2: Remove(x); break;
            case 3: printf("%d\n", Rank(root, x - 1) + 1); break;
            case 4: printf("%d\n", FindKth(x)); break;
            case 5: printf("%d\n", GetPre(root, x)); break;
            case 6: printf("%d\n", GetSuc(root, x)); break;
        }
    }
    return 0;
}

  

转载于:https://www.cnblogs.com/Juruo1103/p/10281403.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可持久化splay是一种数据结构,它是对splay树进行修改和查询的一种扩展。在传统的splay树中,对树的修改操作会破坏原有的树结构,而可持久化splay树则允许我们对树进行修改、查询,并且可以保存修改后的每个版本的树结构。 在可持久化splay树中,我们不会直接对原树进行修改,而是通过复制每个节点来创建新的版本。这样,每个版本都可以独立地修改和查询,保留了原有版本的结构和状态。每个节点保存了其左子树和右子树的引用,使得可以在不破坏原有版本的情况下进行修改和查询。 为了实现可持久化splay树,我们可以使用一些技巧,比如引用中提到的哨兵节点和假的父节点和孩子节点。这些技巧可以帮助我们处理根节点的旋转和其他操作。 此外,可持久化splay树还可以与其他数据结构相结合,比如引用中提到的可持久化线段树。这种结合可以帮助我们解决更复杂的问题,比如区间修改和区间查询等。 对于可持久化splay树的学习过程,可以按照以下步骤进行: 1. 理解splay树的基本原理和操作,包括旋转、插入、删除和查找等。 2. 学习如何构建可持久化splay树,包括复制节点、更新版本和保存历史版本等。 3. 掌握可持久化splay树的常见应用场景,比如区间修改和区间查询等。 4. 深入了解与可持久化splay树相关的其他数据结构和算法,比如可持久化线段树等。 在解决问题时,可以使用二分法来确定答案,一般称为二分答案。通过对答案进行二分,然后对每个答案进行检查,以确定最终的结果。这种方法可以应用于很多问题,比如引用中提到的在线询问问题。 综上所述,可持久化splay是一种对splay树进行修改和查询的扩展,可以通过复制节点来创建新的版本,并且可以与其他数据结构相结合解决更复杂的问题。学习过程中可以按照一定的步骤进行,并且可以使用二分法来解决一些特定的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [[学习笔记]FHQ-Treap及其可持久化](https://blog.csdn.net/weixin_34283445/article/details/93207491)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [可持久化数据结构学习笔记](https://blog.csdn.net/weixin_30376083/article/details/99902410)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值