算法与数据结构(六):堆
博主会对算法与数据结构会不断进行更新,敬请期待,如有什么建议,欢迎联系。
堆的特性:
堆的定义:
堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵完全二叉树的数组对象。
堆的特性
- 它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。
- 它通常用数组来实现。
具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子结点的子结点则分别在位置4,5,6和7,以此类推。 如果一个结点的位置为k,则它的父结点的位置为[k/2],而它的两个子结点的位置则分别为2k和2k+1。这样,在不使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:从a[k]向上一层,就令k等于k/2,向下一层就令k等于2k或2k+1。- 每个结点都大于等于它的两个子结点。这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个子结点的顺序并没有做规定,跟我们之前学习的二叉查找树是有区别的 。
堆的实现细节:
- 堆实现最主要的就是堆的上浮与下沉算法,由于父节点总是比子节点大;
- 堆插入数据总是在数组的末尾插入,所以,当堆插入数据时,就要对新插入的数据进行上浮;
- 当堆取出最大数据时,首先让头节点和最后的节点进行互换,堆的size-1,然后,让头节点进行下沉;
堆的实现代码如下:
package com.victor.heap;
/**
* @description: 堆的实现
* @author: victor
* @date: 2020/12/12 15:11
*/
public class Heap<T extends Comparable<T>> {
//定义一个数组
private T[] items;
//元素的个数
private int N;
@SuppressWarnings("unchecked")
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的元素
*
* @param i 坐标为i
* @param j 坐标为j
*/
private void exchange(int i, int j) {
T temp = items[i];
items[i] = items[j];
items[j] = temp;
}
/**
* 往堆中插入一个元素
*
* @param item 要插入的元素
*/
public void insert(T item) {
items[++N] = item;
swim(N);
}
/**
* 使用上浮算法,使堆中下标为k的元素处于正确的位置
*
* @param k 下标为k的元素
*/
private void swim(int k) {
//当k不为根节点时,上浮
while (k > 1) {
if (less(k / 2, k)) exchange(k / 2, k);
else break; //当k/2节点大于k节点时,停止循环
k = k / 2;
}
}
/**
* 删除最大的元素
*
* @return 删除的最大元素
*/
public T delMax() {
T max = items[1];
//让最大的元素与最后的元素交换
exchange(1, N);
//删除最大元素
items[N] = null;
N--;
sink(1);
return max;
}
/**
* 采用下沉算法,使堆中下标为k的元素处于正确的位置
*
* @param k 下标为k的值
*/
private void sink(int k) {
while (2 * k <= N) {
//获取当前节点子节点最大的节点
int max; //记录最大的节点
if (2 * k + 1 <= N) {
max = less(2 * k, 2 * k + 1) ? 2 * k + 1 : 2 * k;
} else {
max = 2 * k;
}
//如果最大的子节点小于k节点,则结束循环
if (less(max, k)) break;
//k节点与最大子节点进行交换
exchange(k, max);
//循环下一个节点
k = max;
}
}
}
- 由于,堆的父节点总是大于它的子节点,使用堆的第一个值总是最大的值,所以,根据这个规律可以运用的堆的知识对数组进行排序,因此,堆的排序代码如下:
package com.victor.heap;
/**
* 堆排序
*
* @description:
* @author: victor
* @date: 2021/1/22 10:02
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class HeapSort {
private static boolean less(Comparable[] heap, int i, int j) {
return heap[i].compareTo(heap[j]) < 0;
}
private static void exchange(Comparable[] heap, int i, int j) {
Comparable cache = heap[i];
heap[i] = heap[j];
heap[j] = cache;
}
/**
* 对数组进行排序
* 1. 首先创建一个堆数组
* 2. 对堆进行下沉操作,形成堆
* 3. 将首节点与尾结点进行交换,交换后进行首节点下沉,下沉的范围排除交换之前的首节点
* 4. 下沉之后,排除尾结点,循环3
*
* @param source 源数组
*/
public static void sort(Comparable[] source) {
Comparable<Object>[] heap = new Comparable[source.length + 1];
createHeap(source, heap);
int N = heap.length - 1;
while (N != 1) {
exchange(heap, 1, N);
N--;
sink(heap, 1, N);
}
System.arraycopy(heap, 1, source, 0, source.length);
}
/**
* 根据原数组source构造出数组heap
*
* @param source 原数组
* @param heap 堆
*/
private static void createHeap(Comparable[] source, Comparable[] heap) {
System.arraycopy(source, 0, heap, 1, source.length);
for (int i = (heap.length - 1) / 2; i > 0; i--) {
sink(heap, i, heap.length - 1);
}
}
/**
* 下沉算法
*
* @param heap 堆
* @param target 下沉的节点
* @param range 要下沉的范围
*/
private static void sink(Comparable[] heap, int target, int range) {
while (2 * target <= range) {
int max = 2 * target;
int right;
if ((right = max + 1) <= range) //存在右子节点
max = less(heap, max, right) ? right : max;
if (less(heap, target, max))
exchange(heap, max, target);
else
break;
target = max;
}
}
}
- 这里实现了堆的由大到小的排序,当然,堆也可以实现有小到大的排序,只要对上浮和下沉算法进行修改就能实现,这里不再赘述;