【JAVA】用大根堆模拟实现优先级队列

目录

1.优先级队列

1.1概念

2.优先级队列的模拟实现

2.1堆的概念

2.2堆的存储方式

2.3堆的创建(以大根堆为例)

2.3.1堆的元素上浮操作(以大根堆为例)

2.3.2堆的元素下沉操作(以大根堆为例)

2.3.3堆的创建(以大根堆为例)

2.3.4建堆的时间复杂度

2.4堆的插入与删除

2.4.1堆的插入

2.4.2堆的删除

2.5优先级队列的模拟实现

                


1.优先级队列

1.1概念

队列:是一种先进先出(FIFO)的数据结构。

但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如 果有来电,那么系统应该优先处理打进来的电话。这样,我们就引入了优先级队列 这种数据结构

优先级队列(priority queue): 是0个或多个元素的集合,每个元素都有一个优先权,对优先级队列执行的操作有(1)查找(2)插入一个新元素 (3)删除 。一般情况下,查找操作用来搜索优先权最大的元素,删除操作用来删除该元素 。对于优先权相同的元素,可按先进先出次序处理或按任意优先权进行。

2.优先级队列的模拟实现

JDK1.8 中的 PriorityQueue 底层使用了 这种数据结构 ,而堆实际就是在完全二叉树的基础上进行了一些调整。

2.1堆的概念

如果有一个 关键码的集合 K = {k0 k1 k2 kn-1} ,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组
若: Ki <= K2i+1 Ki<= K2i+2 , i = 0 1 2… ,则 称为 小堆
若:Ki >= K2i+1 Ki >= K2i+2, i = 0 1 2… ,则称为 大堆
将根节点最大的堆叫做最大堆或大根堆(图1),根节点最小的堆叫做最小堆或小根堆(图2)

 堆的性质:

1.从结构上来说,堆总是一颗完全二叉树,若根节点索引从0开始编号,某个节点编号为k的话

左孩子索引:2k+1

右孩子索引:2k+2

父节点索引:(k-1)/2

2.从节点的值来说,最大堆的根节点一定>=左右子树的所有节点值,最小堆的根节点一定<=左右子树的所有节点值。

2.2堆的存储方式

堆是一颗完全二叉树,因此可以采用顺序方式进行存储。

注意:对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树, 空间中必须要存储空节 点,就会导致空间利用率比较低。

2.3堆的创建(以大根堆为例)

想要创建一个堆,我们需要先了解堆的两个操作:堆得元素上浮操作和堆的元素下沉操作。

先定义三个方法,用来获取索引为k的节点的父节点,左孩子节点和右孩子节点。

    //获取父节点
    private int parent(int k){
        return (k-1)>>1;
    }
    //左孩子节点
    private int leftChild(int k){
        return (k<<1)+1;
    }
    //右孩子节点
    private int rightChild(int k){
        return (k<<1)+2;
    }

2.3.1堆的元素上浮操作(以大根堆为例)

以数组[8,7,5,4,3,6]为例,在结构上是一个完全二叉树,在数值上,可以看到除了最后一个元素,它是一个大根堆,而加上了这个元素,它就不是一个大根堆了。想要让这个元素到正确的位置,就引入了元素上浮操作

1.当这个元素的值大于它的父节点的值时,两者交换

2.重复上述操作

代码:

    //元素上浮操作
    private void siftUp(int k) {
        while (k > 0 && data.get(k) > data.get(parent(k))) {
            swap(k, parent(k));
            k = parent(k);
        }
    }
    //swap方法实现
    public void swap(int i,int j){
        int tep=data.get(i);
        data.set(i,data.get(j));
        data.set(j,tep);
    }

2.3.2堆的元素下沉操作(以大根堆为例)

以数组[1,7,8,6,5,2,3]为例,仔细观察下图后发现:根节点的左右子树已经完全满足堆的性质,因此只需将根节点向下调整好即可。即元素的下沉操作

 1.将根节点标记为parent,先比较左右孩子的大小,将较大值标记为child。

若child>parent,则交换两个节点;若child<parent,则不交换。

 2.重复上述步骤即可。

 代码:

    //元素下沉操作
    public void siftDown(int k) {
        //首先保证有子树
        while (leftChild(k) < size) {
            //判断是否存在右子树,且右子树的值大于左子树
            int j = leftChild(k);
            if (j + 1 < size && data.get(j + 1) > data.get(j)) {
                j = j + 1;
            }
            //此时j索引一定是左右子树的最大值
            if (data.get(k) >= data.get(j)) {
                break;
            } else {
                swap(k, j);
                k = j;
            }
        }
    }

2.3.3堆的创建(以大根堆为例)

给定一个任意整型数组,将其调整为最大堆或最小堆。

 这里以数组[1,5,3,8,7,6]为例,将其调整为最大堆。

核心思路:

1.从最后一个非叶子节点开始进行元素下沉操作,不断向根节点倒着向下调整,慢慢的将左右子树调整为最大堆。

2.当最后走到根节点时,只要下沉根节点之后,整棵树就满足了最大堆。

详细实现:

1.先将值为3的节点进行siftDown操作;

2.再将值为5的节点进行siftDown操作;

1.最后将值为1的节点进行siftDown操作,得到一个大根堆。

 代码:

    //将任意整型数组调整为最大堆
    public MaxHeap(int[] arr){
        this.data=new ArrayList<>(arr.length);
        //先依次将arr中每个元素放入堆中
        for(int i:arr){
            data.add(i);
            size++;
        }
        //从当前完全二叉树的最后一个非叶子节点开始向下调整,使得每个子树为大根堆
        for(int i=parent(size-1);i>=0;i--){
            siftDown(i);
        }
    }

2.3.4建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明

第一层:2^0个节点,需要向下移动h-1层;

第二层:2^1个节点,需要向下移动h-2层;

第三层:2^2个节点,需要向下移动h-3层;

.......

第h-1层:2^h-2个节点,需要向下移动1层;

则需要移动的总步数为:

T(n)=2^0*(h-1)+2^1*(h-2)+2^2*(h-3)+......2^(h-2)*1 ①

2T(n)=2^1*(h-1)+2^2*(h-2)+2^3*(h-3)+......2^(h-1)*1  ②

②-①错位相减得:

T(n)=1-h+2^1+2^2+2^3+......+2^h+2^(h-1)

 T(n)=2^h-1-h

n=2^h-1     h=log(n+1)

T(n)=n-log(n+1)=n

因此,建堆的时间复杂度为n。

2.4堆的插入与删除

2.4.1堆的插入

堆的插入共需要两个步骤:

1.先将元素放入到底层空间中(空间不够时需要扩容)

2.将最后新插入的节点向上调整,直到满足堆的性质

代码:

    //向最大堆中添加元素
    public void add(int val){
        //先将元素尾插进数组,使其结构为完全二叉树
        this.data.add(val);
        size++;
        siftUp(size-1);
    }

 

2.4.2堆的删除

堆的删除一定是删除堆顶元素

具体如下:

1.将堆顶元素与堆中最后一个元素交换

2.将堆中有效数据个数减少一个

3.对堆顶元素进行向下调整

代码:

    //从最大堆中删除最大值
    public int extractMax(){
        if(data.isEmpty()){
            throw new NoSuchElementException("heap is empty!");
        }
        int val=data.get(0);
        data.set(0,data.get(size-1));
        data.remove(size-1);
        size--;
        siftDown(0);
        return val;
    }

2.5优先级队列的模拟实现

public class PriorityQueue implements MyQueue<Integer> {
    // 使用最大堆作为存储元素的集合
    private MaxHeap heap = new MaxHeap();
    @Override
    public void offer(Integer val) {
        heap.add(val);
    }

    @Override
    public Integer poll() {
        return heap.extractMax();
    }

    @Override
    public Integer peek() {
        return heap.peekMax();
    }
}

                

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值