(九) 堆和优先队列

偶尔善良 提交于 2019-11-27 07:15:52

优先队列

什么是优先队列

普通队列: 先进先出, 后进后出

优先队列: 出队顺序和入队顺序无关, 和优先级相关

堆的基本结构

  • 二叉堆是一棵二叉树
  • 二叉堆是一棵完全二叉树( 完全二叉树就是把元素一层一层的排列, 直到排不下为止)
  • 堆的某个节点值总是不大于其父节点的值(最大堆)

使用数组存储堆

因为堆是完全二叉树, 所以可以把堆一层一层的放入数组. 使用数组实现二叉结构

此时计算节点如下:

  • parent(i) = i/2;
  • left child(i) = i * 2;
  • right child(i) = i * 2 + 1;

堆的使用操作

向堆中添加元素和shiftUp

shiftUp, 就是将一个元素, 上浮到符合堆性质的位置.

取出堆的最大元素和shiftDown

shiftDown, 和shiftUp类似, 就是把一个元素下浮到指定位置

replace 替换操作

replace: 取出最大元素后, 放入一个新元素

实现: 可以先extractMax, 再add, 两次O(logn)的操作

实现: 可以直接将堆顶元素替换以后shifWown, 一次O(logn)的操作

heapify 操作

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

实现: 从第一个非子元素的地方, 上浮元素。

堆的实现代码

package pers.jssd.heap;

/**
 * 最大堆
 *
 * @author jssdjing@gmail.com
 */
public class MaxHeap<E extends Comparable<E>> {
    private Array<E> data;

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

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

    public MaxHeap(E[] arr) {
        data = new Array<E>(arr);
        for (int i = getParent(data.getSize() - 1); i >= 0; i--) {
            shiftDown(i);
        }
    }

    /**
     * 取得堆中所含元素的大小
     *
     * @return 返回堆中元素的多少
     */
    public int getSize() {
        return data.getSize();
    }

    /**
     * 判断堆是否为空
     *
     * @return true则为空, false则不为空
     */
    public boolean isEmpty() {
        return data.isEmpty();
    }

    /**
     * 取得父元素的位置
     *
     * @param i 子孩子的位置
     * @return 返回此子孩子的父元素的位置
     */
    private int getParent(int i) {
        if (i == 0) {
            throw new IllegalArgumentException("can not get parent, the heap is empty");
        }
        return (i - 1) / 2;
    }

    /**
     * 取得左孩子
     *
     * @param i 父元素的位置
     * @return 返回左孩子的位置
     */
    private int getLeftChild(int i) {
        return i * 2 + 1;
    }

    /**
     * 取得右孩子
     *
     * @param i 父元素位置
     * @return 返回右孩子的位置
     */
    private int getRightChild(int i) {
        return i * 2 + 2;
    }

    /**
     * 添加元素
     *
     * @param e 要添加的元素
     */
    public void addData(E e) {
        data.addLast(e);
        shiftUp(data.getSize() - 1);
    }

    /**
     * 将k位置的元素上浮到指定位置
     *
     * @param k 上浮的元素位置
     */
    private void shiftUp(int k) {
        while (k > 0 && data.get(k).compareTo(data.get(getParent(k))) > 0) {
            data.swap(k, getParent(k));
            k = getParent(k);
        }
    }

    /**
     * 取得最大元素
     *
     * @return 返回取得的最大元素
     */
    public E getMax() {
        if (data.isEmpty()) {
            throw new IllegalArgumentException("can not get max value when the heap is empty");
        }
        return data.get(0);
    }

    /**
     * 取出最大元素. 并删除
     *
     * @return 返回取出的最大元素
     */
    public E extractMax() {
        E ref = getMax();

        data.swap(0, data.getSize() - 1);
        data.removeLast();
        shiftDown(0);

        return ref;
    }

    /**
     * 下浮元素
     *
     * @param k 将k位置的元素下浮到指定位置
     */
    private void shiftDown(int k) {
        while (getLeftChild(k) < data.getSize()) {
            int i = getLeftChild(k);
            if (i + 1 < data.getSize() && data.get(i).compareTo(data.get(i + 1)) < 0) {
                i = i + 1;
            }
            if (data.get(k).compareTo(data.get(i)) >= 0) {
                break;
            }
            data.swap(k, i);
            k = i;
        }
    }

    /**
     * 将最大元素取出, 并添加一个元素
     *
     * @param e 将要替换的元素
     * @return 返回被替换的元素
     */
    public E replace(E e) {
        E ref = getMax();
        data.set(0, e);
        shiftDown(0);

        return ref;
    }

}

优先队列的实现(使用堆)

package pers.jssd.priorityqueue;

import pers.jssd.heap.MaxHeap;

/**
 * @author jssdjing@gmail.com
 */
public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
    
    private MaxHeap<E> heap;
    
    public PriorityQueue() {
        heap = new MaxHeap<>();
    }
    
    @Override
    public void enqueue(E e) {
        heap.addData(e);
    }

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

    @Override
    public E getFront() {
        return heap.getMax();
    }

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

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

leetCode例题

第347号问题:https://leetcode-cn.com/problems/top-k-frequent-elements/ 前k个高频元素

package pers.jssd.leetcode;

import pers.jssd.heap.MaxHeap;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 第347号问题, 前k个高频元素
 */
class Solution {

    /**
     * 一个内部类, 含有元素和元素频率.
     */
    private class Fre implements Comparable<Fre> {
        int e;
        int fre;

        public Fre(int e, int fre) {
            this.e = e;
            this.fre = fre;
        }

        @Override
        public int compareTo(Fre another) {
            return Integer.compare(another.fre, this.fre);
        }
    }

    public List<Integer> topKFrequent(int[] nums, int k) {
        // 统计nums的数字频率到map中
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            if (map.containsKey(num)) {
                map.put(num, map.get(num) + 1);
            } else {
                map.put(num, 1);
            }
        }
        System.out.println("map = " + map);

        // 维护具有前k个元素的优先队列
        MaxHeap<Fre> queue = new MaxHeap<>();
        for (Integer integer : map.keySet()) {
            if (queue.getSize() < k) {
                queue.addData(new Fre(integer, map.get(integer)));
            } else {
                int maxValue = queue.getMax().e;
                int max = queue.getMax().fre;
                if (map.get(integer).compareTo(max) > 0) {
                    queue.replace(new Fre(integer, map.get(integer)));
                }
            }
        }
        // 将得到的前k个元素放入list中
        List<Integer> list = new ArrayList<>();
        int size = queue.getSize();
        for (int i = 0; i < size; i++) {
            list.add(0, queue.extractMax().e);
        }

        return list;
    }

    public static void main(String[] args) {
        /*输入: nums = [1,1,1,2,2,3], k = 2
        输出: [1,2]*/

        int[] nums = new int[]{-1, 1, 4, -4, 3, 5, 4, -2, 3, -1};
        List<Integer> list = new Solution().topKFrequent(nums, 3);
        for (Integer integer : list) {
            System.out.println("integer = " + integer);
        }
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!