🍓 简介:java系列技术分享(👉持续更新中…🔥)
🍓 初衷:一起学习、一起进步、坚持不懈
🍓 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正🙏
🍓 希望这篇文章对你有所帮助,欢迎点赞 👍 收藏 ⭐留言 📝🍓 更多文章请点击
文章目录
- 一、堆的简介
- 二、堆的实现
- 2.1 insert插入方法的实现
- 2.2 删除最大元素方法的实现
- 2.3 代码实现
- 2.4 测试-新增和删除最大值
- 三、堆排序
- 3.1 实现思路
- 3.2 堆构造过程
- 3.3 堆排序过程
- 3.4 代码实现
- 3.5 测试
一、堆的简介
堆通常可以被看做是一棵完全二叉树的数组对象。
堆的特性:
- 它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。
- 它通常用数组来实现。
-
具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子结点的子结点则分别在位置4,5,6和7,以此类推。
-
如果一个结点的位置为k,则它的
父结点的位置为[k/2]
,而它的两个子结点的位置则分别为2k和2k+1
。这样,在不使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:从a[k]向上一层,就令k等于k/2,向下一层就令k等于2k或2k+1。 -
每个结点都大于等于它的两个子结点
。这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个子结点的顺序并没有做规定
,跟我们之前学习的二叉查找树是有区别的。
-
二、堆的实现
2.1 insert插入方法的实现
堆是用
数组
完成数据元素的存储的,但是堆中对元素的顺序是有要求的,每一个结点的数据要大于等于它的两个子结点的数据,所以每次插入一个元素,都会使得堆中的数据顺序变乱,这个时候我们就需要通过一些方法让刚才插入的这个数据放入到合适的位置。
所以,如果往堆中新插入元素,我们只需要不断的比较新结点a[k]和它的父结点a[k/2]的大小,然后根据结果完成数据元素的交换,就可以完成堆的有序调整。
2.2 删除最大元素方法的实现
- 由堆的特性我们可以知道,
索引1处的元素,也就是根结点就是最大的元素
,当我们把根结点的元素删除后,需要有一个新的根结点出现,这时我们可以暂时把堆中最后一个元素放到索引1处
,充当根结点,但是它有可能不满足堆的有序性需求,这个时候我们就需要通过一些方法,让这个新的根结点放入到合适的位置。 所以,当删除掉最大元素后,只需要将最后一个元素放到索引1处,并不断的拿着当前结点a[k]与它的子结点a[2k]和a[2k+1]中的较大者交换位置,即可完成堆的有序调整。
2.3 代码实现
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) {items[++N] = t;swim(N);}//使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置private void swim(int k) {//通过循环,不断的比较当前结点的值和其父结点的值,如果发现父结点的值比当前结点的值小,则交换位置while (k > 1) {//比较当前结点和其父结点if (less(k / 2, k)) {exch(k / 2, k);}k = k / 2;}}//删除堆中最大的元素,并返回这个最大元素public T delMax() {T max = items[1];//交换索引1处的元素和最大索引处的元素,让完全二叉树中最右侧的元素变为临时根结点exch(1, N);//最大索引处的元素删除掉items[N] = null;//元素个数-1N--;//通过下沉调整堆,让堆重新有序sink(1);return max;}//使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置private void sink(int k) {//通过循环不断的对比当前k结点和其左子结点2*k以及右子结点2k+1处中的较大值的元素大小,如果当前结点小,则需要交换位置while (2 * k <= N) {//获取当前结点的子结点中的较大结点int max;//记录较大结点所在的索引if (2 * k + 1 <= N) {if (less(2 * k, 2 * k + 1)) {max = 2 * k + 1;} else {max = 2 * k;}} else {max = 2 * k;}//比较当前结点和较大结点的值if (!less(k, max)) {break;}//交换k索引处的值和max索引处的值exch(k, max);//变换k的值k = max;}}
}
2.4 测试-新增和删除最大值
public static void main(String[] args) {Heap<String> heap = new Heap<String>(20);heap.insert("A");heap.insert("B");heap.insert("C");heap.insert("D");heap.insert("E");heap.insert("F");heap.insert("G");String del;while ((del = heap.delMax()) != null) {System.out.print(del + ",");}}
三、堆排序
String[] arr = {“S”,“O”,“R”,“T”,“F”,“X”,“A”,“M”,“P”,“L”,“E”},请对数组中的字符按从小到大排序。
3.1 实现思路
-
构造堆;
-
得到堆顶元素,这个值就是最大值;
-
交换堆顶元素和数组中的最后一个元素,此时所有元素中的最大元素已经放到合适的位置;
-
对堆进行调整,重新让除了最后一个元素的剩余元素中的最大值放到堆顶;
-
重复2~4这个步骤,直到堆中剩一个元素为止。
3.2 堆构造过程
创建一个新数组,把原数组的数据拷贝到新数组,再从新数组长度的一半
处开始往1索引处扫描(从右往左),然后对扫描到的每一个元素做下沉
调整即可。
3.3 堆排序过程
对构造好的堆,我们只需要做类似于堆的删除操作,就可以完成排序。
-
将堆顶元素和堆中最后一个元素交换位置
-
通过对堆顶元素下沉调整堆,把最大的元素放到堆顶(此时最后一个元素不参与堆的调整,因为最大的数据已经到了数组的最右边)
-
重复1~2步骤,直到堆中剩最后一个元素。
3.4 代码实现
public class HeapSort {//判断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;}//根据原数组source,构造出堆heapprivate static void createHeap(Comparable[] source, Comparable[] heap) {//把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);}}//对source数组中的数据从小到大排序public static void sort(Comparable[] source) {//构建堆 因为0索引废弃,所以长度加1Comparable[] heap = new Comparable[source.length + 1];createHeap(source, heap);//定义一个变量,记录未排序的元素中最大的索引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);}//在heap堆中,对target处的元素做下沉,范围是0~rangeprivate static void sink(Comparable[] heap, int target, int range) {while (2 * target <= range) {//1.找出当前结点的较大的子结点int max;if (2 * target + 1 <= range) {if (less(heap, 2 * target, 2 * target + 1)) {max = 2 * target + 1;} else {max = 2 * target;}} else {max = 2 * target;}//2.比较当前结点的值和较大子结点的值if (!less(heap, target, max)) {break;}exch(heap, target, max);target = max;}}}
3.5 测试
public class HeapSortTest {public static void main(String[] args) {//待排序数组String[] arr = {"S","O","R","T","F","X","A","M","P","L","E"};//通过HeapSort对数组中的元素进行排序HeapSort.sort(arr);//打印排序后数组中的元素System.out.println(Arrays.toString(arr));}
}