数据结构(5) treap

news2024/9/23 11:17:05

活动 - AcWing

参考—《算法竞赛进阶指南》-lyd

目录

一、概述

二、具体操作详解

1.常见操作

 2.结构定义

3.操作基础函数

(1)pushup

 (2) 获得一个新节点

(3)左旋右旋

(4)建树

(5)插入操作(如插入k)

(6)删除操作

(7) 查询操作

实际应用


 

一、概述

满足bst性质的二叉查找树不是唯一的。我们可以通过改变二叉查找树的形态,使得左右子树大小达到平衡,从而使整棵树的深度维持在logn级别。

改变方式分为“左旋”和“右旋”。具体方式请参考数据结构教材。

Treap是tree和heap的合成词。如果节点值是随机的,那么构成的树深度平均为logn级别。

Treap在插入新节点时,给该节点一个随机的权值。然后像二叉堆的插入过程一样,自底向上检查,当某个节点不满足大根堆性质的时候就执行单旋转,使得该点与其父节点的关系发生对换。

特别地,对于删除操作,我们可以直接把删除节点向下旋转成叶节点,然后直接删掉。

总之,Treap通过适当的单旋转,维持了bst性质,同时还使得节点上随机生成的额外权值满足大根堆性质。所有操作都是logN级别。

二、具体操作详解

1.常见操作

 2.结构定义

int n;
struct Node{
    int l,r;
    int key,val;
    int cnt,size;
}tr[N];

int root,idx;

l,r表示左右子节点编号。key表示节点的值,val表示随机权值。cnt表示当前的key出现了多少次,size表示这整个子树有多少个节点。

cnt和size都是为了满足根据排名求值 和根据值求排名这两个操作设立的

3.操作基础函数

(1)pushup

根据子节点计算size。为了3 4操作

void pushup(int p)
{
    tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+tr[p].cnt;
}

 (2) 获得一个新节点

int get_node(int key)
{
    tr[++idx].key=key;
    tr[idx].val=rand();
    tr[idx].cnt=tr[idx].size=1;
    return idx;
}

(3)左旋右旋

注意引用

void zig(int &p)//右旋
{
    int q=tr[p].l;
    tr[p].l=tr[q].r,tr[q].r=p,p=q;
    pushup(tr[p].r),pushup(p);
}

void zag(int &p)
{
    int q=tr[p].r;
    tr[p].r=tr[q].l,tr[q].l=p,p=q;
    pushup(tr[p].l),pushup(p);
}

(4)建树

安排两个哨兵。哨兵先后顺序不要反了。

void build()
{
    get_node(-INF),get_node(INF);
    root=1,tr[root].r=2;
    pushup(root);
    
    if(tr[1].val<tr[2].val) zag(root);
}

(5)插入操作(如插入k)

如果p为空,就创建一个新节点。

如果当前key==k,则该节点cnt++

如果大于k,则在左子树插入,回溯时根据需要右旋

如果小于k,则在右子树插入,回溯时根据需要左旋

最后对每个节点,由于插入改变了子树信息,所以要pushup当前节点

void insert(int &p,int key)
{
    if(!p) p=get_node(key);
    else if(tr[p].key==key) tr[p].cnt++;
    else if(tr[p].key>key)
    {
        insert(tr[p].l,key);
        if(tr[tr[p].l].val>tr[tr[p].r].val) zig(p);
    }
    else
    {
        insert(tr[p].r,key);
        if(tr[tr[p].r].val>tr[tr[p].l].val) zag(p);
    }
    pushup(p);
}

(6)删除操作

如果空节点,则无意义,直接return

如果当前节点key==k:

        如果cnt>1,直接cnt--即可

        如果不是叶节点:

                如果没有右子树,或者需要右旋:就右旋,然后递归在p的右子树上删除

                对应的如果没有左子树或者需要左旋:就左旋,然后递归左子树

        剩下情况为叶节点:直接p=0删除即可

如果当前节点key>k 递归左子树

else 递归右子树

最后依然需要记得pushup!!!!

void remove(int &p,int key)
{
    if(!p) return;
    if(tr[p].key==key)
    {
        if(tr[p].cnt>1) tr[p].cnt--;
        else if(tr[p].l||tr[p].r)
        {
            if(!tr[p].r||tr[tr[p].l].val>tr[tr[p].r].val)
            {
                zig(p);
                remove(tr[p].r,key);
            }
            else
            {
                zag(p);
                remove(tr[p].l,key);
            }
        }
        else p=0;
    }
    else if(tr[p].key>key) remove(tr[p].l,key);
    else remove(tr[p].r,key);
    pushup(p);
}

(7) 查询操作

int get_rank_by_key(int p,int key)
{
    if(!p) return 0;
    if(tr[p].key==key) return tr[tr[p].l].size+1;
    if(tr[p].key>key) return get_rank_by_key(tr[p].l,key);
    return tr[tr[p].l].size+tr[p].cnt+get_rank_by_key(tr[p].r,key);
}

int get_key_by_rank(int p,int rank)
{
    if(!p) return INF;
    if(tr[tr[p].l].size>=rank) return get_key_by_rank(tr[p].l,rank);
    if(tr[tr[p].l].size+tr[p].cnt>=rank) return tr[p].key;
    return get_key_by_rank(tr[p].r,rank-tr[tr[p].l].size-tr[p].cnt);
}

int get_prev(int p,int key)
{
    if(!p) return -INF;
    if(tr[p].key>=key) return get_prev(tr[p].l,key);
    return max(tr[p].key,get_prev(tr[p].r,key));
    
}
int get_next(int p, int key)    // 找到严格大于key的最小数
{
    if (!p) return INF;
    if (tr[p].key <= key) return get_next(tr[p].r, key);
    return min(tr[p].key, get_next(tr[p].l, key));
}

完整代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N =1e5+10,INF=1e8;

int n;
struct Node{
    int l,r;
    int key,val;
    int cnt,size;
}tr[N];

int root,idx;

void pushup(int p)
{
    tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+tr[p].cnt;
}

int get_node(int key)
{
    tr[++idx].key=key;
    tr[idx].val=rand();
    tr[idx].cnt=tr[idx].size=1;
    return idx;
}

void zig(int &p)//右旋
{
    int q=tr[p].l;
    tr[p].l=tr[q].r,tr[q].r=p,p=q;
    pushup(tr[p].r),pushup(p);
}

void zag(int &p)
{
    int q=tr[p].r;
    tr[p].r=tr[q].l,tr[q].l=p,p=q;
    pushup(tr[p].l),pushup(p);
}

void build()
{
    get_node(-INF),get_node(INF);
    root=1,tr[root].r=2;
    pushup(root);
    
    if(tr[1].val<tr[2].val) zag(root);
}

void insert(int &p,int key)
{
    if(!p) p=get_node(key);
    else if(tr[p].key==key) tr[p].cnt++;
    else if(tr[p].key>key)
    {
        insert(tr[p].l,key);
        if(tr[tr[p].l].val>tr[tr[p].r].val) zig(p);
    }
    else
    {
        insert(tr[p].r,key);
        if(tr[tr[p].r].val>tr[tr[p].l].val) zag(p);
    }
    pushup(p);
}

void remove(int &p,int key)
{
    if(!p) return;
    if(tr[p].key==key)
    {
        if(tr[p].cnt>1) tr[p].cnt--;
        else if(tr[p].l||tr[p].r)
        {
            if(!tr[p].r||tr[tr[p].l].val>tr[tr[p].r].val)
            {
                zig(p);
                remove(tr[p].r,key);
            }
            else
            {
                zag(p);
                remove(tr[p].l,key);
            }
        }
        else p=0;
    }
    else if(tr[p].key>key) remove(tr[p].l,key);
    else remove(tr[p].r,key);
    pushup(p);
}

int get_rank_by_key(int p,int key)
{
    if(!p) return 0;
    if(tr[p].key==key) return tr[tr[p].l].size+1;
    if(tr[p].key>key) return get_rank_by_key(tr[p].l,key);
    return tr[tr[p].l].size+tr[p].cnt+get_rank_by_key(tr[p].r,key);
}

int get_key_by_rank(int p,int rank)
{
    if(!p) return INF;
    if(tr[tr[p].l].size>=rank) return get_key_by_rank(tr[p].l,rank);
    if(tr[tr[p].l].size+tr[p].cnt>=rank) return tr[p].key;
    return get_key_by_rank(tr[p].r,rank-tr[tr[p].l].size-tr[p].cnt);
}

int get_prev(int p,int key)
{
    if(!p) return -INF;
    if(tr[p].key>=key) return get_prev(tr[p].l,key);
    return max(tr[p].key,get_prev(tr[p].r,key));
    
}
int get_next(int p, int key)    // 找到严格大于key的最小数
{
    if (!p) return INF;
    if (tr[p].key <= key) return get_next(tr[p].r, key);
    return min(tr[p].key, get_next(tr[p].l, key));
}

int main()
{
    build();
    cin>>n;
    while(n--)
    {
        int op,x;
        cin>>op>>x;
        if(op==1)   insert(root,x);
        else if(op==2)  remove(root,x);
        else if(op==3)  cout<<get_rank_by_key(root,x)-1<<endl;
        else if(op==4)  cout<<get_key_by_rank(root,x+1)<<endl; 
        else if(op==5)  cout<<get_prev(root,x)<<endl;
        else
        {
            cout<<get_next(root,x)<<endl;
        }
    }
    
    return 0;
}

实际应用

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;

const int N =33010,INF=1e8;
typedef long long LL;

int n;
struct Node{
    int l,r;
    int key,val;
}tr[N];

int root,idx;

int get_node(int key)
{
    tr[++idx].key=key;
    tr[idx].val=rand();
    return idx;
}

void zig(int &p)
{
    int q=tr[p].l;
    tr[p].l=tr[q].r,tr[q].r=p,p=q;
}

void zag(int &p)
{
    int q=tr[p].r;
    tr[p].r=tr[q].l,tr[q].l=p,p=q;

}

void build()
{
    get_node(-INF),get_node(INF);
    root=1,tr[root].r=2;
    //if(tr[root].val<tr[tr[root].r].val) zag(root);
}

void insert(int &p,int key)
{
    if(!p) p=get_node(key);
    else if(tr[p].key==key) return;
    else if(tr[p].key>key)
    {
        insert(tr[p].l,key);
        if(tr[tr[p].l].val>tr[p].val) zig(p);
    }
    else
    {
        insert(tr[p].r,key);
        if(tr[tr[p].r].val>tr[p].val) zag(p);
    }

}

int get_prev(int p,int key)
{
    if(!p) return -INF;
    if(tr[p].key>key) return get_prev(tr[p].l,key);
    return max(tr[p].key,get_prev(tr[p].r,key));
}

int get_next(int p,int key)
{
    if(!p) return INF;
    if(tr[p].key<key) return get_next(tr[p].r,key);
    return min(tr[p].key,get_next(tr[p].l,key));
}

int main()
{
    build();
    cin>>n;

    LL res=0;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        if(i==1) res+=x;
        else res+= min(x - get_prev(root,x),get_next(root,x)-x);
        insert(root,x);
    }
    cout<<res;
    return 0;
}

作者:yankai
链接:https://www.acwing.com/activity/content/code/content/5279849/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/171529.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

centos上面用docker 安装nacos 1.4.1

方式一&#xff1a; 下载nacos本地文件 1. 去官网下载GitHub - alibaba/nacos: an easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications.2.上传到Linux服务器中cd /usr/uploadtar -zxvf nacos-serv…

二、QML工程之初始工程代码分析

接着上一讲&#xff0c;当建立完工程之后&#xff0c;IDE 会呈现如下的界面下面的代码是main.cpp&#xff0c;工程起始运行的代码段&#xff0c;具体的函数说明都在代码段里面进行了标注。#include <QGuiApplication> //主要是ui进程运行头函数&#xff0c;包含事件循环&…

【云原生】k8s图形化管理工具之rancher

内容预知 前言 1. Rancher的相关知识 1.1 Rancher的简介 1.2 Rancher与k8s的关系及区别 1.3 Rancher具有的优势 2. Rancher的安装部署 2.1 实验环境与部署图分配 2.2 具体的部署操作 &#xff08;1&#xff09;在 rancher 节点下载 rancher 镜像 &#xff08;2&#xff…

【Spring(七)】详细了解Spring的核心容器

文章目录前言容器总结前言 前面我们完成bean以及依赖注入的相关学习&#xff0c;现在我们进入到核心容器的最后一块内容了&#xff0c;也就是与容器相关操作的学习&#x1f388;&#x1f388;。 容器 前边我们获取容器是这样获取的&#x1f447;&#x1f447;。 这只是获取容器…

Vue CLI脚手架

1、Vue的开发模式 目前我们使用vue的过程都是在html文件中&#xff0c;通过template编写自己的模板、脚本逻辑、样式等。 但是随着项目越来越复杂&#xff0c;我们会采用组件化的方式来进行开发&#xff1a; 这就意味着每个组件都会有自己的模板、脚本逻辑、样式等&#xff…

凌恩生物报告升级,科研美图助力群落互作机制研究

2022年&#xff0c;在多位技术支持与生信工程师的通力合作下凌恩生物报告升级重磅升级&#xff01;扩增子分析流程项目从60到120&#xff0c;可谓是加量不加价的超值更新了&#xff01;结题报告的结果图片可直接用于文章发表&#xff0c;快一起来看看&#xff01;小小的举个例子…

pytorch【Conv2d参数介绍】

def __init__(self,in_channels: int,out_channels: int,kernel_size: _size_2_t,stride: _size_2_t 1,padding: _size_2_t 0,dilation: _size_2_t 1,groups: int 1,bias: bool True,padding_mode: str zeros # TODO: refine this type):in_channels&#xff1a;网络输入…

《Linux性能优化实战》学习笔记 Day03

04 | 零拷贝&#xff1a;如何高效地传输文件&#xff1f; 原文摘抄 文件拷贝 上下文切换的成本并不小&#xff0c;虽然一次切换仅消耗几十纳秒到几微秒&#xff0c;但高并发服务会放大这类时间的消耗。 每周期中的 4 次内存拷贝&#xff0c;其中与物理设备相关的 2 次拷贝是…

再学C语言37:函数、数组和指针

编写一个对数组进行操作的函数&#xff0c;并进行调用 一般形式举例&#xff1a; int sum(int * arr, int n); // 函数原型// 第一个参数把数组地址和类型信息传递给函数// 第二个参数把数组中元素的个数传递给函数int main(void) {...int total;total sum(array_t, 10);...…

分分钟你也能写一个自己需要的 Chrome 扩展程序

废话 对于chrome浏览器想必大家大不陌生了&#xff0c;里面的扩展程序&#xff08;本文后面称插件&#xff09;&#xff0c;多多少少也都用过吧&#xff0c;毕竟可以辅助自己的日常工作&#xff0c;娱乐等等&#xff0c;添加完之后呢&#xff0c;就会多出一些操作按钮&#xf…

年度总结-你觉得什么叫生活?

陈老老老板&#x1f9b8;&#x1f468;‍&#x1f4bb;本文专栏&#xff1a;生活&#xff08;主要讲一下自己生活相关的内容&#xff09;&#x1f468;‍&#x1f4bb;本文简述&#xff1a;生活就像海洋,只有意志坚强的人,才能到达彼岸。&#x1f468;‍&#x1f4bb;上一篇文章…

Linux常用命令——tr命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) tr 将字符进行替换压缩和删除 补充说明 tr命令可以对来自标准输入的字符进行替换、压缩和删除。它可以将一组字符变成另一组字符&#xff0c;经常用来编写优美的单行命令&#xff0c;作用很强大。 语法 tr(选…

云逗猫——直播弹幕控制机械臂逗猫棒

这个东西思路是很简单的。简单来说在直播画面用文字跟观看者约定一些弹幕指令&#xff0c;然后用爬虫爬取直播弹幕&#xff0c;当爬到弹幕是约定的指令时&#xff0c;通过串口给舵机控制板发送信号&#xff0c;控制板控制舵机转动&#xff0c;机械臂就会做相应的动作。 猫 两…

Lesson2无头单向非循环链表(中)

1.链表 1.1链表的概念及结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。 1.2链表的分类 实际中链表的结构非常多样&#xff0c;以下情况组合起来就有8种链表结构&#xff1a; 1.…

【SAP Hana】X档案:SAP HANA 数据库基础知识

SAP HANA 数据库基础知识1、基本规则&#xff08;1&#xff09;注释&#xff08;2&#xff09;标识符&#xff08;3&#xff09;引号&#xff08;4&#xff09;保留字2、数据类型&#xff08;1&#xff09;日期时间类型&#xff08;2&#xff09;数字类型&#xff08;3&#xf…

SNMP简单网络管理协议

随着网络的规模越来越庞大&#xff0c;网络中的设备种类繁多&#xff0c;如何对越来越复杂的网络进行有效的管理&#xff0c;从而提供高质量的网络服务&#xff0c;已成为网络管理所面临的巨大挑战。网络的管理和运维手段多样&#xff0c;下面将对几种常见的网管与运维手段展开…

[leetcode.29]两数相除,位运算虽好,不要满眼是她

题目如下 不允许用乘除法&#xff0c;但是我们可以用加减法和位运算。。。不过这里不要用位运算&#xff0c;比如说你要是想用补码交替除法&#xff0c;你根本无法获得移动几位&#xff08;移动31位&#xff1f;太鬼畜了吧&#xff09; 所以说单纯的除法部分&#xff0c;我们可…

测试开发 | 实战演练基于加密接口测试测试用例设计

image1080594 76.4 KB 如果接口测试仅仅只是掌握一些requests或者其他一些功能强大的库的用法&#xff0c;是远远不够的&#xff0c;还需要具有根据公司的业务以及需求去定制化一个接口自动化测试框架能力。所以在这个部分&#xff0c;会主要介绍接口测试用例分析以及通用的流程…

从零开始 verilog 以太网交换机(二)MAC接收控制器的设计与实现

从零开始 verilog 以太网交换机&#xff08;二&#xff09;MAC接收控制器的设计与实现 &#x1f508;声明&#xff1a; &#x1f603;博主主页&#xff1a;王_嘻嘻的CSDN主页 &#x1f9e8; 从零开始 verilog 以太网交换机系列专栏&#xff1a;点击这里 &#x1f511;未经作者允…

plt绘制点线图 点(marker)过密的解决办法

设置 markevery10 plt.plot(x, y, markero, markevery10) import matplotlib.pyplot as plt import numpy as npxnp.arange(0,2*np.pi,0.01) ynp.sin(x)fig, ax plt.subplots(constrained_layoutTrue)plt.title(markevery10)ax.plot(x, y, markero, markevery50, mfcr,mecr)…