美文网首页
数据结构-堆和优先队列

数据结构-堆和优先队列

作者: 听你讲故事啊 | 来源:发表于2019-04-04 11:01 被阅读0次

    优先队列

    普通队列: 先进先出; 后进后出
    优先队列: 出队顺序和入队顺序无关; 和优先级有关

    优先队列可以让操作系统动态的选择优先级最高的任务执行

    https://github.com/raywenderlich/swift-algorithm-club/tree/master/Heap

    最大堆: 堆中的某个节点的值总是不大于其父节点的值

    用数组实现堆,如果下标从0开始

    parent(i) = curr((i - 1)/2)
    left(i)   = 2i + 1
    right(i)  = 2i + 2
    

    堆的基本结构

    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 int size() {
            return data.getSize();
        }
    
        // 返回一个布尔值, 表示堆中是否为空
        public boolean isEmpty() {
            return data.isEmpty();
        }
    
        // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
        private int parent(int index) {
            if (index == 0)
                throw new IllegalArgumentException("index-0 doesn't 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 k){
    
            while(k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0 ){
                data.swap(k, parent(k));
                k = parent(k);
            }
        }
    

    Array类中添加swap方法

        public void swap(int i, int j){
    
            if(i < 0 || i >= size || j < 0 || j >= size)
                throw new IllegalArgumentException("Index is illegal.");
    
            E t = data[i];
            data[i] = data[j];
            data[j] = t;
        }
    

    取出堆中的最大元素

    首先将最后一个元素和根元素进行交换, 此时根元素需要进行下沉, 最后一个元素就成了最大元素

        // 看堆中的最大元素
        public E findMax() {
            if (data.getSize() == 0)
                throw new IllegalArgumentException("Can not findMax when 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;
        }
    
        private void siftDown(int k) {
    
            while (leftChild(k) < data.getSize()) {
                int j = leftChild(k); // 在此轮循环中,data[k]和data[j]交换位置
                if (j + 1 < data.getSize() &&
                        data.get(j + 1).compareTo(data.get(j)) > 0)
                    j++;
                // data[j] 是 leftChild 和 rightChild 中的最大值
    
                if (data.get(k).compareTo(data.get(j)) >= 0)
                    break;
    
                data.swap(k, j);
                k = j;
            }
        }
    

    重整堆

    heapify: 将任意数组整理成堆的形状
    删除任意元素后进行堆的调整
    采用的是自底向上的思想

    现有一个长度为n的数组, 里面的元素都没有顺序,将其看成完全二叉树,
    从最后一个非叶子节点开始,不断进行下沉(上浮)操作
    最后一个非叶子节点(parent(i) = curr((i - 1)/2))
    时间复杂度(O(n))

        public MaxHeap(E[] arr){
            data = new Array<> (arr);
            for(int i = parent(arr.length - 1) ; i >= 0 ; i --)
                siftDown(i);
        }
    
            // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
        private int parent(int index) {
            if (index == 0)
                throw new IllegalArgumentException("index-0 doesn't have parent.");
            return (index - 1) / 2;
        }
    

    也可以向空堆中添加元素,时间复杂度是O(nlogn)
    n个元素, 添加元素的时间复杂度是O(logn)

    
    import java.util.Random;
    
    public class Main {
    
        private static double testHeap(Integer[] testData, boolean isHeapify){
    
            long startTime = System.nanoTime();
    
            MaxHeap<Integer> maxHeap;
            if(isHeapify)
                maxHeap = new MaxHeap<>(testData);
            else{
                maxHeap = new MaxHeap<>();
                for(int num: testData)
                    maxHeap.add(num);
            }
    
            int[] arr = new int[testData.length];
            for(int i = 0 ; i < testData.length ; i ++)
                arr[i] = maxHeap.extractMax();
    
            for(int i = 1 ; i < testData.length ; i ++)
                if(arr[i-1] < arr[i])
                    throw new IllegalArgumentException("Error");
            System.out.println("Test MaxHeap completed.");
    
            long endTime = System.nanoTime();
    
            return (endTime - startTime) / 1000000000.0;
        }
    
        public static void main(String[] args) {
    
            int n = 5000000;
    
            Random random = new Random();
            Integer[] testData = new Integer[n];
            for(int i = 0 ; i < n ; i ++)
                testData[i] = random.nextInt(Integer.MAX_VALUE);
    
            double time1 = testHeap(testData, false);
            System.out.println("Without heapify: " + time1 + " s");
    
            double time2 = testHeap(testData, true);
            System.out.println("With heapify: " + time2 + " s");
        }
    }
    
    Test MaxHeap completed.
    Without heapify: 7.5391171 s
    Test MaxHeap completed.
    With heapify: 5.4018795 s
    

    基于堆的优先队列

    复用之前的Queue接口

    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 E getFront(){
            return maxHeap.findMax();
        }
    
        @Override
        public void enqueue(E e){
            maxHeap.add(e);
        }
    
        @Override
        public E dequeue(){
            return maxHeap.extractMax();
        }
    }
    

    解决leetcode347号问题
    给定非空的整数数组,返回k个频次最高的元素

    输入: nums = [1,1,1,2,2,3],k = 2 
    输出:[1,2]
    

    使用map将元素和频次进行统计
    创建一个优先队列, 将map中的前k个元素压入队列中
    后面的元素与根元素进行比较,判断是否入队

    import java.util.LinkedList;
    import java.util.List;
    import java.util.TreeMap;
    
    public class Solution {
        private class Freq implements Comparable<Freq>{
    
            public int e, freq;
    
            public Freq(int e, int freq){
                this.e = e;
                this.freq = freq;
            }
    
            @Override
            public int compareTo(Freq another){
                if(this.freq > another.freq)
                    return 1;
                else if(this.freq < another.freq)
                    return -1;
                else
                    return 0;
            }
        }
    
        public List<Integer> topKFrequent(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> pq = new PriorityQueue<>();
            for(int key: map.keySet()){
                if(pq.getSize() < k)
                    pq.enqueue(new Freq(key, map.get(key)));
                else if(map.get(key) > pq.getFront().freq){
                    pq.dequeue();
                    pq.enqueue(new Freq(key, map.get(key)));
                }
            }
    
            LinkedList<Integer> res = new LinkedList<>();
            while(!pq.isEmpty())
                res.add(pq.dequeue().e);
            return res;
        }
    
        private static void printList(List<Integer> nums){
            for(Integer num: nums)
                System.out.print(num + " ");
            System.out.println();
        }
    
        public static void main(String[] args) {
    
            int[] nums = {1, 1, 1, 2, 2, 3};
            int k = 2;
            printList((new Solution()).topKFrequent(nums, k));
        }
    }
    

    完整代码

    相关文章

      网友评论

          本文标题:数据结构-堆和优先队列

          本文链接:https://www.haomeiwen.com/subject/rdkfiqtx.html