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.mzph.cn/news/604378.shtml

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

相关文章

听GPT 讲Rust源代码--compiler(33)

File: rust/compiler/rustc_middle/src/macros.rs 在Rust源代码中&#xff0c;rust/compiler/rustc_middle/src/macros.rs文件的作用是定义了一些用于宏展开的辅助宏和宏规则。 首先&#xff0c;这个文件导入了许多其他相关的模块&#xff0c;这些模块定义了编译器内部所需要的…

详解Java中的原子操作

第1章&#xff1a;什么是原子操作 大家好&#xff0c;我是小黑&#xff0c;面试中一个经常被提起的话题就是“原子操作”。那么&#xff0c;到底什么是原子操作呢&#xff1f;在编程里&#xff0c;当咱们谈论“原子操作”时&#xff0c;其实是指那些在执行过程中不会被线程调度…

1329:【例8.2】细胞 广度优先搜索

1329&#xff1a;【例8.2】细胞 时间限制: 1000 ms 内存限制: 65536 KB 【题目描述】 一矩形阵列由数字0 到9组成,数字1到9 代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。如: 4 10 0234500067 1034560500 2045600671 00000000…

最新-mybatis-plus 3.5分页插件配置

mybatis-plus 3.5分页插件配置 前提 1.项目不是springboot, 是以前的常规spring项目 2.mp 从3.2升级到3.5&#xff0c;升级后发现原本的分页竟然不起作用了&#xff0c;每次查询都是查出所有 前后配置对比 jar包对比 jsqlparser我这里单独引了包&#xff0c;因为版本太低…

数据结构入门到入土——链表(2)

目录 一&#xff0c;与链表相关的题目&#xff08;2&#xff09; 1.输入两个链表&#xff0c;找出它们的第一个公共节点 2.给定一个链表&#xff0c;判断链表中是否有环 3.给定一个链表&#xff0c;返回链表开始入环的第一个节点&#xff0c;若无则返回null 一&#xff0c;…

java字节码

1. 字节码 1.1 什么是字节码&#xff1f; Java之所以可以“一次编译&#xff0c;到处运行”&#xff0c;一是因为JVM针对各种操作系统、平台都进行了定制&#xff0c;二是因为无论在什么平台&#xff0c;都可以编译生成固定格式的字节码&#xff08;.class文件&#xff09;供…

机器学习模型可解释性的结果分析

模型的可解释性是机器学习领域的一个重要分支&#xff0c;随着 AI 应用范围的不断扩大&#xff0c;人们越来越不满足于模型的黑盒特性&#xff0c;与此同时&#xff0c;金融、自动驾驶等领域的法律法规也对模型的可解释性提出了更高的要求&#xff0c;在可解释 AI 一文中我们已…

案例介绍|钡铼助力2023年全国职业院校技能大赛工业网络智能控制与维护赛项

如今&#xff0c;越来越多的企业开始意识到数字制造和工业物联网已经成为工业自动化中大规模生产的核心驱动力。这其中&#xff0c;工业网络作为基础设施&#xff0c;是实现工厂设备联网与数据采集&#xff0c;建设数字工厂的基础和前提&#xff0c;甚至成为关乎数字工厂能否真…

给您的应用添加弹窗

概述 在我们日常使用应用的时候&#xff0c;可能会进行一些敏感的操作&#xff0c;比如删除联系人&#xff0c;这时候我们给应用添加弹窗来提示用户是否需要执行该操作&#xff0c;如下图所示&#xff1a; 弹窗是一种模态窗口&#xff0c;通常用来展示用户当前需要的或用户必须…

thinkphp学习02-目录结构、控制器、路由、配置文件

目录结构 www WEB部署目录&#xff08;或者子目录&#xff09; ├─app 应用目录 │ ├─controller 控制器目录 │ ├─model 模型目录 │ ├─ ... 更多类库目录 │ │ │ ├─common.php 公共函数文件 │ └─event.ph…

鸿蒙OS:不止手机,是物联网应用开发

鸿蒙开发是华为自主研发的面向全场景的分布式操作系统&#xff0c;旨在将生活场景中各类终端进行整合&#xff0c;实现不同终端设备间的快速连接、资源共享、匹配合适设备、提供流畅的全场景体验。 鸿蒙开发具有以下特点&#xff1a; 面向全场景&#xff1a;鸿蒙系统能够覆盖…

【51单片机】独立按键控制LED灯

不同于上篇文章只用代码控制&#xff0c;这次我们要再加上独立按键一同控制LED灯 目录 独立按键控制LED亮灭&#xff1a;代码实现&#xff1a; 独立按键控制LED状态&#xff1a;代码实现&#xff1a; 独立按键实现二进制LED显示&#xff1a;代码实现&#xff1a; 独立按键控制…

超声波模块的驱动(STM32、51单片机等)

一、前言 本文旨在分享单片机对超声波模块的驱动&#xff0c;测量距离和显示 二、超声波的驱动 1、超声波模块 2、模块性能 &#xff08;1&#xff09;TCT40-16T/R1 压电陶瓷超声传感器&#xff08;通用型&#xff09; 3、接口定义 Vcc、 Trig&#xff08;控制端&#xff09…

FreeRTOS移植详解

一、前言 本文旨在讲解FreeRTOS在STM32单片机上的移植步骤&#xff0c;对于FreeRTOS在其他单片机上的移植已具有一定的参考意义。相信读者在看完这篇文章后&#xff0c;一定会有所收获&#xff01; 文末附有相关资料连接&#xff0c;有需要的读者可以自行下载。 二、FreeRTOS源…

OpenSource - 基于Netty的网络扩展库HServer

文章目录 概述官网Hserver的理念特点原理图代码案例HelloWorld 概述 HServer是一个基于Netty开发网络扩展库.使用插件方式来扩展我们的业务 HServer提供 web,gateway,rpc 等插件 同时用户也可以自定义插件&#xff0c;来完成各种各样的业务场景。 官网 https://gitee.com/HSe…

1018:奇数偶数和1028:I love 闰年!和1029:三角形判定

1018&#xff1a;奇数偶数 要求&#xff1a;输入一个整数&#xff0c;判断该数是奇数还是偶数。如果该数是奇数就输出“odd”&#xff0c;偶数就输出“even”&#xff08;输出不含双引号&#xff09;。 输入样例&#xff1a;8 输出样例&#xff1a;even 程序流程图&#xff1a…

支付宝扫码(Easy版)支付实现

文章目录 一 技术准备1.1 二维码技术&#xff08;java&#xff09;1.2 支付宝沙箱环境准备1.3 内网穿透 二 支付宝支付相关知识2.1 各种支付方式2.2 扫码付接入流程2.3 系统交互流程(时序图)2.4 加密逻辑 三 扫码支付实现3.1 添加maven依赖&#xff08;Easy版&#xff09;3.2 完…

揭秘六大热门认证考试

六大热门认证考试是什么❓今天为大家详细解读PMP、ACP、CDGA、软考中项、软考高项、NPDP、CISP等热门认证考试&#xff0c;让你不再彷徨&#x1f447; 1️⃣PMP &#x1f451;PMP认证是全qiu公ren的项目管理专业认证&#xff0c;旨在评估项目管理人员在项目过程中所需的知识、技…

05 Ciso模拟器连接腾讯云物联网开发平台

Ciso声明&#xff1a;本篇文章基于使用腾讯云物联网平台连接自定义esp8266物联网设备(腾讯连连控制开关实现) - CSDN App改编 一、总体概览 功能描述&#xff1a; 使用腾讯连连小程序进行控制&#xff0c; Alarm&#xff08;警铃&#xff09;&#xff1a;开的时候&#xff…

【软考中级-软件设计师】day3:程序设计语言基础知识

概述 练习题 程序设计语言的基本成分 练习题 编译程序基本原理 名词解释 词法分析 词法分析&#xff08;英语&#xff1a;lexical analysis&#xff09;是计算机科学中将字符序列转换为单词&#xff08;Token&#xff09;序列的过程。进行词法分析的程序或者函数叫作…