【java多线程】队列系统之PriorityBlockingQueue源码

一、二叉堆

如题,二叉堆是一种基础数据结构

事实上支持的操作也是挺有限的(相对于其他数据结构而言),也就插入,查询,删除这一类

对了这篇文章中讲到的堆都是二叉堆,而不是斜堆,左偏树,斐波那契堆什么的 我都不会啊

二叉堆的表现形式:我们可以使用数组的索引来表示元素在二叉堆中的位置。

从二叉堆中,我们可以得出:

· 元素k的父节点所在的位置为 :k的位置/2]

· 元素k的子节点所在的位置为:2*k的位置和2*k的位置+1

跟据以上规则,我们可以使用二维数组的索引来表示二叉堆。通过二叉堆,我们可以实现插入和删除最大值都达到O(nlogn)的时间复杂度。

对于堆来说,最大元素已经位于根节点,那么删除操作就是移除并返回根节点元素,这时候二叉堆就需要重新排列;当插入新的元素的时候,也需要重新排列二叉堆以满足二叉堆的定义。现在就来看这两种操作。

一.堆的性质

1.堆是一颗完全二叉树

2.堆的顶端一定是“最大”,最小”的,但是要注意一个点,这里的大和小并不是传统意义下的大和小,它是相对于优先级而言的,当然你也可以把优先级定为传统意义下的大小,但一定要牢记这一点,初学者容易把堆的“大小”直接定义为传统意义下的大小,某些题就不是按数字的大小为优先级来进行堆的操作的

(但是为了讲解方便,下文直接把堆的优先级定为传统意义下的大小,所以上面跟没讲有什么区别?)

3.堆一般有两种样子,小根堆和大根堆,分别对应第二个性质中的“堆顶最大”“堆顶最小”,对于大根堆而言,任何一个非根节点,它的优先级都小于堆顶,对于小根堆而言,任何一个非根节点,它的优先级都大于堆顶(这里的根就是堆顶啦qwq)

来一张图了解一下堆(这里是小根堆)(原谅我丑陋无比的图)

不难看出,对于堆的每个子树,它同样也是一个堆(因为是完全二叉树嘛)

二.堆的操作

1.插入

假设你已经有一个堆了,就是上面那个

这个时候你如果想要给它加入一个节点怎么办,比如说0?

先插到堆底(严格意义上来说其实0是在5的左儿子的,图没画好放不下去,不过也不影响)

然后你会发现它比它的父亲小啊,那怎么办?不符合小根堆的性质了啊,那就交换一下他们的位置

交换之后还是发现不符合小根堆的性质,那么再换

 还是不行,再换

好了,这下就符合小根堆的性质了,是不是顺眼很多了?(假的,图越来越丑,原谅我不想再画)

事实上堆的插入就是把新的元素放到堆底,然后检查它是否符合堆的性质,如果符合就丢在那里了,如果不符合,那就和它的父亲交换一下,一直交换交换交换,直到符合堆的性质,那么就插入完成了

Code:

void swap(int &x,int &y){int t=x;x=y;y=t;}//交换函数 
int heap[N];//定义一个数组来存堆
int siz;//堆的大小 
void push(int x){//要插入的数 
    heap[++siz]=x;
    now=siz;
    //插入到堆底 
    while(now){//还没到根节点,还能交换 
        ll nxt=now>>1;//找到它的父亲 
        if(heap[nxt]>heap[now])swap(heap[nxt],heap[now]);//父亲比它大,那就交换 
        else break;//如果比它父亲小,那就代表着插入完成了 
        now=nxt;//交换 
    }
    return; 
}
View Code

2.删除

把0插入完以后,忽然你看这个0不爽了,本来都是正整数,怎么就混进来你这个0?

于是这时候你就想把它删除掉

怎么删除?在删除的过程中还是要维护小根堆的性质

如果你直接删掉了,那就没有堆顶了,这个堆就直接乱了,所以我们要保证删除后这一整个堆还是个完好的小根堆

首先在它的两个儿子里面,找一个比较小的,和它交换一下,但是还是没法删除,因为下方还有节点,那就继续交换

还是不行,再换

再换

好了,这个碍眼的东西终于的下面终于没有节点了,这时候直接把它扔掉就好了

这样我们就完成了删除操作,但是在实际的代码操作中,并不是这样进行删除操作的,有一定的微调,代码中是直接把堆顶和堆底交换一下,然后把交换后的堆顶不断与它的子节点交换,直到这个堆重新符合堆性质(但是上面的方式好理解啊)

手写堆的删除支持任意一个节点的删除,不过STL只支持堆顶删除,STL的我们后面再讲

Code:

void pop(){
    swap(heap[siz],heap[1]);siz--;//交换堆顶和堆底,然后直接弹掉堆底 
    int now=1;
    while((now<<1)<=siz){//对该节点进行向下交换的操作 
        int nxt=now<<1;//找出当前节点的左儿子 
        if(nxt+1<=siz&&heap[nxt+1]<heap[nxt])nxt++;//看看是要左儿子还是右儿子跟它换 
        if(heap[nxt]<heap[now])swap(heap[now],heap[nxt]);//如果不符合堆性质就换 
        else break;//否则就完成了 
        now=nxt;//往下一层继续向下交换 
    }
}
View Code

3.查询

因为我们一直维护着这个堆使它满足堆性质,而堆最简单的查询就是查询优先级最低/最高的元素,对于我们维护的这个堆heap,它的优先级最低/最高的元素就是堆顶,所以查询之后输出heap[1]就好了

一般的题目里面查询操作是和删除操作捆绑的,查询完后顺便就删掉了,这个主要因题而异

参考:https://www.cnblogs.com/yangecnu/p/Introduce-Priority-Queue-And-Heap-Sort.html

https://www.cnblogs.com/henry-1202/p/9307927.html

猜你喜欢

转载自www.cnblogs.com/shangxiaofei/p/10642012.html
今日推荐