Java数据结构-堆

article/2025/11/7 13:54:30

目录

  • 一、简介
  • 二、堆的实现
    • (1)堆的插入
    • (2)删除根节点
    • (3)上浮操作
      • ① 大根堆的上浮
      • ② 小根堆的上浮
    • (4)下沉操作
      • ① 大根堆的下沉
      • ② 小根堆的下沉
    • (5)堆的构造
    • (6)堆排序
  • 三、优先队列
    • (1)最大优先队列实现

一、简介

堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵完全二叉树的数组对象。

堆的特性:

  1. 它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。 在这里插入图片描述

  1. 它通常用数组来实现。并且从索引 1 开始存储,即索引 0 直接废弃。具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置 1,它的子结点在位置 2 和 3,而子结点的子结点则分别在位置 4, 5 , 6 和 7,以此类推。
    在这里插入图片描述
    如果一个结点的位置为 k,则它的父结点(根节点没有父结点)的位置为[k/2],而它的两个子结点的位置则分别为2k2k+1。这样,在不使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动。

  1. 每个结点都(大于或小于)等于它的两个子结点。这里要注意堆中仅仅规定了每个结点(大于或小于)等于它的两个子结点,但这两个子结点的顺序并没有做规定,跟二叉查找树是有区别的。

  1. 上述提到的结点需要(大于或小于)等于它的两个子节点,是根据堆的类别来判断的。将根节点最大的堆叫做最大堆或大根堆,结点需要大于等于它的两个子结点;根节点最小的堆叫做最小堆或小根堆,结点需要小于等于它的两个子结点。
    在这里插入图片描述

二、堆的实现

堆的API设计

**在这里插入图片描述


public class Heap<T extends Comparable<T>> {// 存储堆中的元素private T[] items;// 记录堆中元素的个数private int N;public Heap(int capacity) {this.items = (T[]) new Comparable[capacity + 1];this.N = 0;}// 判断堆中索引i处的元素是否小于索引j处的元素private boolean less(int i, int j){return items[i].compareTo(items[j]) < 0;}// 交换堆中i索引和j索引处的值private void exch(int i, int j){T temp = items[i];items[i] = items[j];items[j] = temp;}// 往堆中插入一个元素public void insert(T t){}// 使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置private void swim(int k){}// 删除堆中最值并返回public T delFirst(){}// 使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置private void sink(int k){}
}

(1)堆的插入

堆是用数组完成数据元素的存储的,我们往数组中从索引 1 处开始,依次往后存放数据,但是堆中对元素的顺序是有要求的,每一个结点的数据要(大于或小于)等于它的两个子结点的数据,所以每次插入一个元素,都会使得堆中的数据顺序变乱,这个时候我们就需要通过一些方法让刚才插入的这个数据放入到合适的位置。

以下图例根据大根堆为例

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


所以,如果往堆中新插入元素,我们只需要不断的比较新结点 a[k] 和它的父结点 a[k/2] 的大小,然后根据结果完成数据元素的交换,就可以完成堆的有序调整。这里就设计到堆的上浮操作,等会再细谈。

// 往堆中插入一个元素
public void insert(T t){// 先将新元素加入堆items[++ N] = t;// 再对这个元素进行上浮操作swim(N);
}

(2)删除根节点

由堆的特性我们可以知道,索引1处的元素,也就是根结点。当我们把根结点的元素删除后,堆的顺序就乱了,那么我们应该怎么删除呢?

思路:

  1. 交换根节点与最后一个元素
  2. 把末尾的根节点删除
  3. 对新的根节点进行下沉操作,使之处于正确的位置

这里又提到了一个下沉操作,我们还是以大根堆为例,如图所示。

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


所以,当需要删除最值时,只需要将最后一个元素放到索引 1 处,并不断的拿着当前结点 a[k] 与它的子结点 a[2k]和 a[2k+1] 中的(较大者或较小者,根据大根堆。小根堆判断)交换位置,即可完成堆的有序调整。

// 删除堆中最值并返回
public T delFirst(){T first = items[1];// 交换索引1处的元素和最大索引处的元素,// 让完全二叉树中最右侧的元素变为临时根结点exch(1, N);// 最大索引处的元素删除掉items[N] = null;// 元素个数-1N --;// 通过下沉调整堆,让堆重新有序sink(1);return first ;
}

(3)上浮操作

① 大根堆的上浮

思路:

  1. 确定需要上浮元素的下标 k
  2. k > 1时,比较 item[k]item[k / 2] 的大小
  • item[k] > item[k / 2],交换两者位置,k = k / 2
  • item[k] <= item[k / 2],上浮结束

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


// 使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k){// 通过循环,不断的比较当前结点的值和其父结点的值while(k > 1){// 如果发现父结点的值比当前结点的值小,则交换位置if (less(k / 2, k)){exch(k / 2, k);}k /= 2;}
}

② 小根堆的上浮

思路:

  1. 确定需要上浮元素的下标 k
  2. k > 1时,比较 item[k]item[k / 2] 的大小
  • item[k] < item[k / 2],交换两者位置,k = k / 2
  • item[k] >= item[k / 2],上浮结束

这里实际就以大根堆的上浮操作是差不多的,只是比较当前结点与父结点的标准不同罢了。

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


// 使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k){// 通过循环,不断的比较当前结点的值和其父结点的值while(k > 1){// 如果发现当前结点的值比父结点的值小,则交换位置if (less(k, k / 2)){exch(k, k / 2);}k /= 2;}
}

(4)下沉操作

① 大根堆的下沉

思路:

  1. 确定需要下沉元素的下标 k
  2. k * 2 <= N (N 为堆中元素个数)时,比较 item[k]max{ item[k * 2],item[k * 2 + 1]} 的大小,并记录 item[k * 2]item[k * 2 + 1] 较大值的下标 maxIndex
  • item[k] < item[maxIndex],交换两者位置,k = maxIndex
  • item[k] >= maxIndex,下沉结束

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


// 使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k){/*通过循环不断的对比当前 k 结点和其左子结点 2 * k 以及右子结点 2 * k + 1处中的较大值的元素大小*/while(2 * k <= N){// 获取当前结点的子结点中的较大结点// 记录较大结点所在的索引int maxIndex;// 判断是否有右孩子结点if (2 * k + 1 <= N){maxIndex = less(2 * k,2 * k + 1) ? 2 * k + 1 : 2 * k;}else {maxIndex = 2 * k;}// 比较当前结点和较大结点的值if (!less(k, maxIndex)){break;}// 交换 k 索引处的值和 maxIndex 索引处的值exch(k, maxIndex);// 变换k的值k = maxIndex;}
}

② 小根堆的下沉

思路:

  1. 确定需要下沉元素的下标 k
  2. k * 2 <= N (N 为堆中元素个数)时,比较 item[k]min{ item[k * 2],item[k * 2 + 1]} 的大小,并记录 item[k * 2]item[k * 2 + 1] 较小值的下标 minIndex
  • item[k] > item[maxIndex],交换两者位置,k = minIndex
  • item[k] <= maxIndex,下沉结束

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


// 使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k){/*通过循环不断的对比当前 k 结点和其左子结点 2 * k 以及右子结点 2 * k + 1处中的较小值的元素大小*/while(2 * k <= N){// 获取当前结点的子结点中的较小结点// 记录较小结点所在的索引int minIndex;// 判断是否有右孩子结点if (2 * k + 1 <= N){minIndex = less(2 * k,2 * k + 1) ? 2 * k : 2 * k + 1;}else {minIndex = 2 * k;}// 比较当前结点和较小结点的值if (less(k, minIndex)){break;}// 交换 k 索引处的值和 minIndex 索引处的值exch(k, minIndex);// 变换k的值k = minIndex;}
}

(5)堆的构造

堆的构造,最直观的想法就是另外再创建一个新数组,然后从左往右遍历原数组,每得到一个元素后,添加到新数组中,并通过上浮,对堆进行调整,最后新的数组就是一个堆。
上述的方式虽然很直观,也很简单,但是我们可以用更聪明一点的办法完成它。创建一个新数组,把原数组[0 ~ length -1]的数据拷贝到新数组的 [1 ~ length]处,再从新数组长度的一半处开始往 1 索引处扫描(从右往左),然后对扫描到的每一个元素做下沉调整即可。

以下代码以构建大根堆为例

private static Comparable[] createHeap(Comparable[] source) {Comparable[] heap = new Comparable[source.length + 1];// 把source中的元素拷贝到heap中,heap中的元素就形成一个无序的堆System.arraycopy(source,0,heap,1,source.length);// 对堆中的元素做下沉调整(从长度的一半处开始,往索引1处扫描)for (int i = (heap.length)/2;i>0;i--){sink(heap,i,heap.length-1);}return heap;
}// 在heap堆中,对target处的元素做下沉,范围是0~range
private static void sink(Comparable[] heap, int target, int range){while(2 * target <= range){// 找出当前结点的较大的子结点int maxIndex;if (2*target+1<=range){if (less(heap,2*target,2*target+1)){maxIndex = 2*target+1;}else{maxIndex = 2*target;}}else{maxIndex = 2*target;}//2.比较当前结点的值和较大子结点的值if (!less(heap,target, maxIndex)){break;}exch(heap,target, maxIndex);target = maxIndex;}
}

(6)堆排序

这里以实现数据的升序为例

实现步骤:

  1. 构造堆
  2. 得到堆顶元素,这个值就是最大值
  3. 交换堆顶元素和数组中的最后一个元素,此时所有元素中的最大元素已经放到合适的位置
  4. 对堆进行调整,重新让除了最后一个元素的剩余元素中的最大值放到堆顶
  5. 重复2~4这个步骤,直到堆中剩一个元素为止

对于堆的构造,上述已经谈到,对构造好的堆,我们只需要做类似于堆的删除操作,就可以完成排序。

  1. 将堆顶元素和堆中最后一个元素交换位置;
  2. 通过对堆顶元素下沉调整堆,把最大的元素放到堆顶(此时最后一个元素不参与堆的调整,因为最大的数据已经到了数组的最右边)
  3. 重复1~2步骤,直到堆中剩最后一个元素。
public class HeapSort {// 对source数组中的数据从小到大排序public static  void sort(Comparable[] source) {// 构建堆Comparable[] heap = createHeap(source);// 定义一个变量,记录未排序的元素中最大的索引int N = heap.length - 1;// 通过循环,交换1索引处的元素和排序的元素中最大的索引处的元素while(N != 1){// 交换元素exch(heap,1, N);// 排序交换后最大元素所在的索引,让它不要参与堆的下沉调整N --;// 需要对索引1处的元素进行对的下沉调整sink(heap,1, N);}// 把heap中的数据复制到原数组source中System.arraycopy(heap,1, source,0, source.length);}// 根据原数组source,构造出堆heapprivate static Comparable[] createHeap(Comparable[] source) {Comparable[] heap = new Comparable[source.length + 1];// 把source中的元素拷贝到heap中,heap中的元素就形成一个无序的堆System.arraycopy(source,0,heap,1,source.length);// 对堆中的元素做下沉调整(从长度的一半处开始,往索引1处扫描)for (int i = (heap.length)/2;i>0;i--){sink(heap,i,heap.length-1);}return heap;}// 在heap堆中,对target处的元素做下沉,范围是0~rangeprivate static void sink(Comparable[] heap, int target, int range){while(2 * target <= range){// 找出当前结点的较大的子结点int maxIndex;if (2*target+1<=range){if (less(heap,2*target,2*target+1)){maxIndex = 2*target+1;}else{maxIndex = 2*target;}}else{maxIndex = 2*target;}//2.比较当前结点的值和较大子结点的值if (!less(heap,target, maxIndex)){break;}exch(heap,target, maxIndex);target = maxIndex;}}// 判断heap堆中索引i处的元素是否小于索引j处的元素private static  boolean less(Comparable[] heap, int i, int j) {return heap[i].compareTo(heap[j]) < 0;}// 交换heap堆中i索引和j索引处的值private static  void exch(Comparable[] heap, int i, int j) {Comparable tmp = heap[i];heap[i] = heap[j];heap[j] = tmp;}
}

三、优先队列

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在某些情况下,我们可能需要找出队列中的最大值或者最小值,例如使用一个队列保存计算机的任务,一般情况下计算机的任务都是有优先级的,我们需要在这些计算机的任务中找出优先级最高的任务先执行,执行完毕后就需要把这个任务从队列中移除。普通的
队列要完成这样的功能,需要每次遍历队列中的所有元素,比较并找出最大值,效率不是很高,这个时候,我们就可以使用一种特殊的队列来完成这种需求,优先队列

优先队列按照其作用不同,可以分为以下两种:

  • 最大优先队列:

可以获取并删除队列中最大的值

  • 最小优先队列:

可以获取并删除队列中最小的值

(1)最大优先队列实现

这里我们以实现构建最大优先队列为例,最大优先队列就是以大根堆实现的。

public class MaxPriorityQueue<T extends Comparable<T>> {// 存储堆中的元素private T[] items;// 记录堆中元素的个数private int N;public MaxPriorityQueue(int capacity) {this.items = (T[]) new Comparable[capacity+1];this.N = 0;}// 获取队列中元素的个数public int size() {return N;}// 判断队列是否为空public boolean isEmpty() {return N == 0;}// 判断堆中索引i处的元素是否小于索引j处的元素private boolean less(int i, int j) {return items[i].compareTo(items[j]) < 0;}// 交换堆中i索引和j索引处的值private void exch(int i, int j) {T tmp = items[i];items[i] = items[j];items[j] = tmp;}// 往堆中插入一个元素public void insert(T t) {items[++N] = t;swim(N);}// 删除堆中最大的元素,并返回这个最大元素public T delMax() {T max = items[1];exch(1, N);N --;sink(1);return max;}// 使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置private void swim(int k) {while(k > 1){if (less(k / 2, k)){exch(k / 2, k);}k = k / 2;}}// 使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置private void sink(int k) {while(2 * k <= N){int maxIndex;if (2 * k + 1 <= N){if (less(2 * k,2 * k + 1)){maxIndex = 2 * k + 1;}else{maxIndex = 2*k;}}else {maxIndex = 2*k;}if (!less(k, maxIndex)){break;}exch(k, maxIndex);k = maxIndex;}}
}

最小优先队列就是以小根堆实现,只需更改上述代码的上浮与下沉代码,更改判断结点与父结点、子结点的标准即可。


http://chatgpt.dhexx.cn/article/uDFYFcF2.shtml

相关文章

JVM JAVA中的堆

堆的核心概述&#xff1a; 一个进程对应一个JVM的实例&#xff0c;一个JVM实例中只有一个运行时数据区&#xff0c;里面只有一个方法区和堆&#xff0c;一个进程的多个线程共享方法区和堆&#xff0c;那就要考虑线程的安全问题。 每个线程各有一套程序计数器、本地方法栈、虚拟…

Java堆是如何划分的?

写在前面 本文隶属于专栏《100个问题搞定Java虚拟机》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和文献引用请见100个问题搞定Java虚拟机 解答 Java 虚拟机将堆划分为…

数据结构-堆和堆的Java实现

1. 定义 堆&#xff08;英语&#xff1a;heap)是计算机科学中一类特殊的数据结构的统称。 堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质&#xff1a; 1.堆中某个节点的值总是不大于或不小于其父节点的值&#xff1b; 2.堆总是一棵完全二叉树。 常见的堆有二…

Java数据结构之堆(Heap)

文章目录 一、基本概念二、上浮操作(siftUp)三、下沉操作(siftDown)四、数组堆化五、实现大根堆 提示&#xff1a;以下是本篇文章正文内容&#xff0c;Java系列学习将会持续更新 一、基本概念 堆在逻辑上就是一棵完全二叉树。堆在物理上是储存在数组中。满足任意根节点值>…

JVM--堆

45. JVM--堆 1. 堆的核心概述 一个进程对应一个JVM的实例&#xff0c;一个JVM实例中只有一个运行时数据区&#xff0c;里面只有一个方法区和堆&#xff0c;一个进程的多个线程共享方法区和堆&#xff0c;那就要考虑线程的安全问题。 每个线程各有一套程序计数器、本地方法栈、虚…

java中的堆

文章目录 一、堆的分类二、详述Java堆中各个区域1、堆大小2、新生代 ( Young ) 的划分3、工作原理4、GC日志 一、堆的分类 Java 中的堆是 JVM 管理的最大的一块内存空间&#xff0c;主要用于存放Java类的实例对象 其被划分为两个不同的区域&#xff1a;新生代 ( Young )和老年…

Java数据结构--堆

一.堆的定义及本质 要知道在Java中&#xff0c;堆是以优先级队列来表示的。因此学会了堆&#xff0c;我们也就学会优先级队列 1.堆的定义 当一颗二叉树的每个节点都大于等于它的两个子字节时&#xff0c;它被称为堆有序。而堆是一组能够用堆有序的完全二叉树排序的元素&…

Java数据结构之堆

堆的概念 堆逻辑上是一棵完全二叉树堆物理上是保存在数组中满足任意结点的值都大于其子树中结点的值&#xff0c;叫做大堆&#xff0c;或者大根堆&#xff0c;或者最大堆反之&#xff0c;则是小堆&#xff0c;或者小根堆&#xff0c;或者最小堆堆的基本作用是快速找集合中的最…

常用三极管引脚定义

给大家总结了常用封装的三极管的引脚定义 配套的视频讲解 常用三极管引脚定义

常用电子器件 ——三极管

说明&#xff1a;   本文章旨在总结备份、方便以后查询&#xff0c;由于是个人总结&#xff0c;如有不对&#xff0c;欢迎指正&#xff1b;另外&#xff0c;内容大部分来自网络、书籍、和各类手册&#xff0c;如若侵权请告知&#xff0c;马上删帖致歉。   QQ 群 号&#xf…

三极管开关特性

三极管在我们数字电路和模拟电路中都有大量的应用&#xff0c;在我们开发板上也用了多个三极管。在我们板子上的 LED 小灯部分&#xff0c;就有这个三极管的应用了&#xff0c;图 3-5 的 LED 电路中的 Q16就是一个 PNP 型的三极管。 图 3-5 LED 电路 三极管的初步认识 三极管…

常用三极管入门级电路设计(如何选型和搭电路?)

转自&#xff1a; 常用的三极管电路设计-电阻到底是怎么选的&#xff08;修正后&#xff09;-面包板社区 硬件基础知识---如何设计一个三极管放大电路_学无止境的专栏-CSDN博客_uce怎么算 今天的内容超级简单&#xff0c;主要给硬件新手写点东西&#xff0c;关于三极管实用方…

三极管相关知识点

1.两种类型的三极管图示如下&#xff0c;基极&#xff08;Base&#xff09;、集电极&#xff08;Collector&#xff09;和发射极&#xff08;Emitter&#xff09;&#xff0c;等效二极管电路如图所示 2.三极管的几种工作状态&#xff0c; 3.三极管做开关功能时&#xff0c;工…

三极管的一些基本知识

导通条件 NPN型三极管的导通条件是C点电位>B点电位>E点电位&#xff0c;三极管饱和导通的条件是Ub>Ue,Ub>Uc。 PNP型三极管的导通条件是E点电位>B点电位>C点电位&#xff0c;三极管饱和导通的条件是Ue>Ub,Uc>Ub。 NPN型三极管 1、定义&#xff1a; …

三极管的几点应用

关注星标公众号&#xff0c;不错过精彩内容 直接来源 | 巧学模电数电单片机 在复杂多变的模拟电路中&#xff0c;少不了学习三极管&#xff0c;过往的血泪史&#xff0c;不想再提&#xff0c;什么正偏反偏都给我统统滚蛋&#xff01;今天来点实际的。 三极管有三个工作状态&…

【三极管】

文章目录 一、图片&#xff08;NPN - PNP&#xff09;二、导通、截至、饱和条件&#xff08;一&#xff09;NPN&#xff08;二&#xff09;PNP 三、三极管的作用&#xff08;一&#xff09;开关作用 1、同或、异或、或非、与非—真值表 ① 同或&#xff1a;相同为1&#…

三极管常用封装的引脚排列

描述 三极管具有三个引脚&#xff0c;定义分别为基极b、集电极c、发射极e&#xff0c;在设计电路和设计封装时&#xff0c;这三个引脚的顺序必须和封装对应一致&#xff0c;否则电路无法正常工作&#xff0c;三极管常用的封装有&#xff1a;直插TO-92&#xff0c;贴片SOT-23、S…

电子元器件篇---三极管

目录 简介 三极管基本参数 2.1、封装形式 2.2、特征频率 2.3、工作电压/电流 2.4、电流放大倍数 2.5、集电极发射极反向击穿电压 2.6、最大允许耗散功率 三极管种类 三极管用途 4.1共射放大电路 4.2共集放大电路 4.3共基放大电路 4.4开关和反相电路 4.5稳压作用 简…

三极管的基础知识(上)

一、半导体三极管 1.作用 主要用于放大电路、开关电路。 2.三极管的结构与类型 &#xff08;1&#xff09;结构&#xff1a;三极管有两个PN结&#xff08;集电结、发射结&#xff09;、三个区&#xff08;集电区、基区、发射区&#xff09;、三个电极&#xff08;集电极、基…

三极管

三极管 一.三极管介绍参考资料 二. 三极管的作用1.开关来使用 2.二级驱动3.放大信号4.三极管还可以用来反向5.隔离参考来源 pnp型三极管电压总结 一.三极管介绍 三极管是最重要的电子元器件之一&#xff0c;成功制作世界上第一只半导体三极管的美国物理学家约翰巴丁(John Bard…