数据结构之优先级队列(堆)及top-k问题讲解(二)

简介: 数据结构之优先级队列(堆)及top-k问题讲解(二)

数据结构之优先级队列(堆)及top-k问题讲解(一)+https://developer.aliyun.com/article/1413566

2. PriorityQueue常用接口介绍

1.构造方法

1.1不含参的构造方法
// 不含参的构造方法
    public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }
    // 默认容量是11
    private static final int DEFAULT_INITIAL_CAPACITY = 11;
1.2 指定容量的构造方法
// 指定容量的构造方法
    public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }
1.3指定容量  并接受比较器的构造方法(最核心的一个构造方法)
// 指定容量  并接受比较器的构造方法
    public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        // Note: This restriction of at least one is not actually needed,
        // but continues for 1.5 compatibility
        // 如果初始容量<1  就抛出异常  但实际上源码中也说了  <1这个条件并不是必需的  只不过是为了和1.5保持一致设置的条件
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }
1.4利用其他集合创建一个优先级队列

只要实现了Collection的集合都能作为参数参与创建一个优先级队列

PriorityQueue(Collection<? extends E> c)  用一个集合来创建优先级队列

List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(12);
        list.add(13);
        PriorityQueue<Integer> priorityQueue =
                new PriorityQueue<>(list);
        System.out.println(priorityQueue.toString());// 输出1 12 13

2.PriorityQueue的扩容机制

PriorityQueue的存储结构是顺序表,在不断添加数据的时候涉及到扩容问题,下面来研究一下PriorityQueue中是如何进行扩容的

先来看offer对应的源码

public boolean offer(E e) {
        // 为空 直接抛出异常  不能插入空指针
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        // 进行扩容
        if (i >= queue.length)
            // grow方法内部是扩容的机制
            grow(i + 1);
        size = i + 1;
        if (i == 0)
            queue[0] = e;
        else  // 向上调整
            siftUp(i, e);
        return true;
    }

接下来看grow方法的实现


3.如何实现大根堆

PriorityQueue默认是小根堆,想要实现大根堆则需要重新构建比较逻辑,使用Comparator接口,下面以整数的比较为例实现大根堆

1.方法一:直接构造一个比较器
// 构造比较器 实现大根堆
        class IntCmp implements Comparator<Integer> {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        }
        PriorityQueue<Integer> priorityQueue =
                new PriorityQueue<>(new IntCmp());
        priorityQueue.offer(1);
        priorityQueue.offer(2);
        priorityQueue.offer(33);
        priorityQueue.offer(44);
        priorityQueue.offer(55);
        System.out.println(priorityQueue.poll());// 输出55
2.方法二:使用匿名内部类
// 使用匿名内部类
        PriorityQueue<Integer> priorityQueue =
                new PriorityQueue<>(new Comparator<Integer>() {
                    @Override
                    public int compare(Integer o1, Integer o2) {
                        return o2.compareTo(o1);
                    }
                });
        priorityQueue.offer(1);
        priorityQueue.offer(2);
        priorityQueue.offer(33);
        priorityQueue.offer(44);
        priorityQueue.offer(55);
        System.out.println(priorityQueue.poll());// 输出55
3.方法三:使用lambda表达式(推荐)
// 使用lambda表达式
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(
                (o1,o2) ->{return o2.compareTo(o1);}
        );
        priorityQueue.offer(1);
        priorityQueue.offer(2);
        priorityQueue.offer(33);
        priorityQueue.offer(44);
        priorityQueue.offer(55);
        System.out.println(priorityQueue.poll());// 输出55

四.重点:top-k问题的三种解决方法

https://leetcode.cn/problems/smallest-k-lcci/description/

1.最简单的思路:对数组进行排序

// 解法1
    public int[] smallestK1(int[] arr, int k) {
        int[] ret = new int[k];
        if(arr == null || k <= 0) return ret;
        Arrays.sort(arr);
        // 排序之后  arr从小到大排列完毕
        for (int i = 0; i < k; i++) {
            ret[i] = arr[i];
        }
        return ret;
    }

2.直接使用堆(优先级队列)的特性  创建小根堆  poll k次即可

// 解法2
    public int[] smallestK2(int[] arr, int k) {
        int[] ret = new int[k];
        if(arr == null || k <= 0) return ret;
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
        for (int i = 0; i < arr.length; i++) {
            priorityQueue.offer(arr[i]);
        }
        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }
        return ret;
    }

3.建立一个k个结点的大根堆  再与数组剩余元素进行比较(重点掌握)

步骤:

  1. 先将前k个元素创建为大根堆(k 是因为我最后要返回k个元素)
  2. 将数组中剩余的元素依次去和堆顶元素进行比较,如果小于堆顶元素,插入堆中
  3. 返回容量为k的大根堆

// 解法3  效率最高的一种方法  创建一个具有k个结点的大根堆
    // 注意  优先级队列默认是小根堆  要实现大根堆  需要先创建一个实现了Comparator接口的对象
    class IntCmp implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2.compareTo(o1);
        }
    }
    public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k];
        if(arr == null || k <= 0) return ret;
        // 创建一个具有k个结点的大根堆
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new IntCmp());
        for (int i = 0; i < k; i++) {
            // 将前k个元素做成大根堆
            priorityQueue.offer(arr[i]);
        }
        for (int i = k; i < arr.length; i++) {
            // 去和栈顶元素比较
            if (arr[i] < priorityQueue.peek()) {
                // 证明栈顶元素不是前k个最小的元素  要删除
                priorityQueue.poll();
                priorityQueue.offer(arr[i]);
            }
        }
        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }
        return ret;
    }

时间复杂度分析:

前两种方法对于数据量特别大的情况十分不友好!占用的内存太大

变式:

求数组中第k大/小元素

// 求数组中第k大/小元素
    //  前k个元素存储到小根堆中  堆里存放最大的k个元素 以小根堆的形式存储  则堆头一定是第k大的元素
    public int maxKestK(int[] arr, int k) {
        if(arr == null || k <= 0) {
            throw new ArrayEmptyException("不含有元素或k不合法");
        }
        // 创建一个具有k个结点的大根堆
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
        for (int i = 0; i < k; i++) {
            // 将前k个元素做成小根堆
            priorityQueue.offer(arr[i]);
        }
        for (int i = k; i < arr.length; i++) {
            if (arr[i] > priorityQueue.peek()) {
                priorityQueue.poll();
                priorityQueue.offer(arr[i]);
            }
        }
        return priorityQueue.poll();
    }

五.堆排

 堆排序即利用堆的思想进行排序,堆排序的过程可以分为两步

  1. 确定排序方式:升序--建立大根堆  降序--建立小根堆
  2. 利用堆删除的思想(向下调整进行堆排)

图解:

代码实现

// 堆排序
    // 升序  从小到大  创建大根堆
    // 降序  从大到小  创建小根堆
    /**
     * 升序  调整为大根堆  堆首元素一定是最大的
     * 交换堆首和堆尾元素  向下调整(不包含被调下去的最大元素)  使第二大的元素位于堆顶
     * 重复上述操作  每次都是堆首元素和堆尾元素进行交换
     */
    public void headSort() {
        int end = usedSize-1;
        while(end > 0) {
            swap(0,end);
            shiftDown(0,end);
            end--;
        }
    }
    private void shiftDown(int parent, int usedSize) {
        int child = 2 * parent+1;// 得到左孩子的下标
        while (child < usedSize) {
            // 首先要保证child是左右孩子最大元素的下标
            if(child + 1 < usedSize && elem[child] < elem[child+1]) {
                // 有右孩子  且右孩子的值比左孩子大
                child++;
            }
            // 此时child就是值最大孩子的下标
            if(elem[child] > elem[parent]) {
                swap(child,parent);
                parent = child;
                child = 2 * parent+1;
            }else {
                break;
            }
        }
    }

总结:

堆排时要选择好排序的顺序,如果是升序排序就创建大根堆,如果是降序排序就是用小根堆

以升序排序为例,创建一个大根堆存储数据,再不断交换堆顶和堆尾元素(此时一定是最大元素放到堆尾,最小元素放到堆顶),再进行向下调整,向下调整的数据范围并不报错刚刚被挪动到堆尾的元素,使整个堆仍然保持大根堆的性质,这样最大的元素就被移动到最后,依次操作,每次都可以把当前数据范围内的最大元素移动到最后,最后创建出的堆中存储的数据就是升序排序的

这种"逆向思维"在堆中很常见,想让堆顶是最小的元素,就先创建大根堆,再不断地挪动数据,向下调整


目录
相关文章
|
23天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
114 9
|
20天前
|
存储 算法 Java
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
30 1
散列表的数据结构以及对象在JVM堆中的存储过程
|
22天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
61 16
|
26天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
2月前
|
存储 JavaScript 前端开发
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
72 1
|
2月前
【数据结构】-- 栈和队列
【数据结构】-- 栈和队列
17 0
|
2月前
|
存储 算法
探索数据结构:分支的世界之二叉树与堆
探索数据结构:分支的世界之二叉树与堆
|
2月前
探索数据结构:队列的的实现与应用
探索数据结构:队列的的实现与应用
|
14天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
22 1
|
2天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
17 5

热门文章

最新文章

下一篇
无影云桌面