数据结构-(堆)PriorityQueue

目录

前言

一、堆是什么?

1.1堆的储存方式

二、堆的创建

2.1:我们用向下调整的方法构建堆:

2.2:建堆的时间复杂度

三:堆的方法

3.1:堆的插入

3.2:堆的删除

四:用堆模拟实现优先级队列

总结


前言

      队列是一种先进先出 (FIFO) 的数据结构 ,但有些情况下, 操作的数据可能带有优先级,一般出队 列时,可能需要优先级高的元素先出队列 ,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。 在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象 。这种数 据结构就是优先级队列 (Priority Queue)

一、堆是什么?

       如果有一个关键码的集合K = {k0k1 k2kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一 个一维数组中,并满足:Ki <= K2i+1 Ki<= K2i+2 (Ki >= K2i+1 Ki >= K2i+2) i = 012…,则称为 小堆(或大 堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
                                           如下图所示是大根堆和小根堆

1.1堆的储存方式

       注意:对于 非完全二叉树,则不适合使用顺序方式进行存储 ,因为为了能够还原二叉树, 空间中必须要存储空节 点,就会导致空间利用率比较低
      将元素存储到数组中后,可以根据二叉树章节的性质 5 对树进行还原。假设 i 为节点在数组中的下标,则有:
如果 i 0 ,则 i 表示的节点为根节点,否则 i 节点的双亲节点为 (i - 1)/2
如果 2 * i + 1 小于节点个数,则节点 i 的左孩子下标为 2 * i + 1 ,否则没有左孩子
如果 2 * i + 2 小于节点个数,则节点 i 的右孩子下标为 2 * i + 2 ,否则没有右孩子

二、堆的创建

代码如下:

public class TestHeap {
    //给一个数组
    private int [] elem;
    //记录当前堆中 有效的数据个数
    public int usedSize;
    //构造方法
    TestHeap(){
        this.elem = new int[10];

    }

    //初始化elem数组
    public void initElem(int [] array){
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
    }
    //创建大根堆
    public void createHeap(){

        for (int parent = (usedSize-1-1)/2;parent >=0 ;parent--){
            siftDown(parent,usedSize);
        }
    }

2.1:我们用向下调整的方法构建堆:

首先,siftDown 方法接受两个参数:parent 表示要调整的父节点的下标,len 表示堆数组的长度。在这个方法中,我们定义了一个 child 变量,表示左孩子的下标,初始值为 2*parent+1

接下来,使用一个循环来进行调整,只要左孩子的下标小于堆数组的长度,就继续进行调整。在循环中,首先进行的是判断右孩子是否存在并且比左孩子的值大,如果成立,就更新 child 为右孩子的下标。

接着,我们比较 elem[child]elem[parent] 的值,如果左右孩子的最大值大于父节点的值,就进行交换,并更新 parentchild 的值,使其向下移动到子节点位置。交换操作使用了 swap 方法,用于交换数组中两个元素的值。

最后,如果左右孩子的最大值小于等于父节点的值,则跳出循环,结束调整。

/*
    向下调整
     */

    private void siftDown(int parent, int len){
        int child = 2*parent+1;

        //至少有左孩子
        while (child < len ){
            //左孩子 和 右孩子 比较大小 如果右孩子的值大 那么
            if(elem[child] < elem[child+1]){
                if(child+1 < len && elem[child] < elem[child]+1){
                    child =child+1;
                }
            }
            //走完上述if语句 证明child下标 一定保存的是左右孩子最大值的下标
            if (elem[child] > elem[parent]) {
                //交换
                swap(child,parent);
                parent = child;
                child = 2*parent +1;
            }
            //如果child<parent
            else {
                break;
            }
        }
    }

    private  void swap(int i , int j){
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }

2.2:建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明 ( 时间复杂度本来看的就是 近似值,多几个节点不影响最终结果)

三:堆的方法

3.1:堆的插入

首先,push 方法接受一个参数 val,表示要插入的值。在方法中,首先判断堆是否已满,如果满了,则将数组 elem 扩容为原来的两倍大小,使用 Arrays.copyOf 方法进行扩容操作。

然后,将要插入的值 val 存储在堆数组的 usedSize 位置上,并将 usedSize 自增。

接下来,调用 siftUp 方法进行向上调整,以保持堆的性质。在 siftUp 方法中,定义了一个 parent 变量,表示子节点的父节点下标,初始值为 (child - 1) / 2

然后,使用一个循环进行调整,只要子节点的下标大于0,就继续进行调整。在循环中,首先比较子节点和父节点的值,如果子节点的值大于父节点的值,就进行交换,并更新 childparent 的值,使其向上移动到父节点位置。交换操作使用了 swap 方法,用于交换数组中两个元素的值。

最后,如果子节点的值小于等于父节点的值,则跳出循环,结束调整。

public void push(int val){
        //满了
        if(isFull()){
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize] = val;

        //向上调整
        siftUp(usedSize);

        usedSize++;
    }


    public boolean isFull(){
        return usedSize == elem.length;
    }
    //向上调整
    public void siftUp(int child) {
        int parent = (child - 1) / 2;
        while (child > 0) {
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                child = parent;
                parent = (child - 1) / 2;
            } else {
               break;
            }
        }
    }

3.2:堆的删除

堆的删除,我们要保证调整后它也是一个大根堆。

首先,pop 方法用于弹出堆顶元素。在方法中,首先判断堆是否为空,如果是,则返回 -1 表示操作失败。

然后,将堆顶元素 elem[0] 的值保存到变量 oldVal 中。接着,将堆顶元素和最后一个元素进行交换,并将 usedSize 自减,相当于删除了堆顶元素。然后,调用 siftDown 方法进行向下调整,以保持堆的性质。

最后,返回被弹出的堆顶元素的值 oldVal

empty 方法用于判断堆是否为空,如果 usedSize 的值为 0,则表示堆为空。

//判断是否为空
    public  int pop(){
        if(empty()){
            return -1;
        }
        int oldVal = elem[0];
        swap(0,usedSize-1);
        usedSize--;
        siftDown(0,usedSize);
        return oldVal;
    }

    public boolean empty(){
        return  usedSize == 0;
    }

四:用堆模拟实现优先级队列

首先我们使用数组存储元素,并提供了 offerpollpeek 方法分别用于插入元素、弹出队列头部元素和查看队列头部元素。

offer 方法中,首先将要插入的元素存储在数组的末尾(下标为 size),然后调用 shiftUp 方法进行向上调整,以保持堆的性质。在调用 shiftUp 方法时,传入的参数是数组末尾元素的下标 size - 1

poll 方法中,首先将队列头部元素保存到变量 oldValue 中,然后将数组末尾元素移动到队列头部,并将队列大小 size 自减。接着,调用 shiftDown 方法进行向下调整,以保持堆的性质。

peek 方法中,直接返回数组的第一个元素(即队列头部元素)。

public class MyPriorityQueue {

private int[] array = new int[100];
private int size = 0;
public void offer(int e) {
array[size++] = e;

shiftUp(size - 1);
}
public int poll() {
int oldValue = array[0];
array[0] = array[--size];
shiftDown(0);
return oldValue;
}
public int peek() {
return array[0];
}
}


总结

希望大家多多支持。给个三连吧

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

所遇皆随风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值