Java 堆的设计,如何用堆进行排序

Java 学习+面试指南:https://javaxiaobear.cn

1、堆的定义

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

1、堆的特性

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

  2. 它是由数组实现的。就是将二叉树的结点按层级顺序放入数组中,根节点在位置1,它的子节点在2和3,以此类推;

    如果一个节点的位置为k,则它的父节点的位置为k/2,而它的两个子节点的位置分别为2k和2k+1

    我们可以通过计算数组的索引在树中上下移动,从a[k]向上一层,就令 k = k/2,向下一层就令k = 2k/ k = 2k+1

  3. 每个节点都大于等于它的两个子节点

    ps:这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个子结点的顺序并没有做规定,跟我们之前学习的二叉查找树是有区别的。

2、堆的设计

1、API设计

类名Heap<>
构造方法Heap(int capacity):创建容量为capacity的Heap对象
成员方法private boolean less(int i,int j):判断堆中索引i处的元素是否小于索引j处的元素
private void exch(int i,int j):交换堆中i索引和j索引处的值
public T delMax():删除堆中最大的元素,并返回这个最大元素
public void insert(T t):往堆中插入一个元素
private void swim(int k):使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k):使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
成员变量private T[] items : 用来存储元素的数组
private int N:记录堆中元素的个数

2、插入方法实现

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

总结:如果往堆中新插入元素,我们只需要不断的比较新结点a[k]和它的父结点a[k/2]的大小,然后根据结果完成数据元素的交换,就可以完成堆的有序调整。

3、删除方法实现

由堆的特性我们可以知道,索引1处的元素,也就是根结点就是最大的元素,当我们把根结点的元素删除后,需要有一个新的根结点出现,这时我们可以暂时把堆中最后一个元素放到索引1处,充当根结点,但是它有可能不满足堆的有序性需求,这个时候我们就需要通过一些方法,让这个新的根结点放入到合适的位置。

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

4、完整代码

public class Heap<T extends Comparable<T>> {/*** 存储元素*/private T[] items;/*** 堆元素个数*/private int size;public Heap(int capacity){items = (T[]) new Comparable[capacity];size = 0;}/*** 判断堆中索引i的值是否小于索引j处的值* @param i* @param j* @return*/private boolean less(int i, int j){return items[i].compareTo(items[j]) < 0;}/*** 交换索引i和索引j的值* @param i* @param j*/private void each(int i, int j){T temp = items[i];items[i] = items[j];items[j] = temp;}/*** 插入一个元素* @param t*/public void insert(T t){items[++size] = t;//上浮,重新排序swim(size);}/*** 上浮算法,使索引k处的元素能在堆中处于一个正确的位置* @param k*/public void swim(int k){//从叶子节点往上面循环,到根节点就结束循环while (1 < k){//比较当前节点与父节点if(less(k/2, 2)){//父结点小于当前结点,需要交换each(k/2,k);}k = k/2;}}/*** 删除堆中最大的元素* @return max*/public T delMax(){//获取最大元素T max = items[1];//交换索引1处和索引size处的值each(1,size);//交换后置nullitems[size] = null;size--;//将索引1的值下沉sink(1);return max;}/*** 下沉算法,使索引k的元素位于堆中一个正确的位置* @param k*/private void sink(int k){//如果到底层就结束循环 2*k > sizewhile(2*k <= size){int max;//是否存在右子节点if(2*k + 1 <= size){//if (less(2*k,2*k+1)){max = 2*k + 1;}else {max = 2*k;}}else { //不存在右节点max = 2*k;}//比较当前结点和子结点中的较大者,如果当前结点不小,则结束循环if(!less(k,max)){break;}each(k, max);k = max;}}
}

测试类

public class HeapTest {public static void main(String[] args){Heap<String> heap = new Heap<String>(20);heap.insert("S");heap.insert("G");heap.insert("I");heap.insert("E");heap.insert("N");heap.insert("H");heap.insert("O");heap.insert("A");heap.insert("T");heap.insert("P");heap.insert("R");String del;while ((del= heap.delMax()) != null){System.out.print(del + " ");}}
}

3、堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。

String[] arr = {"S","O","R","T","E","X","A","M","P","L","E"};

实现步骤

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

API设计

类名HeapSort
成员方法public static void sort(Comparable[] source):对source数组中的数据从小到大排序
private static void createHeap(Comparable[] source, Comparable[] heap):根据原数组source,构造出堆heap
private static boolean less(Comparable[] heap, int i, int j):判断heap堆中索引i处的元素是否小于索引j处的元素
private static void exch(Comparable[] heap, int i, int j):交换heap堆中i索引和j索引处的值
private static void sink(Comparable[] heap, int target, int range):在heap堆中,对target处的元素做下沉,范围是0~range。

1、堆构造过程

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

2、堆排序过程

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

  1. 将堆顶元素和堆中最后一个元素交换位置;
  2. 通过对堆顶元素下沉调整堆,把最大的元素放到堆顶(此时最后一个元素不参与堆的调整,因为最大的数据已经到了数组的最右边)
  3. 重复1~2步骤,直到堆中剩最后一个元素。

3、完整代码

public class HeapSort {/*** 判断堆中索引i处的值是否小于索引j的值* @param heap 堆* @param i 索引i* @param j 索引j* @return true or false*/private static boolean less(Comparable[] heap, int i, int j){return heap[i].compareTo(heap[j]) < 0;}/*** 交换索引i和索引j处的值* @param heap* @param i* @param j*/private static void swap(Comparable[] heap, int i, int j){Comparable temp = heap[i];heap[i] = heap[j];heap[j] = temp;}/*** 根据原数组source,构造出堆heap* @param source 原数组* @param heap 目标数组*/private static void createHeap(Comparable[] source, Comparable[] heap){//把source中的数据拷贝到heap中,从heap的1索引处开始填充System.arraycopy(source,0,heap,1,source.length);//从heap索引的一半处开始倒叙遍历,对得到的每一个元素做下沉操作for (int i = (heap.length - 1) / 2; i > 0; i--) {sink(heap,i, heap.length - 1);}}/*** 对source数组中从小到大排序* @param source*/public static void sort(Comparable[] source){//创建一个比原数组大1的数组Comparable[] heap = new Comparable[source.length + 1];//创造堆createHeap(source,heap);int n = heap.length - 1;while (1 != n){//交换heap中索引1处的元素和N处的元素swap(heap, 1, n);n--;//对索引1处的元素在0~N范围内做下沉操作sink(heap, 1, n);}//heap中的数据已经有序,拷贝到source中System.arraycopy(heap,1,source,0,source.length);}/*** 在heap堆中,对target处的元素做下沉,范围是0~range。* @param heap 堆* @param target 索引* @param range 范围*/private static void sink(Comparable[] heap, int target, int range){//没有子节点 退出循环while (2 * target <= range){//找出target结点的两个子结点中的较大值int max = 2*target;if(2 * target + 1 <= range){if (less(heap,2*target,2*target+1)){max = 2 * target + 1;}}//如果当前结点的值小于子结点中的较大值,则交换if(less(heap,target,max)){swap(heap, target, max);}//更新target的值target = max;}}public static void main(String[] args) {String[] arr = {"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"};HeapSort.sort(arr);System.out.println(Arrays.toString(arr));}
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/329599.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【AI】使用Jan.ai在本地部署大模型开启AI对话(含通过huggingface下载大模型,实现大模型自由)

文章目录 前言一、Jan.ai是什么&#xff1f;二、下载大模型1. 找到大模型文件地址2. 下载大模型3. 修改model.json文件 三、使用Jan调用大模型进行对话总结 前言 2023年是AIGC元年。以后&#xff0c;每个人多少都会接触到GPT带来的变化。别人都在用&#xff0c;我们也不能落下…

【解决】hosts文件无修改权限问题

1. 以管理员身份运行命令提示符&#xff08;cmd&#xff09;&#xff1a; 2. 在cmd中输入notepad进入记事本&#xff1a; 3. 通过记事本打开hosts文件&#xff1a; 4. 修改并保存&#xff1a;

超维空间M1无人机使用说明书——61、ROS无人机yolo识别与投放

引言&#xff1a;使用yolo进行物体识别&#xff0c;根据返回的目标位置信息&#xff0c;控制无人机全向移动&#xff0c;当无人机到达目标物的正上方&#xff0c;满足一定的误差阈值后&#xff0c;ROS发布控制控制将舵机挂载的物体抛下&#xff0c;紧接着无人机前往起飞点上方寻…

Spring Cloud之OpenFeign异常处理

简易原理图 原理基于请求头传递错误消息&#xff0c;利用aop和全局异常拦截机制实现。 服务提供者 远程调用本地方法b&#xff0c;throw异常出来FeignExceptionAspect AOP拦截处理异常到请求头中&#xff0c;继续throwGlobalExceptionHandler处理&#xff0c;返回响应Respons…

相控阵天线阵元波程差相位差计算

如下图所示&#xff0c;O点为相位为0的基准点&#xff0c;P(x,y)点为阵元所在位置&#xff0c;需要计算P点相对于基准点在波束方向上的相位差。OP2为波束方向&#xff0c;OP2与Z轴的角度为Theta&#xff0c;OP2在XOY的投影OP1与X轴的角度为Phi。 计算得到波程差OP2&#xff0c;…

机器学习:贝叶斯估计在新闻分类任务中的应用(实验报告)

文章摘要 随着互联网的普及和发展&#xff0c;大量的新闻信息涌入我们的生活。然而&#xff0c;这些新闻信息的质量参差不齐&#xff0c;有些甚至包含虚假或误导性的内容。因此&#xff0c;对新闻进行有效的分类和筛选&#xff0c;以便用户能够快速获取真实、有价值的信息&…

Java分布式锁理论(redis、zookeeper) 详解

目录 一、分布式锁有哪些应用场景&#xff1f; 二、分布式锁的实现方案 三、zookeeper实现分布式锁 一直不释放锁怎么办&#xff1f; 如何避免分布式锁羊群效应问题&#xff1f; 四、redis实现分布式锁 一、分布式锁有哪些应用场景&#xff1f; 1、定时任务 2、秒杀抢购…

Proteus 各版本安装指南

Proteus下载链接 https://pan.baidu.com/s/1vHgg8jK9KSHdxSU9SDy4vQ?pwd0531 1.鼠标右击【Proteus8.15(64bit&#xff09;】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到Proteus8.15(64bit&#xff09; 】。 2.打开解压后的文件夹&#…

dnSpy调试Web应用程序

文章目录 前言一、dnSpy是什么&#xff1f;二、如何使用dnSpy三、如何调试Web应用程序四、下载总结 前言 dnSpy是一个.NET程序集调试器和编辑器&#xff0c;主要用于调试和编辑没有源码的.NET程序集。 一、dnSpy是什么&#xff1f; dnSpy是一个.NET程序集调试器和编辑器&#…

Python武器库开发-武器库篇之子域名扫描器开发(四十一)

Python武器库开发-武器库篇之子域名扫描器开发(四十一) 在我们做红队攻防或者渗透测试的过程中&#xff0c;信息收集往往都是第一步的&#xff0c;有人说&#xff1a;渗透的本质就是信息收集&#xff0c;前期好的信息收集很大程度上决定了渗透的质量和攻击面&#xff0c;本文将…

C语言基础知识(6):UDP网络编程

UDP 是不具有可靠性的数据报协议。细微的处理它会交给上层的应用去完成。在 UDP 的情况下&#xff0c;虽然可以确保发送消息的大小&#xff0c;却不能保证消息一定会到达。因此&#xff0c;应用有时会根据自己的需要进行重发处理。 1.UDP协议的主要特点&#xff1a; &#xf…

【C++】STL 算法 ⑥ ( 二元谓词 | std::sort 算法简介 | 为 std::sort 算法设置 二元谓词 排序规则 )

文章目录 一、二元谓词1、二元谓词简介2、 std::sort 算法简介3、 代码示例 - 为 std::sort 算法设置 二元谓词 排序规则 一、二元谓词 1、二元谓词简介 " 谓词 ( Predicate ) " 是一个 返回 布尔 bool 类型值 的 函数对象 / 仿函数 或 Lambda 表达式 / 普通函数 , …