【笔记】可删除堆

可删除堆

考虑到没什么人会选择手写普通的堆,所以用优先队列实现就好。

问题:

我们知道,在使用堆或优先队列的时候,我们只能取出堆顶,也就是所维护的最大或最小值。

那么如果我们要从所维护的一个元素里删除一个非最大或最小值呢?

最暴力的做法是将元素一个一个从堆顶弹出,直到弹出我们要删的元素,再将之前所弹出的不需要删的元素放回堆里。

这样做显然是不能接受的。

思考:

我们现在考虑,如果我们选择摆烂无视掉删除操作,继续往后执行任务,似乎也不一定会出错。

如果我们需要删的数是当时堆里第 \(k\) 大的数,现在我们需要取出堆里前 \(k-1\) 大的数。那么我们的操作将不会取出这个要删的数,所取出的这些数也是正确的结果。换而言之,只要我们查询时不需要取出这个要删的数,那么这个要删的数就不会对我们的答案产生影响。

只有当我们取出一个本应删除的数时,答案才会出错。

原理:

对于一个堆,我们真正需要的是它的对顶,只要对顶始终保持正确,对顶底下的数是什么对我们所以到的堆是没有影响的,堆里的数唯一的用处就是选出堆顶和成为堆顶。

那么我们只需要保证一个堆的堆顶的数正确就好了。

实现:

思路类似对顶堆。

我们再准备一个删除堆,删除堆和我们要用的堆一样,我们要用的堆是大根堆或小根堆,这个删除堆就也是大根堆或小根堆。

当我们要从堆里删一个数 \(k\) 时,我们将 \(k\) 放入删除堆的对顶,要用的堆则不需要操作。

当我们查询时,我们在将对顶从要用的堆中取出时,如果发现这个堆顶和删除堆的堆顶一致,那么两个堆就都弹出这个元素,并将这个元素扔掉(不使用这个元素)。重复上述操作,直到我们将该取出的元素都从要用的堆里取出,或者删除堆被删空。

时间复杂度:

删除:每删一个数就相当于向堆里放一个数的复杂度。

询问:询问时的复杂度取决于我们取出堆顶元素是不是要删的数。

都不会太大,基本可以看做是一个常数大点的堆。

代码:

点击查看代码
namespace ksd{ //封装一下。 
    priority_queue<int>q1; //用这个堆存放需要维护的数。
	priority_queue<int>q2; // 用这个堆存放需要删除的数。
	//绝大多数情况没必要额外写以下几个函数 
	void Insert(int x){ //插入一个值为x的数
		q1.push(x);
	} 
	void POP(int x) { // 删除一个值为x的数。 
		q2.push(x);
	} 
	int query() { //查询堆顶元素 
		while(!q1.empty()&&!q2.empty()&&q1.top()==q2.top()) q1.pop(),q2.pop();
		return q1.top();
	}
}

补充:

如果需要删的数需要考虑这个数的键值(或者说下标),那么就将堆维护的元素改成pair,多维护一个键值(我还是更喜欢叫下标)即可。

posted @ 2023-11-14 20:03  int_Hello_world  阅读(80)  评论(0编辑  收藏  举报