09.优先队列与堆(完全二叉树与动态数组索引的联系)

[toc]

一、优先队列

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

优先队列的各种实现比较

结构 入队 出队(拿出最大的元素)
普通线性结构 O(1) O(n)
顺序线性结构 O(n) O(1)
O(logn) O(logn)

二、二叉堆 Binary Heap

  1. 二叉堆是一个完全二叉树

注意:满二叉树是值除了叶子节点,其他节点都含有左右子节点

完全二叉树:不一定是一个满二叉树,但是缺失的那部分一定在整棵树的右下侧;也就是从上到下,从左到右,一层一层的铺,铺满一层再铺下一层,把元素顺序排列成树的形状,

  1. 最大堆(同样可以定义最小堆) 堆中某个节点的值,总是不大于其父节点的值(子节点的值永远小于等于父节点的值)

注意:二叉堆不同层元素之间无明确比较性,层级越浅不一定比层级更深的大。

二叉堆(完全二叉树)与数组索引之间的关系

利用动态数组储存二叉堆,从上到下,从左到右,从0开始索引编号,对于第i节点的左孩子的索引为2i + 1 , 右孩子为2i + 2,而对于第i节点的父节点,索引为(i - 1) /2

二叉堆在内存中是以动态数组储存的,而逻辑上是用二叉树的思想实现的

这里用的数组为之前实现的动态数组Array

SiftUP操作:向最大堆中添加节点,先将节点添加到完全二叉树中,然后将节点与父节点比较,如果大于父节点,就进行交换。

SiftDown:从堆中取出元素:具体的算法思想是将堆顶上的值返回,然后将完全二叉树最后一个元素放在堆顶,然后将堆最后一个元素删除。将顶的元素与孩子进行比较,如果不存在左孩子,则不需要置换,如果存在左孩子且存在右孩子且右孩子大于左孩子,则将节点与右孩子进行置换,否则与左孩子进行置换。再根据情况判断是否进入下一个循环。

详细请参考下面的代码MaxHeap类。

replace
取出最大元素后,放入一个新的元素,先将最大元素返回,然后将最大元素替换为一个新的元素,然后进行Sift Down,这样复杂度就变为O(logn)级别的了。

Heapify
通过构造函数实现,传入一个数组,将其转换为一个最大堆,
算法思想:找到完全二叉树中最后一个含有子节点的节点索引(其实就为动态数组最后一个节点的父节点),以此位置为基础,倒着遍历完全二叉树的节点,反复进行shiftDown操作。这样操作的复杂度为O(logn)

  • Array.java 动态数组中添加的部分置换代码
/**一个新的构造函数*/


/** 交换两个索引对应的值 */
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 class MaxHeap<E extends Comparable<E>> {

    /**引入之前写的动态数组类Array*/
    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;
    }

    /**向堆中添加元素 sift up
     * 将节点添加到树中,反复与父节点比较,
     * 为了满足最大堆可以将其进行反复交换*/
    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);
        }
    }

    /** 看堆中的最大元素*/
    public E findMax(){
        if(data.getSize() == 0)
            throw new IllegalArgumentException("Can not findMax when heap is empty.");

        return data.get(0);
    }

    /**取出堆中最大的元素*/
    public E extrackMax(){
        E ret = findMax();
        data.swap(0, data.getSize() - 1);
        data.removeLast();
        siftDown(0);
        return ret;
    }

    /**shiftDown的思想实现*/
    private void siftDown(int k){

        // 如果存在左节点则进行循环(不可能只有右节点,这是个完全二叉树)
        while(leftChild(k) < data.getSize()){
            /**获得最大子节点的索引*/
            // j 暂存可能会被替换的索引
            int j = leftChild(k);
            // 如果右孩子存在且右孩子比左孩子大
            if(j + 1 < data.getSize() &&
                data.get(j + 1).compareTo(data.get(j)) > 0)
                j = rightChild(k);
            // data[j] 是leftChild和rightChild中最大的

            /**将父节点与最大子节点比较*/
            if(data.get(k).compareTo(data.get(j)) >= 0)
                break;

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


}

三、优先队列

  • 优先队列实现的接口

Queue.java

public interface Queue<E> {
    int getSize();
    boolean isEmpty();
    void enqueue(E e);
    E dequeue();
    E getFront();
}
  • 优先队列的实现
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 this.maxHeap.size() == 0;
    }

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

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

    @Override
    public E dequeue(){
        return maxHeap.extrackMax();
    }
}
  • 测试代码

Main.java

import java.util.Random;
public class Main {

    public 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(Integer data : testData)
                maxHeap.add(data);
        }

        int[] arr = new int[testData.length];
        for(int i = 0; i < testData.length; i ++)
            arr[i] = maxHeap.extrackMax();

        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[] agrs){

        int n = 1000000;

        MaxHeap<Integer> maxHeap = new MaxHeap<>();
        Random random = new Random();
        for(int i = 0; i < n; i++)
            maxHeap.add(random.nextInt(Integer.MAX_VALUE));

        int [] arr = new int[n];
        for(int i = 0; i < n; i++){
            arr[i] = maxHeap.extrackMax();
        }

        for(int i = 1; i < n; i++){
            if(arr[i - 1] < arr[i])
                throw new IllegalArgumentException("Error");
        }

        System.out.println("Test MaxHeap completed");

        /**============================================*/

        int m = 1000000;
        Random random2 = new Random();
        Integer[] testData = new Integer[m];
        for(int i = 0; i < m; i ++){
            testData[i] = random2.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");

    }
}

四、Leetcode上的347号问题

在N个元素中选出前M个元素(N远远大于M)
并归排序时间复杂度: NlogN
使用优先队列(基于堆):NlogM

思路:使用优先队列,只维护当前看得到的前M个元素
多态compareTo方法实现。

347号问题:请点击Leecode进行查看

1、用自己实现的最大堆实现

import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;

public class Solution {

    /**重写入堆的优先级,implements了Comparable
     * 必须实现compareTo方法*/
    private class Freq implements Comparable<Freq> {
        private int e, freq;

        private Freq(int e, int freq) {
            this.e = e;
            this.freq = freq;
        }

        /**重写compareTo方法,重新定义优先级的规则
         * 从而利用最大堆来储存'最小堆',这波操作特别六*/
        @Override
        public int compareTo(Freq another) {
            if(this.freq < another.freq)
                return 1;
            else if(this.freq > another.freq)
                return -1;
            else
                return 0;
        }
    }

    /**List是一个接口,需要制定底层的实现,这里用的ArrayList进行实现的*/
    public List<Integer> topKFrequent(int[] nums, int k) {

        /**先用map统计词频*/
        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);
        }

        /**优先队列,最大堆
         * 利用最大堆储存一个最小堆(很神奇是吧),
         * 利用优先队列只保存所需个数的长度,数顶是当前最小频率的数
         * 然后将之后遍历得到的频率与最小的比较,如果比最小的大,就将
         * 顶的数出队列,然后将当前的入队列并自动排序,当遍历完map中所有
         * 的key,优先队列里面保存的就是所需的前频次最高的数*/
        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(int num : nums)
            System.out.println(num + " ");
        System.out.println();
    }

    public static void main(String[] args) {
        int[] nums = {1,1,1,1,1,2,2,2,3,3,3,4,4,6,7,8,0};
        int k = 4;
        printList((new Solution()).topKFrequent(nums, k));
    }

}

2、使用Java标准类库中提供的PriorityQueue(底层是使用的最小堆)实现

    1. (基础版)使用一个新的类Freq,实现比较接口Comparable进行实现
/**使用Java标准库中 PriorityQueue,是用最小堆实现的
 * 但是可以自己传入一个 自定义优先级*/

import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import java.util.PriorityQueue;

public class MySolution {
    public 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<>();

        // 利用map统计词频
        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.size() < k)
                pq.add(new Freq(key, map.get(key)));
            else if(map.get(key) > pq.peek().freq) {
                pq.remove();
                pq.add(new Freq(key, map.get(key)));
            }
        }

        LinkedList<Integer> res = new LinkedList<>();
        while(!pq.isEmpty()) {
            res.add(pq.remove().e);
        }

        return res;
    }

    private static void printList(List<Integer> nums) {
        for(int num : nums)
            System.out.print(num + " ");
        System.out.println();
    }

    public static void main(String[] args) {
        int[] nums = {1,1,1,1,2,2,2,2,3,3,4,5,6,7,7};
        int k = 2;
        printList((new MySolution()).topKFrequent(nums, k));
    }

}

    1. (进阶版)自定义一个比较的方法,交给优先队列(通过构造函数),告诉优先队列使用自己实现比较的方法
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import java.util.PriorityQueue;
import java.util.Comparator;

public class MySolution {
    public class Freq {
        public int e, freq;

        public Freq(int e, int freq) {
            this.e = e;
            this.freq = freq;
        }
    }

    /**实现Comparator,定制比较的方法*/
    public class FreqComparator implements Comparator<Freq> {
        @Override
        public int compare(Freq a, Freq b) {
            return a.freq - b.freq;
        }
    }

    public List<Integer> topKFrequent(int[] nums, int k) {
        TreeMap<Integer, Integer> map = new TreeMap<>();

        // 利用map统计词频
        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<>(new FreqComparator());
        for(int key: map.keySet()) {
            if(pq.size() < k)
                pq.add(new Freq(key, map.get(key)));
            else if(map.get(key) > pq.peek().freq) {
                pq.remove();
                pq.add(new Freq(key, map.get(key)));
            }
        }

        LinkedList<Integer> res = new LinkedList<>();
        while(!pq.isEmpty()) {
            res.add(pq.remove().e);
        }

        return res;
    }

    private static void printList(List<Integer> nums) {
        for(int num : nums)
            System.out.print(num + " ");
        System.out.println();
    }

    public static void main(String[] args) {
        int[] nums = {1,1,1,1,2,2,2,2,3,3,4,5,6,7,7};
        int k = 2;
        printList((new MySolution()).topKFrequent(nums, k));
    }

}

    1. (高阶版)在2的基础上使用匿名函数,并去掉Freq类
package maxHeap;

import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import java.util.PriorityQueue;
import java.util.Comparator;

public class MySolution {

    public List<Integer> topKFrequent(int[] nums, int k) {
        TreeMap<Integer, Integer> map = new TreeMap<>();

        // 利用map统计词频
        for(int num : nums) {
            if(map.containsKey(num))
                map.put(num, map.get(num) + 1);
            else
                map.put(num, 1);
        }

        // 调用定制比较方法的构造函数。使用匿名函数的方式
        PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return map.get(o1) - map.get(o2);
            }
        });
        for(int key: map.keySet()) {
            if(pq.size() < k)
                pq.add(key);
            else if(map.get(key) > map.get(pq.peek())) {
                pq.remove();
                pq.add(key);
            }
        }

        LinkedList<Integer> res = new LinkedList<>();
        while(!pq.isEmpty()) {
            res.add(pq.remove());
        }

        return res;
    }

    private static void printList(List<Integer> nums) {
        for(int num : nums)
            System.out.print(num + " ");
        System.out.println();
    }

    public static void main(String[] args) {
        int[] nums = {1,1,1,1,2,2,2,2,3,3,4,5,6,7,7};
        int k = 2;
        printList((new MySolution()).topKFrequent(nums, k));
    }

}


    1. (终极牛逼版)使用lamda表达式的方式
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import java.util.PriorityQueue;
import java.util.Comparator;

public class MySolution {

    public List<Integer> topKFrequent(int[] nums, int k) {
        TreeMap<Integer, Integer> map = new TreeMap<>();

        // 利用map统计词频
        for(int num : nums) {
            if(map.containsKey(num))
                map.put(num, map.get(num) + 1);
            else
                map.put(num, 1);
        }

        /**使用lamda的方式,定义优先队列的优先级*/
        PriorityQueue<Integer> pq = new PriorityQueue<>(
                (a, b) -> map.get(a) - map.get(b)
        );
        for(int key: map.keySet()) {
            if(pq.size() < k)
                pq.add(key);
            else if(map.get(key) > map.get(pq.peek())) {
                pq.remove();
                pq.add(key);
            }
        }

        LinkedList<Integer> res = new LinkedList<>();
        while(!pq.isEmpty()) {
            res.add(pq.remove());
        }

        return res;
    }

    private static void printList(List<Integer> nums) {
        for(int num : nums)
            System.out.print(num + " ");
        System.out.println();
    }

    public static void main(String[] args) {
        int[] nums = {1,1,1,1,2,2,2,2,3,3,4,5,6,7,7};
        int k = 3;
        printList((new MySolution()).topKFrequent(nums, k));
    }

}

五、和堆相关的更多话题和广义队列

d叉堆 d-ary heap 索引堆 二项堆 斐波那契堆
时间复杂度为NlogdM,对应的代价就是 比较高级
![广义队列接口.png](https://upload-images.jianshu.io/upload_images/14371562-4779ce702373c817.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 广义队列 支持一个普遍的接口
广义队列接口.png

普通队列:先进先出
优先队列:优先级高的先出

栈,也可以理解为一个队列

六、整合案例:实现一个比较器来定义二叉堆元素的优先级,默认为最小堆

从底层到测试实现的思路

Array.java :
Array是一个动态数组,可以根据储蓄的容量自动扩容和自动缩容,自动扩容和缩容也进行了边界的优化。这里就不展示全部的代码了在我前面的一篇笔记 动态数组中详细介绍了实现的思路。

MinHeap.java
MinHeap是一个最小堆,基于Array实现的,支持自定义优先级排序只需要在构造方法中传入一个比较器(规定返回值为int,小于等于0则第一个参数的优先级高,否则第二个优先级高),如果不传入比较器,默认以入堆的元素的最小堆进行排序,并且内部实现了siftUp/shiftDown/replace/Heapify操作

import java.util.Comparator;

/**设计一个最小堆,默认是最小堆,但是支持自定义优先队列的优先级
 * 底层是以数组形式储存的,索引从0开始*/
public class MinHeap<E extends Comparable<E>> {

    private Array<E> data;

    /**接收定义优先级方法,默认使用最小堆进行堆排序
     * priority下面的compare接受两个参数compare(E a, E b)
     * 统一规定,返回的结果<=0,a的优先级高,反之b的优先级高*/
    private Comparator<E> priority;

    public MinHeap(int capacity) {
        this.data = new Array<>(capacity);
        this.priority = null;
    }

    public MinHeap() {
        this.data = new Array<>();
        this.priority = null;
    }

    /**设计一个支持自定义优先级的构造函数
     * 需要用户传递一个实现了Comparator接口的类,并且返回int类型*/
    public MinHeap(Comparator<E> priority) {
        this.data = new Array<>();
        this.priority = priority;
    }

    /**返回堆中的元素个数*/
    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 has a 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);
    }

    /**sift up,将节点添加到完全二叉树的末尾,
     * 反复与父节点比较,按照优先级进行交换,
     * 直到与满足最小堆*/
    private void siftUp(int k) {

        /**实现默认情况下的最小堆*/
        if(this.priority == null) {
            while (k > 0 && data.get(k).compareTo(data.get(parent(k))) < 0) {
                data.swap(k, parent(k));
                k = parent(k);
            }
        } else {
            /**规定接收定义优先级方法,默认使用最小堆进行堆排序
             * priority下面的compare接受两个参数compare(E a, E b)
             * 规定,返回的结果<=0,a的优先级高,反之b的优先级高*/
            while (k > 0 && priority.compare(data.get(k), data.get(parent(k))) < 0) {
                data.swap(k, parent(k));
                k = parent(k);
            }
        }
    }

    /** shiftDown 操作
     * 对于传进来的索引,根据这个索引与其节点最小的子节点
     * 进行交换,一直交换到满足最小堆*/
    private void siftDown(int k) {
        while (leftChild(k) < data.getSize()) {
            int j = leftChild(k);

            if(j + 1 < data.getSize()) {

                /**默认最小堆的情况*/
                if(this.priority == null){
                    if(data.get(j + 1).compareTo(data.get(j)) < 0)
                        j ++;
                } else {
                    /**规定接收定义优先级方法,默认使用最小堆进行堆排序
                     * priority下面的compare接受两个参数compare(E a, E b)
                     * 规定,返回的结果<=0,a的优先级高,反之b的优先级高*/
                    if(this.priority.compare(data.get(j + 1), data.get(j)) < 0)
                        j ++;
                }
            }

            /**将父节点与最大子节点比较*/
            if(this.priority == null) {
                if(data.get(j).compareTo(data.get(k)) < 0)
                    data.swap(j, k);
            } else {
                if(this.priority.compare(data.get(j), data.get(k)) < 0)
                    data.swap(j, k);
            }

            k = j;
        }
    }

    /**查看堆中的最大元素*/
    public E peek() {
        if(data.getSize() == 0)
            throw new IllegalArgumentException("The minHeap is empty!");

        return data.get(0);
    }

    /**取出堆中最大的元素*/
    public E extrackTop() {
        E res = this.peek();
        data.swap(0, data.getSize() - 1);
        data.removeLast();
        siftDown(0);
        return res;
    }

    /**取出堆中最大的元素,并且替换成元素e*/
    public E replace(E e) {
        E ret = this.peek();
        data.set(0, e);
        this.siftDown(0);
        return ret;
    }

    /**Heapify操作,使用构造函数实现*/
    public MinHeap(E[] arr) {
        data = new Array<>();
        for(int i = parent(arr.length - 1); i >= 0; i --) {
            siftDown(i);
        }
    }
}

Queue.java
Queue是普通队列的一个接口

public interface Queue<E> {
    int getSize();
    boolean isEmpty();
    void enqueue(E e);
    E dequeue();
    E getFront();
}

PriorityQueue.java
PriorityQueue是基于MinHeap实现的Queue接口的一个优先队列,默认的构造函数创建默认最小堆,带有参数的构造函数创建带有比较器的自定义堆。强烈建议传入一个lamda表达式。

import java.util.Comparator;

public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {

    private MinHeap<E> minHeap;

    public PriorityQueue() {
        this.minHeap = new MinHeap<>();
    }

    /**传入一个自定义优先级,构造函数*/
    public PriorityQueue(Comparator<E> priority) {
        this.minHeap = new MinHeap<>(priority);
    }

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

    @Override
    public boolean isEmpty() {
        return this.minHeap.size() == 0;
    }

    @Override
    public E getFront() {
        return this.minHeap.peek();
    }

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

    @Override
    public E dequeue() {
        return minHeap.extrackTop();
    }

    public void replace(E e) {
        minHeap.replace(e);
    }
}

Main.java

主类进行测试,测试用例是:有一个容量为1000000的装int类型的数组,里面有很多重复的元素,而且是乱序,现在要求快速找出频率最高的前10个元素并打印出来。

很low的思路:我可以先把这1000000排个序,啥冒泡排序啊,选择排序啊,并归排序啊三路块排啊什么什么的,排序后的数组标记为arr1,然后再建立一个装小数组arrL(长度为2)的大数组allB(长度为1000000),arrL第一个元素用于储存元素,arrL第二个储存统计的频率,遍历arr1,将相关数据储存到arrL,再将arrL装到allB,然后再根据arrL[1]进行从大到小排序,最后取前10个。对应的这种系统开销是非常可观的,对应的时间复杂度大概为O(n^2)级别的。数据量越大,复杂度结果越可怕。。。

牛逼思路:遍历这个数组A,利用标准库中的TreeMap统计各个数组出现的频率,利用优先队列,限制只储存10个元素,根据对应的频率大小确定入队的优先级,储存相关频率对应的数字,采用最小堆(注意,优先队列中储蓄的是数字,储存的数字本身大小不参与优先级的排序,参与排序的是数字对应map中的频率,而且是最小堆,堆顶是10个中频率最小的),遍历TreeMap,将数字存入优先队列,当优先队列中存入了10个元素后,之后遍历的数字与优先队列顶进行优先级比较(比较对应的频率),如果遍历的数字频率大于顶的频率,就进行replace操作,就是将这个数与顶的数进行交换,然后进行SiftDown请参照上面的SiftDown操作。遍历完了优先队列中储存的就为频率最大的前10个数。对应的时间复杂度大概为O(nlogm) ,n个数,取前m个数

import java.util.Comparator;
import java.util.Random;
import java.util.TreeMap;

public class Main {

    public static void main(String[] args) {

        int[] res0 = {1,1,1,1,1,1,3,3,5,5,5,5,5,5,7,7,8,8,8,8,8,9,0,2,3,4,5,6,7,8,9,0,1};
        int m0 = 3;
        printArray(findPro(res0, m0));
        System.out.println("===================");

        /**统计一个容量为n的数组中频次最高的前m个元素
         * 时间复杂度估计为O(nlogm), (如果不对请批评指教,我对于时间复杂度的计算不是特别熟)
         * 随机生成int类型的数字*/
        int n = 1000000;
        int m = 10;
        int[] res = new int[n];
        Random random = new Random();
        for(int i = 0; i < n; i ++) {
            res[i] = random.nextInt(Integer.MAX_VALUE);
        }

        printArray(findPro(res, m));
    }

    /**打印结果*/
    public static void printArray(int[] array) {
        for(int num : array) {
            System.out.print(num + " ");
        }

        System.out.println();
    }

    /**通过标准库的treeMap统计词频然后利用自己实现的基于动态
     * 数组->最小堆->优先队列统计,使用自定义实现Comparator确定优先级,
     * 找出前频次最高的元素*/
    public static int[] findPro(int[] array, int k) {

        // 利用map统计字频
        TreeMap<Integer, Integer> map = new TreeMap<>();
        for(int arr : array) {
            if(!map.containsKey(arr))
                map.put(arr, 1);
            else
                map.put(arr, map.get(arr) + 1);
        }

        /**通过优先队列遍历并储蓄m个最高的词频, 抛出的接口表示,
         * 返回值<=0,则第一个参数的优先级高。
         * 匿名函数 或者 lamda表达式能够访问当前的作用域*/

        /** 匿名函数的写法
        PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return map.get(o1) - map.get(o2);
            }
        });*/

        /** (高级玩儿法)lamda表达式*/
        PriorityQueue<Integer> pq = new PriorityQueue<>(
                (Integer o1, Integer o2) -> map.get(o1) - map.get(o2)
        );

        for(int key : map.keySet()) {
            if(pq.getSize() < k) {
                pq.enqueue(key);
            } else {
                if(map.get(key).compareTo(map.get(pq.getFront())) > 0){
                    pq.replace(key);
                }
            }
        }

        int[] ret = new int[k];
        for(int i = 0; i < k; i ++) {
            ret[i] = pq.dequeue();
        }
        return ret;
    }
}

谢谢阅读,如有错误的地方请批评指出,让大家一起进步!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容

  • 一 什么是优先队列? 1⃣️ 优先队列其实就是队列的一种,不过优先队列是区别于普通队列的;普通队列是一种先进先出,...
    十丈_红尘阅读 585评论 0 1
  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,455评论 1 31
  • 一些概念 数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这...
    Winterfell_Z阅读 5,806评论 0 13
  • 方法一 ___FULLUSERNAME___ : 用户登录的MAC账户名 ___COPYRIGHT___ : 版权...
    木子_礼阅读 1,827评论 0 1
  • 每次回家总是欣喜若狂,可以睡的昏天暗,可以吃的肆无忌惮,可以被养尊处优个几天,而这次我发现,不同寻常了。她...
    zCeline阅读 277评论 0 1