堆
堆的实现代码
下面展示一些 堆代码基本功能的实现
。
package Heap;
/**
* @author Fantic
* @create 2021-08-17 10:59
*/
public class Heap<T extends Comparable<T>> {
//用来存储元素的数组
private T[] items;
//记录堆中元素的个数
private int Num;
//创建容量为capacity的Heap对象
public Heap(int capacity) {
this.items = (T[]) new Comparable[capacity + 1];
Num = 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){
//由于有对元素转换为数组,便于调用,将索引0处废止,起始存储索引位置向前一位
items[++Num] = t;
//插入元素后使用,由于元素大小问题,采用上浮算法将元素移到适当的位置
swim(Num);
}
//使用上浮算法,将索引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,Num);
//将交换后的最大元素的索引处的元素删除
items[Num] = null;
//元素个数减1
Num--;
//通过下沉算法,让堆重新排序
sink(1);
return max;
}
//使用下沉算法,将索引k处的元素能在堆中处于一个正确的位置
private void sink(int k){
//通过循环不断的对比当前k结点和其左子结点2*k以及右子节点2*k+1处中的较大值的元素大小
while (2*k <= Num) {
//获取当前结点的子节点中的较大值
//记录较大结点所在索引值
int max;
if (2 * k + 1 <= Num){
if (less(2 * k,2 * k + 1)){
max = 2 * k + 1;
}else {
max = 2 * k;
}
}else {
max = 2 * k;
}
//比较当前结点和最大结点的值,如果k的值比子结点最大值大,直接退出循环,没有下沉的必要
if (!less(k,max)) {
break;
}
//交换k结点和最大子结点的值
exch(k,max);
//变换k的值,与接下来的子结点进行二次比较
k = max;
}
}
public static void main(String[] args) {
Heap<String> heap = new Heap<String>(15);
heap.insert("A");
heap.insert("B");
heap.insert("C");
heap.insert("D");
heap.insert("E");
heap.insert("F");
heap.insert("G");
heap.insert("H");
String del;
while ((del = heap.delMax()) != null){
System.out.print(del + " ");
}
}
}
1.1 堆的定义
堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵完全二叉树的数组对象。
堆的特性: 1.它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。
2.它通常用数组来实现。具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子结点的子结点则分别在位置4,5,6和7,以此类推。 (用数组实现堆,对应索引0位置不放入元素)
如果一个结点的位置为k,则它的父结点的位置为[k/2],而它的两个子结点的位置则分别为2k和2k+1。这样,在不使用指针的情况下,我们也可以通过计算数组的索引在树中上下移动:从a[k]向上一层,就令k等于k/2,向下一层就令k等于2k或2k+1。
3.每个结点都大于等于它的两个子结点。这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个子结点的顺序并没有做规定,跟我们之前学习的二叉查找树是有区别的。
1.2 堆的API设计
1.3 堆的实现
1.3.1 insert插入方法的实现
堆是用数组完成数据元素的存储的,由于数组的底层是一串连续的内存地址,所以我们要往堆中插入数据,我们只能往数组中从索引0处开始,依次往后存放数据,但是堆中对元素的顺序是有要求的,每一个结点的数据要大于等于它的两个子结点的数据,所以每次插入一个元素,都会使得堆中的数据顺序变乱,这个时候我们就需要通过一些方法让刚才插入的这个数据放入到合适的位置。
所以,如果往堆中新插入元素,我们只需要不断的比较新结点a[k]和它的父结点a[k/2]的大小,然后根据结果完成数据元素的交换,就可以完成堆的有序调整。
1.3.2 delMax删除最大元素方法的实现
由堆的特性我们可以知道,索引1处的元素,也就是根结点就是最大的元素,当我们把根结点的元素删除后,需要有一个新的根结点出现,这时我们可以暂时把堆中最后一个元素放到索引1处,充当根结点,但是它有可能不满足堆的有序性需求,这个时候我们就需要通过一些方法,让这个新的根结点放入到合适的位置。
所以,当删除掉最大元素后,只需要将最后一个元素放到索引1处,并不断的拿着当前结点a[k]与它的子结点a[2k]和a[2k+1]中的较大者交换位置,即可完成堆的有序调整。
1.4 堆排序
给定一个数组:
String[] arr = {“S”,“O”,“R”,“T”,“E”,“X”,“A”,“M”,“P”,“L”,“E”}
请对数组中的字符按从小到大排序。
实现步骤:
1.构造堆;
2.得到堆顶元素,这个值就是最大值;
3.交换堆顶元素和数组中的最后一个元素,此时所有元素中的最大元素已经放到合适的位置;
4.对堆进行调整,重新让除了最后一个元素的剩余元素中的最大值放到堆顶;
5.重复2~4这个步骤,直到堆中剩一个元素为止。
API设计:
1.4.1 堆构造过程
堆的构造,最直观的想法就是另外再创建一个和新数组数组,然后从左往右遍历原数组,每得到一个元素后,添加到新数组中,并通过上浮,对堆进行调整,最后新的数组就是一个堆。
上述的方式虽然很直观,也很简单,但是我们可以用更聪明一点的办法完成它。创建一个新数组,把原数组0 ~ length-1的数据拷贝到新数组的1~ length处,再从新数组长度的一半处开始往1索引处扫描(从右往左),然后对扫描到的每一个元素做下沉调整即可。
1.4.2 堆排序过程
对构造好的堆,我们只需要做类似于堆的删除操作,就可以完成排序。
1.将堆顶元素和堆中最后一个元素交换位置;
2.通过对堆顶元素下沉调整堆,把最大的元素放到堆顶(此时最后一个元素不参与堆的调整,因为最大的数据已经到了数组的最右边)
3.重复1~2步骤,直到堆中剩最后一个元素。
下面展示一些 堆排序代码功能实现
。
package Heap;
import java.util.Arrays;
/**
* @author Fantic
* @create 2021-08-17 11:00
*/
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 temp = heap[i];
heap[i] = heap[j];
heap[j] = temp;
}
//根据原数组source,构造出堆heap
private static void creatHead(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){
//构建堆
Comparable[] heap = new Comparable[source.length + 1];
creatHead(source,heap);
//定义一个变量,交换索引1和最大索引处的元素
int num = heap.length - 1;
while(num != 1){
//交换元素
exch(heap,1,num);
//交换元素,删除最大索引处元素
num--;
//对调序后的元素进行下沉
sink(heap,1,num);
}
//把heap中的数据复制到原数组中
System.arraycopy(heap,1,source,0,source.length);
}
//在heap堆中,对target处的元素做下沉,范围是0 ~ range
private static void sink(Comparable[] heap,int target,int range){
while (target * 2 <= 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;
}
}
public static void main(String[] args) {
//待排序的数组
String[] array = {"S","O","R","T","E","X","A","M","P","L","E"};
//通过heapSort对数组中的元素进行排序
HeapSort.sort(array);
System.out.println(Arrays.toString(array));
}
}