【算法&数据结构体系篇class06】:堆、大根堆、小根堆、优先队列

Java实现堆结构与堆排序
文章详细介绍了堆结构,包括大根堆和小根堆的实现,以及如何通过堆实现优先级队列。堆排序的两种方法和时间复杂度被探讨,同时提供了手写堆排序的代码示例。此外,还讨论了在特定条件下(元素移动不超过k)的堆排序策略。最后,文章展示了最大线段重合问题的堆解法。

一、堆结构

1)堆结构就是用数组实现的完全二叉树结构
2)完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
3)完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
4)堆结构的heapInsertheapify操作
5)堆结构的增大add和减少poll
6)优先级队列结构,就是堆结构
  • heapInsert:

 入堆排序操作,从数组最后一个位置插入,然后再与其父节点(i-1)/2比较大小,大则交换上去,接着往其爷节点持续走..直到顶,或小于当前节点的父节点则停止,完成排序

  • heapify:

堆下沉排序操作 剔除元素后,需要将交换到根部的元素往下沉判断排序,如果大于左右节点就不用动,小于则与左右较大节点交换,并下沉继续判断,直到底部或者大于左右子节点

代码演示:

package class06;

import java.util.Comparator;
import java.util.PriorityQueue;

public class Heap {
    //大根堆 每个子树根节点比左右节点大
    public static class MyMaxHeap {
        private int[] heap;
        private int heapSize;
        private final int limit;

        public MyMaxHeap(int limit) {
            heap = new int[limit];
            heapSize = 0;
            this.limit = limit;
        }

        public boolean isEmpty() {
            return heapSize == 0;
        }

        public boolean isFull() {
            return limit == heapSize;
        }

        //入堆,同时保持堆的大根堆 有序
        public void push(int value) {
            if (isFull()) {
                throw new RuntimeException("堆已满,无法添加元素!");
            }
            //没满就赋值追加到数组后
            heap[heapSize] = value;
            //依次与该元素的父节点比较大小,大则交换两元素,然后接着往上走,直到顶或者不大于父节点,同时最后要把size+1
            heapInsert(heap, heapSize++);
        }

        //入堆操作,从数组最后一个位置插入,然后再与其父节点(i-1)/2比较大小,大则交换上去,接着往其爷节点持续走..直到顶,或小于当前节点的父节点则停止,完成排序
        private void heapInsert(int[] heap, int i) {
            //这个条件判断了两种情况,一个是大于父节点,一个是还没到顶节点(假如i来到顶部0 那么(i-1)/2也等0 为自己,是等于) 则继续循环。
            while (heap[i] > heap[(i - 1) / 2]) {
                swap(heap, i, (i - 1) / 2);
                i = (i - 1) / 2;
            }
        }

        //出堆,弹出最大值,顶部,然后保证当前堆仍有序 是大根堆
        public int pop() {
            if (isEmpty()) {
                throw new RuntimeException("堆已空,无法弹出元素!");
            }
            //弹出首元素,最大值
            int ans = heap[0];
            //然后把元素剔除两步  1.将首元素,与尾元素(heapSize是长度,尾元素是heapSize-1)交换,因为弹出操作,需要将heapSize 元素个数-1,两个操作只需要用--heapSize就能符合
            swap(heap, 0, --heapSize);
            //2.交换后表示将根节点元素剔除,然后需要确保现有堆的顺序
            heapify(heap, 0, heapSize);
            return ans;
        }

        //堆下沉排序操作 剔除元素后,需要将交换到根部的元素往下沉判断排序,如果大于左右节点就不用动,小于则与左右较大节点交换,并下沉继续判断,直到底部或者大于左右子节点
        private void heapify(int[] heap, int i, int heapSize) {
            //首先判断是否存在左子节点,左节点索引是i*2+1,不能超过heapSize-1尾索引,如果超过那肯定就到最后一个元素,右节点是比左节点大1 也更不会存在
            while (i * 2 + 1 < heapSize) {
                //此时确定有左节点,但需要判断是否有右节点i*2+2 如果有 并且大于左节点,那么左右节点较大值就是右节点,否则就是左节点
                int largest = i*2+2 < heapSize && heap[i*2+2]>heap[i*2+1]?i*2+2:i*2+1;
                //然后把较大的节点与父节点比较,谁大则重新赋值 largest最大值
                largest = heap[largest] > heap[i]?largest:i;
                if(largest == i) break; //如果判断后这个最大值位置就是父节点位置,相当于父节点都大于子节点,那么就不用交换,再下沉,此时已经完成排序,大的仍旧在前面,小的在下面,直接退出循环
                //子节点大于当前节点,那么与其较大的节点交换,交换完之后,当前节点i要来到较大节点largest位置 循环下沉
                swap(heap,i,largest);
                i = largest;
            }
        }
        private void swap(int[] heap, int i, int j) {
            int temp = heap[i];
            heap[i] = heap[j];
            heap[j] = temp;
        }
    }

    public static class RightMaxHeap {
        private int[] arr;
        private final int limit;
        private int size;

        public RightMaxHeap(int limit) {
            arr = new int[limit];
            this.limit = limit;
            size = 0;
        }

        public boolean isEmpty() {
            return size == 0;
        }

        public boolean isFull() {
            return size == limit;
        }

        public void push(int value) {
            if (size == limit) {
                throw new RuntimeException("heap is full");
            }
            arr[size++] = value;
        }

        public int pop() {
            int maxIndex = 0;
            for (int i = 1; i < size; i++) {
                if (arr[i] > arr[maxIndex]) {
                    maxIndex = i;
                }
            }
            int ans = arr[m
&lt;think&gt;好的,我现在需要回答用户关于大根堆优先队列以及在JavaScript中实现它们的问题。首先,我需要回忆这些概念的定义和原理,确保自己理解正确。然后,考虑如何用JavaScript实现,并解释每一步的原理。同时,要注意用户可能需要的是清晰的解释和实用的代码示例。 首先,是一种特殊的完全二叉树,分为大根堆小根堆大根堆的特点是父节点的值大于或等于子节点的值。优先队列通常使用来实现,因为可以高效地获取和删除最大或最小元素。 接下来,我需要确定如何在JavaScript中实现。由于JS没有内置的结构,通常用数组来模拟。数组中的元素按层次顺序存储,父节点和子节点的索引可以通过公式计算。例如,父节点索引为(i-1)/2,左子节点为2i+1,右子节点为2i+2。 然后,实现大根堆的关键操作:插入元素和提取最大值。插入时,将元素添加到数组末尾,然后上浮到合适的位置。提取最大值时,交换顶和末尾元素,删除末尾,然后调整顶下沉。 优先队列基于实现,主要操作是入队和出队,分别对应的插入和提取。需要确保每次操作后的性质保持不变。 在编写代码时,需要注意边界条件,比如为空时的处理。同时,要解释每个方法的作用和实现原理,例如上浮和下沉的过程。 现在,检查是否有遗漏的部分。例如,是否需要处理的构过程?用户可能想知道如何将一个无序数组转化为,但可能优先队列的实现更关注插入和提取。因此,可能不需要详细解释构算法,除非用户特别提到。 另外,考虑代码的可读性和效率。例如,在比较子节点时,需要确保不越界,选择较大的子节点进行交换。在上浮和下沉的过程中,循环条件是否正确,避免死循环。 最后,总结这些概念之间的关系,帮助用户理解优先队列为什么常用来实现,以及大根堆在其中的作用。确保示例代码清晰,注释详细,方便用户理解每一步的操作。&lt;/think&gt;以下是对大根堆优先队列的解释及JavaScript实现: --- ### 一、核心概念解析 **1. (Heap)** - 完全二叉树结构 - 满足性质:任意节点的值总 &ge;(大根堆)或 &le;(小根堆)子节点的值 - 物理存储通常使用数组实现 - 时间复杂度:插入/删除 $O(\log n)$,获取极值 $O(1)$ **2. 大根堆(Max Heap)** - 父节点值 &ge; 子节点值 - 根节点是整个的最大值 - 典型应用场景:优先队列 **3. 优先队列(Priority Queue)** - 抽象数据结构 - 元素带有优先级,按优先级出队 - 常用大根堆实现(高优先级=大数值) --- ### 二、JavaScript实现代码 ```javascript class MaxHeap { constructor() { this.heap = []; } // 获取父节点索引(公式推导) parentIndex(i) { return Math.floor((i - 1) / 2); } // 插入元素(上浮操作) insert(val) { this.heap.push(val); let i = this.heap.length - 1; while (i &gt; 0 &amp;&amp; this.heap[i] &gt; this.heap[this.parentIndex(i)]) { const p = this.parentIndex(i); [this.heap[i], this.heap[p]] = [this.heap[p], this.heap[i]]; // ES6解构交换 i = p; } } // 调整顶元素(下沉操作) heapify(i) { const n = this.heap.length; let max = i; const l = 2 * i + 1; // 左子节点索引 const r = 2 * i + 2; // 右子节点索引 if (l &lt; n &amp;&amp; this.heap[l] &gt; this.heap[max]) max = l; if (r &lt; n &amp;&amp; this.heap[r] &gt; this.heap[max]) max = r; if (max !== i) { [this.heap[i], this.heap[max]] = [this.heap[max], this.heap[i]]; this.heapify(max); // 递归调整 } } // 提取最大值 extractMax() { if (this.heap.length === 0) return null; const max = this.heap[0]; this.heap[0] = this.heap.pop(); // 用最后一个元素替换顶 this.heapify(0); // 从顶开始调整 return max; } } // 优先队列实现 class PriorityQueue { constructor() { this.heap = new MaxHeap(); } enqueue(val) { this.heap.insert(val); } dequeue() { return this.heap.extractMax(); } peek() { return this.heap.heap[0] || null; } } ``` --- ### 三、关键操作原理 **插入元素(上浮)**: 1. 将新元素添加到数组末尾 2. 循环比较与父节点的大小 3. 若大于父节点则交换位置 4. 时间复杂度:$O(\log n)$ **提取最大值(下沉)**: 1. 取出顶元素(最大值) 2. 将最后一个元素移到顶 3. 循环比较与子节点的大小 4. 与较大的子节点交换位置 5. 时间复杂度:$O(\log n)$ --- ### 四、使用示例 ```javascript const pq = new PriorityQueue(); pq.enqueue(3); // 队列:3 pq.enqueue(5); // 队列:5,3 pq.enqueue(1); // 队列:5,3,1 console.log(pq.dequeue()); // 输出5(当前队列:3,1) console.log(pq.peek()); // 输出3 ``` --- ### 五、应用场景 - 任务调度系统(高优先级任务优先执行) - 图算法(Dijkstra最短路径算法) - 合并K个有序链表 - 数据流中实时获取Top K元素 通过结构实现的优先队列,能够在$O(1)$时间复杂度获取最大值,插入/删除操作只需$O(\log n)$时间复杂度,比普通数组实现效率更高。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值