• 「学习笔记」重修左偏树


    左偏树,是一种可并堆,同时也是一棵二叉树,可以快速地完成合并操作。

    dist 的性质

    对于一棵二叉树,我们定义左孩子或右孩子为空的节点为外节点,定义外节点的 distdist11,空节点的 distdist00,不是外节点也不是空节点的 distdist 为其到子树中最近的外节点的距离加一。
    一棵根的 distdistxx 的二叉树至少有 2x12x1 个节点。此性质所有二叉树都有,并非左偏树特有。
    distdist 不是深度,左偏树的深度没有保证,一条向左的链也是左偏树。

    左偏树的性质

    左偏树是一棵二叉树,并且是“左偏”的,即每个节点左儿子的 distdist 都大于等于右儿子的 distdist
    因此,左偏树中每个节点的 distdist 是它右儿子的 distdist 加一。

    变量

    int lson[N], rson[N], fa[N], fat[N];
    ll val[N], dist[N];
    

    lson: 左孩子(左偏);
    rson: 右孩子;
    fa: 父节点;
    fat: 祖先(并查集);
    val: 权值;
    dist: 就是 distdist

    操作

    • 合并

    int merge(int x, int y) { // 合并
    	if (!x || !y) {
    		return x | y;
    	}
    	if (val[x] > val[y] || (val[x] == val[y] && x > y))
    		swap(x, y);
    	rson[x] = merge(rson[x], y);
    	fat[rson[x]] = fa[rson[x]] = x;
    	if (dist[lson[x]] < dist[rson[x]])
    		swap(lson[x], rson[x]);
    	dist[x] = dist[rson[x]] + 1;
    	return x;
    }
    

    if (!x || !y) { return x | y; }
    如果与空节点合并,则直接合并即可
    if (val[x] > val[y] || (val[x] == val[y] && x > y))
    说明这是个小根堆,小元素在上面。
    if (dist[lson[x]] < dist[rson[x]]) swap(lson[x], rson[x]);
    维护左偏的性质。

    • 删除任意一个节点

    左偏树是不支持删除给定权值的点的,只能删除知道点的标号的点。

    void earse(int u) { // 删除任意一点
    	int tmp = merge(lson[u], rson[u]), fu = fa[u];
    	fat[tmp] = fa[tmp] = fu;
    	fat[u] = fa[u] = tmp;
    	lson[fu] == u ? lson[fu] = tmp : rson[fu] = tmp;
    	while (fu) {
    		if (dist[lson[fu]] < dist[rson[fu]])
    			swap(lson[fu], rson[fu]);
    		if (dist[fu] == dist[rson[fu]] + 1)
    			return ;
    		dist[fu] = dist[rson[fu]] + 1;
    		fu = fa[fu];
    	}
    }
    

    int tmp = merge(lson[u], rson[u]), fu = fa[u]; 先将被删节点的左右孩子合并。
    fat[tmp] = fa[tmp] = fu; 处理好父亲和孩子的关系。

    while (fu) {
    	if (dist[lson[fu]] < dist[rson[fu]])
    		swap(lson[fu], rson[fu]);
    	if (dist[fu] == dist[rson[fu]] + 1)
    		return ;
    	dist[fu] = dist[rson[fu]] + 1;
    	fu = fa[fu];
    }
    

    删除点之后可能不符合左偏性质,需要我们向上修改,直到到根节点或符合左偏性质为止。

    • 查询 uu 点所在堆的堆顶元素的标号

    这个操作类似于并查集操作。

    int find(int u) { // 查询堆顶的元素的标号
    	return (fat[u] == u || fat[u] == 0) ? u : fat[u] = find(fat[u]);
    }
    
    • 删除 uu 点所在堆的堆顶元素

    void pop(int u) { // 弹出 u 点所在对的堆顶元素
    	int g = find(u);
    	earse(g);
    }
    
    • 查询 uu 点所在堆的堆顶元素

    ll top(int u) { // 查询 u 点所在堆的堆顶元素
    	int g = find(u);
    	return val[g];
    }
    
    • 建树操作

    int build(int n) { // 建树
    	queue<int> q;
    	for (int i = 1; i <= n; ++ i) {
    		q.push(i);
    	}
    	int x, y, z;
    	while (q.size() > 1) {
    		x = q.front(), q.pop();
    		y = q.front(), q.pop();
    		z = merge(x, y), q.push(z);
    	}
    	return q.front();
    }
    

    模板

    // 左偏树(小根堆)
    struct leftist_tree {
    	int lson[N], rson[N], fa[N], fat[N];
    	ll val[N], dist[N];
    
    	int merge(int x, int y) { // 合并
    		if (!x || !y) {
    			return x | y;
    		}
    		if (val[x] > val[y] || (val[x] == val[y] && x > y))
    			swap(x, y);
    		rson[x] = merge(rson[x], y);
    		fat[rson[x]] = fa[rson[x]] = x;
    		if (dist[lson[x]] < dist[rson[x]])
    			swap(lson[x], rson[x]);
    		dist[x] = dist[rson[x]] + 1;
    		return x;
    	}
    
    	int find(int u) { // 查询堆顶的元素的标号
    		return (fat[u] == u || fat[u] == 0) ? u : fat[u] = find(fat[u]);
    	}
    
    	void earse(int u) { // 删除任意一点
    		int tmp = merge(lson[u], rson[u]), fu = fa[u];
    		fat[tmp] = fa[tmp] = fu;
    		fat[u] = fa[u] = tmp;
    		lson[fu] == u ? lson[fu] = tmp : rson[fu] = tmp;
    		while (fu) {
    			if (dist[lson[fu]] < dist[rson[fu]])
    				swap(lson[fu], rson[fu]);
    			if (dist[fu] == dist[rson[fu]] + 1)
    				return ;
    			dist[fu] = dist[rson[fu]] + 1;
    			fu = fa[fu];
    		}
    	}
    
    	ll top(int u) { // 查询 u 点所在堆的堆顶元素
    		int g = find(u);
    		return val[g];
    	}
    
    	void pop(int u) { // 弹出 u 点所在对的堆顶元素
    		int g = find(u);
    		earse(g);
    	}
    
    	int build(int n) { // 建树
    		queue<int> q;
    		for (int i = 1; i <= n; ++ i) {
    			q.push(i);
    		}
    		int x, y, z;
    		while (q.size() > 1) {
    			x = q.front(), q.pop();
    			y = q.front(), q.pop();
    			z = merge(x, y), q.push(z);
    		}
    		return q.front();
    	}
    };
    

    pb_ds 中的堆

    __gnu_pbds :: priority_queue

    成员函数

    push(): 向堆中压入一个元素,返回该元素位置的迭代器。
    pop(): 将堆顶元素弹出。
    top(): 返回堆顶元素。
    size(): 返回元素个数。
    empty(): 返回是否非空。
    modify(point_iterator, const key): 把迭代器位置的 key 修改为传入的 key,并对底层储存结构进行排序。
    erase(point_iterator): 把迭代器位置的键值从堆中擦除。
    join(__gnu_pbds :: priority_queue &other): 把 other 合并到 *this 并把 other 清空。


    __EOF__

  • 本文作者: yi_fan0305
  • 本文链接: https://www.cnblogs.com/yifan0305/p/17343706.html
  • 关于博主: 四叶草少年
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章下方的【推荐】一下。
  • 相关阅读:
    如何实现一个让面试官拍大腿的防抖节流函数
    敏捷、DevOps和嵌入式系统测试
    一个 SpringBoot 问题就干趴下了?我却凭着这份 PDF 文档吊打面试官
    Web自动化简单的定位元素id,name....实现
    enum PlayerEvents C++ 枚举 案例
    MATLAB中Simulink.findBlocksOfType用法
    高并发技巧-流量聚合和高并发写入处理技巧
    MQ消息队列(五)——RabbitMQ进阶 MQ集群+集群的部署+集群的扩容
    华为ensp防火墙虚拟系统在网络中的部署
    HTML基本骨架与编辑器选择
  • 原文地址:https://www.cnblogs.com/yifan0305/p/17343706.html