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

article/2025/11/7 14:42:04

1. 定义

堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。

堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树。

常见的堆有二叉堆、斐波那契堆等。

堆的定义:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2)

堆是一颗完全二叉树,在这棵树中,所有父节点都满足大于等于其子节点的堆叫大根堆,所有父节点都满足小于等于其子节点的堆叫小根堆。
堆虽然是一颗树,但是通常存放在一个数组中,父节点和孩子节点的父子关系通过数组下标来确定。如下图的小根堆及存储它的数组:
在这里插入图片描述

在这里插入图片描述

从图中,我们可以很容易总结出,通过一个节点在数组中的索引怎么计算出它的父节点及左右孩子节点的索引?下面直接给出对应的Java代码:

public int left(int i) {return (i + 1) * 2 - 1;}public int right(int i) {return (i + 1) * 2;}public int parent(int i) {// i为根结点if (i == 0) {return -1;}return (i - 1) / 2;}

2. Java算法

计算一个节点的父节点和左右孩子节点的索引对大根堆和小根堆都是一样的。要构建一个堆,除了知道怎么计算一个节点的父节点和孩子节点的索引外,我们还需要两个算法,即保持堆的性质和建堆。
我们将看到建堆的方法对大根堆和小根堆也是一样的。接下来看看“保持堆的性质”是个什么意思?
保持堆的性质,对大根堆和小根堆很相似,但不完全一样,所以得分开说。

大根堆

对大根堆来说是,已经存在两个大根堆,现在要把一个元素作为这两个大根堆根的父节点,构成一个新的堆,但是这个堆的根结点可能不满足大根堆的性质,也就是说,它可能比它的孩子节点小,所以需要对它进行操作,操作的方式就是,我们从这个节点和它的孩子节点中选出最大的,如果最大的节点是这个节点本身,堆就已经满足大根堆性质了,否则,将这个节点与最大节点交换,交换后该节点在新的位置上也可能违背大根堆性质,所以需要递归的进行,直至这个节点比孩子节点都大或者是子节点为止(这个过程也叫做元素下降,因为元素从根结点开始一步一步往下移)。下图是算法导论中给出大根堆保持堆的性质的伪代码:
在这里插入图片描述

对应的Java代码如下:

public void heapify(T[] a, int i, int heapLength) {int l = left(i);int r = right(i);int largest = -1;/*** 下面两个if条件句用来找到三个元素中的最大元素的位置largest; * l < heapLength说明l在数组内,i非叶子结点;*/if (l < heapLength && a[i].compareTo(a[l]) < 0) {largest = l;} else {largest = i;}// r < heapLength说明r在数组内if (r < heapLength && a[largest].compareTo(a[r]) < 0) {largest = r;}// 如果i处元素不是最大的,就把i处的元素与最大处元素交换,交换会使元素下降if (i != largest) {T temp = a[i];a[i] = a[largest];a[largest] = temp;// 交换元素后,以a[i]为根的树可能不在满足大根堆性质,于是递归调用该方法heapify(a, largest, heapLength);}}

小根堆

对小根堆,保持堆的性质,过程是,已经存在两个小根堆,现在要把一个元素作为这两个堆的根,作为新的小根堆,但是这个元素可能会违背小根堆的性质,所以需要将它下降,也就是说,不断将它与它和孩子节点中的最小节点交换,直到它是它和它孩子节点中最小的或者是叶子节点为止。对应的Java代码如下:

public void heapify(T[] a, int i, int heapLength) {int l = left(i);int r = right(i);int smallest = -1;/*** 下面两个if条件句用来找到三个元素中的最小元素的位置smallest; * s < heapLength说明l在数组内,i非叶子结点;*/if (l < heapLength && a[i].compareTo(a[l]) > 0) {smallest = l;} else {smallest = i;}// r < heapLength说明r在数组内if (r < heapLength && a[smallest].compareTo(a[r]) > 0) {smallest = r;}// 如果i处元素不是最小的,就把i处的元素与最小处元素交换,交换会使元素下降if (i != smallest) {T temp = a[i];a[i] = a[smallest];a[smallest] = temp;// 交换元素后,以a[i]为根的树可能不在满足大根堆性质,于是递归调用该方法heapify(a, smallest, heapLength);}}

创建堆

有了上面的这些准备工作,我们终于可以建堆了。正如前面所说,建堆的过程对大根堆和小根堆是一样的。我们可以把单个元素看作大根堆或者小根堆。假设数组中最后一个堆元素的下标为i,则数组中从0下标开始,最后一个有孩子节点的元素就是j=parent(i)。于是,我们从j到0,对一个元素都调用heapify方法,堆就建好了。下图是算法导论给出的伪代码:
在这里插入图片描述

对应的Java代码如下:

public  void buildHeap(T[] a, int heapLength) {// 从后往前看,lengthParent - 1处的元素是第一个有孩子节点的节点int lengthParent = parent(heapLength - 1);// 最初,parent(length)之后的所有元素都是叶子结点;// 因为大于length/2处元素的孩子节点如果存在,那么// 它们的数组下标值必定大于length,这与事实不符;// 在数组中,孩子元素必定在父亲元素的后面,从后往前// 对元素调用maxHeapify,保证了元素的孩子都是// 大根堆for(int i = lengthParent; i >= 0; i--){heapify(a, i, heapLength);}}

关于堆的用途,可能大家最熟悉堆排序了,除此之外,我们可以用堆构建优先级队列,因为我们可以在常数时间内获得堆中(优先级)最大或者最小的元素,这对于优先级队列来说是很重要的。

3. 优先队列(PriorityQueue)的二叉堆

insert(插入)

根据优先队列的模型,二叉堆实现的优先队列应该具备插入操作,
二叉堆插入元素14的情况。
在这里插入图片描述

为将一个元素 X 插入到堆中,我们在下一个可用位置创建一个空穴,否则该堆将不是完全数。如果 X 可以放在该空穴中而不破坏堆的序,那么插入完成。否则,我们把空穴的父节点上的元素。移入该空穴中,这样,空穴就朝着根的方向上冒一步。继续改过程直到 X 能被放入空穴中为止。这种实现过程称之为上滤:新元素在堆中上滤直到找出正确的插入位置。

public void insert( Comparable x ) throws Overflow{//这里没有进行扩容处理if( isFull( ) )throw new Exception( );// Percolate upint hole = ++currentSize;//上滤,首先找到插入位置,之后元素交换一次for( ; hole > 1 && x.compareTo( array[ hole / 2 ] ) < 0; hole /= 2 )array[ hole ] = array[ hole / 2 ];array[ hole ] = x;}

deleteMin(删除最小元)

在这里插入图片描述

当删除一个最小元时,要在根节点建立一个空穴。由于现在堆少了一个元素,因此堆中最后一个元素 X 必须移动到该堆的某个地方。如果 X 可以直接被放到空穴中,那么 deleteMin 完成。不过这一般不太可能,因此我们将空穴的两个儿子中比较小者移入空穴,这样就把空穴向下推了一层。重复该步骤直到 X 可以被放入空穴中。因此,我们的做法是将 X 置入沿着从根开始包含最小儿子的一条路径上的一个正确的位置。

源码实现:

public Comparable deleteMin( ){if( isEmpty( ) )return null;Comparable minItem = findMin( );array[ 1 ] = array[ currentSize-- ];percolateDown( 1 );return minItem;}
private void percolateDown( int hole ){int child;Comparable tmp = array[ hole ];for( ; hole * 2 <= currentSize; hole = child ){child = hole * 2;if( child != currentSize &&array[ child + 1 ].compareTo( array[ child ] ) < 0 )child++;if( array[ child ].compareTo( tmp ) < 0 )array[ hole ] = array[ child ];elsebreak;}array[ hole ] = tmp;}

4. 整体代码

public class BinaryHeap {public BinaryHeap() {this(DEFAULT_CAPACITY);}public BinaryHeap(Comparable[] items) {currentSize = items.length;array = new Comparable[(currentSize + 2) * 11 / 10];int i = 1;for (Comparable item : items) {array[i++] = item;}buildHeap();}public BinaryHeap(int capacity) {currentSize = 0;array = new Comparable[capacity + 1];}public void insert(Comparable x) {// Percolate upint hole = ++currentSize;for (; hole > 1 && x.compareTo(array[hole / 2]) < 0; hole /= 2)array[hole] = array[hole / 2];array[hole] = x;}public Comparable findMin() {if (isEmpty())return null;return array[1];}public Comparable deleteMin() {if (isEmpty())return null;Comparable minItem = findMin();array[1] = array[currentSize--];percolateDown(1);return minItem;}private void buildHeap() {for (int i = currentSize / 2; i > 0; i--)percolateDown(i);}public boolean isEmpty() {return currentSize == 0;}public boolean isFull() {return currentSize == array.length - 1;}public void makeEmpty() {currentSize = 0;}private static final int DEFAULT_CAPACITY = 100;private int currentSize;      // Number of elements in heapprivate Comparable[] array; // The heap arrayprivate void percolateDown(int hole) {int child;Comparable tmp = array[hole];//保存变量for (; hole * 2 <= currentSize; hole = child) {child = hole * 2;//获取左子树结点//如果左子树结点不是堆的长度并且左子树大于右子树的值if (child != currentSize && array[child + 1].compareTo(array[child]) < 0)child++;//child指向右子树if (array[child].compareTo(tmp) < 0)//如果孩子比tmp小array[hole] = array[child];//交换值elsebreak;}array[hole] = tmp;}// Test programpublic static void main(String[] args) {int numItems = 10;BinaryHeap h = new BinaryHeap(numItems);int i = 37;try {for (i = 37; i != 0; i = (i + 37) % numItems){h.insert(i);}for (int j=1;j<=h.currentSize;j++){System.out.print(h.array[j]+" ");}System.out.println("\n"+h.findMin());h.deleteMin();for (int j=1;j<=h.currentSize;j++){System.out.print(h.array[j]+" ");}} catch (Exception e) {System.out.println("Overflow (expected)! " + i);}}
}

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

相关文章

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…

三极管与mos管通俗讲解

文章目录 一、三极管1.结构2.应用 二、mos管1.结构及原理2.mos管通断3.mos管最常用作用4.应用5.MOS管的栅极和源极之间的电阻 一、三极管 1.结构 电流控制型元器件&#xff0c;只要基极B有输入&#xff08;或输出&#xff09;电流就可以对这个晶体管进行控制了 三极管有三个…

三极管基础知识

二极管的导电特性 1. 正向偏置 在电子电路中&#xff0c;将二极管的正极接在高电位端&#xff0c;负极接在低电位端&#xff0c;二极管就会导通&#xff0c;这种连接方式&#xff0c;称为正向偏置。必须说明&#xff0c;当加在二极管两端的正向电压很小时&#xff0c;…

三极管基础分类, 参数选择及常见型号对比

三极管基础 具体的原理就不说了, N型和P型都是通过硅和锗参杂不同的元素带来的电子富余或缺少从而产生内在的电动势, 这里说的是如何帮助记忆. 半导体类型 P型半导体, p-type Semiconductor, p-type 这个名称代表的是 the positive charge of a hole, 知道这个就很好记忆了N…