数据结构-优先队列和树

8、优先队列和树

优先队列

  • 普通队列:先进先出,后进后出
  • 优先队列:出队顺序和入队顺序无关,和优先级有关
  • 为什么使用优先队列
    • 动态选择优先级最高的执行任务
    • 关键词:动态
  • 优先队列实现接口

在这里插入图片描述

  • 优先队列的实现方式:
    • 普通顺序结构:数组
    • 顺序线性结构:已经排序好的
    • 堆:最大堆或者最小堆

优先队列的实现(基于堆)

public class PriorityQueue <E extends Comparable<E>> implements Queue<E> {
    private MaxHeap<E> maxHeap;

    public PriorityQueue() {
        maxHeap = new MaxHeap<>();
    }

    @Override
    public int getSize() {
        return maxHeap.size();
    }

    @Override
    public boolean isEmpty() {
        return maxHeap.isEmpty();
    }

    @Override
    public void enqueue(E e) {
        maxHeap.add(e);
    }

    @Override
    public E dequeue() {
        return maxHeap.extractMax();
    }

    @Override
    public E getFront() {
        return maxHeap.findMax();
    }
}

优先队列实现方式的时间复杂度对比

在这里插入图片描述

  • 堆的基本结构:二叉堆
  • 满二叉树:每一层均是满的
  • 完全二叉树:当满二叉树在最后一层级容纳不下的话,继续在下面一层从左到右继续添加节点。 简而言之,完全二叉树:把元素顺序排列成树的形状

在这里插入图片描述

  • 二叉堆的性质:
    • 堆中任意个节点的值总是不大于其父节点的值(所有父节点的值大于其孩子节点的值),所以根节点的值最大,所以这样的堆可以定义成最大堆。
    • 上面可以得出最大堆,相反,可以得出最小堆,最小堆就是堆中的任意节点的值总是小于其父节点的值,所以此时最小堆中的根节点值最小。
  • 用数组存储二叉堆(当数组索引从1开始计数)
    • left child(i): 2*i
    • right child(i):2*i+1
    • parent(i):i/2
  • 用数组存储二叉堆(当数组索引从0开始计数)
    • left child(i): 2*i+1
    • right child(i):2*i+2
    • parent(i):(i-1)/ 2

在这里插入图片描述

在这里插入图片描述

最大堆的实现

  • 添加元素:堆中添加元素均是在堆的最后索引位置添加元素

    下面是最大堆中添加元素的详细图解:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 取出元素:只能取出最大元素,所以是索引为0的元素

    • 取出最大元素
    • 互换索引为0和索引为最大的元素
    • 将最后一个元素删除
    • 此时索引为0的元素是最小的,所以需要往下调

    下面是最大堆取出最大元素的详细图解:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

public class MaxHeap<E extends Comparable<E>> {
    private Array<E> data;

    public MaxHeap() {
        data = new Array<>();
    }

    public MaxHeap(int capacity) {
        data =  new Array<>(capacity);
    }

    public int size() {
        return data.getSize();
    }

    public boolean isEmpty() {
        return data.isEmpty();
    }

    private int parent(int index) {
        if(index == 0)
            throw new IllegalArgumentException("index 0 does not have parent");
        return (index-1)/2;
    }

    private int leftChild(int index) {
        return index*2 +1;
    }

    private int rightChild(int index) {
        return index*2 +2;
    }

    //向堆中添加元素
    public void add(E e) {
        data.addLast(e);
        siftUp(data.getSize()-1);
    }
	
    //将节点往上调,在添加元素时运用
    private void siftUp(int index) {
        //当节点的父节点比自身的节点值大时,说明当前节点也比父节点的另一个子节点值大,
        //所以互换当前节点与父节点之后,父节点大于两个子节点。
        while(index>0 && data.get(parent(index)).compareTo(data.get(index))<0) {
            data.swap(index,parent(index));
            index = parent(index);
        }
    }
	
    //找出最大元素的节点
    public E findMax() {
        if(data.getSize() == 0) {
            throw new IllegalArgumentException("heap is empty");
        }

        return data.get(0);
    }

    //取出堆中最大元素
    public E extractMax() {
        E ret = findMax();
        data.swap(0,data.getSize()-1);
        data.removeLast();
        siftDown(0);
        return ret;
    }
	
    //将节点往下调,在删除最大元素节点时运用
    public void siftDown(int index) {
        while(leftChild(index) < data.getSize()) {
            int j = leftChild(index);
            //当右子节点存在并且在比较左右子节点大小时,如果右子节点大于左子节点时
            if(j+1 < data.getSize() && data.get(j+1).compareTo(data.get(j))>0) {
                j = j +1;
            }

            //此时data[j]是leftChild与rightChild中的最大值,
            // 所以使用data[j]与data[index]进行比较
            if(data.get(index).compareTo(data.get(j)) >= 0)
                break;

            data.swap(index,j);
            index = j;
        }
    }
}

最大堆的测试

//测试MaxHeap
int n = 1000000;
MaxHeap<Integer> maxHeap = new MaxHeap<>(n);
Random random = new Random();
for(int i = 0; i<n; i++) {
    maxHeap.add(random.nextInt(Integer.MAX_VALUE));
}
int[] data = new int[n];
for(int i=0; i<n; i++) {
    data[i] = maxHeap.extractMax();
}

for(int i =1; i<n; i++) {
    //最大堆中如果存在前面索引的比后面索引的小,则表明最大堆实现有问题。
    if(data[i-1]<data[i])
        throw new IllegalArgumentException("maxHeap is illegal");
}
System.out.print("Test MaxHeap is Completed");

replace

将堆顶的最大元素返回,并替换成某个元素

//取出最大元素,并替换成元素e
public E repalce(E e) {
    E ret = findMax();
    data.set(0,e);
    siftDown(0);
    return ret;
}

heapify

将任意数组整理成堆的形状

实现

  • 定义一个构造方法,传入数组后,得到的数组就是最大堆了

  • 实现要点,对最后一个叶子节点的父节点开始往前每一个节点进行siftDown操作

  • 依次对22,13,19,17,15进行siftDown操作

  • 最后一个节点的叶子节点是(n-1)/2,其中n-1就是最后一个节点的索引

  • 下图中蓝色的均为叶子节点

在这里插入图片描述

public MaxHeap(E[] arr) {
    data = new Array<>(arr);
    //从最后一个叶子节点的父节点开始进行往下调节,执行siftDown操作
    for(int i = parent(data.getSize()-1);i>=0;i--) {
        siftDown(i);
    }
}

heapify的算法复杂度

在这里插入图片描述

优先队列的经典问题

在N个元素中选出前M个元素

private class Freq implements Comparable<Freq>{
    int e,freq;
    public Freq(int e,int freq) {
        this.e = e;
        this.freq = freq;
    }
    
    //定义优先级,当前元素小于另外一个元素的频率时,返回1
    @Override
    public int compareTo(Freq o) {
        if(this.e < o.freq)
            return 1;
        else if(this.e > o.freq)
            return -1;
        else
            return 0;
    }
}

//在N个元素中选出前M个元素问题,在数组nums中取出前k个频率最大的元素
public List<Integer> topFrequent(int[] nums,int k) {
    TreeMap<Integer,Integer> map = new TreeMap<>();
    for(int num : nums) {
        if(map.containsKey(num)) {
            map.put(num,map.get(num)+1);
        } else {
            map.put(num,1);
        }
    }
    PriorityQueue<Freq> priorityQueue = new PriorityQueue<>();
    for(int key : map.keySet()) {
        //当优先队列的元素个数小于k时
        if(priorityQueue.getSize() < k) {
            priorityQueue.enqueue(new Freq(key,map.get(key)));
        //当优先队列中的最小频率元素priorityQueue.getFront().freq的频率
        //小于当前遍历到映射中的元素频率时
        //优先队列取出一个元素,然后将当前遍历到映射中的元素放入
        } else if(priorityQueue.getFront().freq < map.get(key)) {
            priorityQueue.dequeue();
            priorityQueue.enqueue(new Freq(key,map.get(key)));
        }
    }
    LinkedList<Integer> linkedList = new LinkedList<>();
    while(!priorityQueue.isEmpty()) {
        linkedList.add(priorityQueue.dequeue().e);
    }
    return linkedList;
}

java标准库中的PriorityQueue

  • java的PriorityQueue内部是基于最小堆实现
    • peek():队首元素
    • remove():去除队首元素
    • add():队尾添加元素
    • size():队内元素个数

高级堆

索引堆

二项堆

斐波拉切堆

广义队列

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值