1. 简介
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树。
常见的堆有二叉堆、斐波那契堆等。
堆的定义:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2)
2. 算法演示
排序动画过程解释
-
首先,将所有的数字存储在堆中
-
按大顶堆构建堆,其中大顶堆的一个特性是数据将被从大到小取出,将取出的数字按照相反的顺序进行排列,数字就完成了排序
-
在这里数字 5 先入堆
-
数字 2 入堆
-
数字 7 入堆, 7 此时是最后一个节点,与最后一个非叶子节点(也就是数字 5 )进行比较,由于 7 大于 5 ,所以 7 和 5 交互
-
按照上述的操作将所有数字入堆,然后从左到右,从上到下进行调整,构造出大顶堆
-
入堆完成之后,将堆顶元素取出,将末尾元素置于堆顶,重新调整结构,使其满足堆定义
-
堆顶元素数字 7 取出,末尾元素数字 4 置于堆顶,为了维护好大顶堆的定义,最后一个非叶子节点数字 5 与 4 比较,而后交换两个数字的位置
-
反复执行调整+交换步骤,直到整个序列有序
3. 代码
/**
* Created by lizq on 2020/3/3.
*/
public class HeapSort {
public static void main(String[] args) {
long[] arrs = RandomArr.createLongArr(20, 0, 200);
long[] arrs2 = Arrays.copyOf(arrs, arrs.length);
Arrays.stream(arrs).forEach(l -> {
System.out.print(l + " ");
});
System.out.println();
sort(arrs);
Arrays.stream(arrs).forEach(l -> {
System.out.print(l + " ");
});
System.out.println();
Arrays.stream(arrs2).sorted().forEach(l -> {
System.out.print(l + " ");
});
}
public static void sort(long[] arrs) {
buildHeap(arrs);
int len = arrs.length;
for (int i = len - 1; i > 0; i--) {
swap(arrs, 0, i);
len--;
heapify(arrs, 0, len);
}
}
public static void buildHeap(long[] arrs) {
// 找到最后一个没有子树的节点
for (int i = (int) Math.floor(arrs.length / 2); i >= 0; i--) {
heapify(arrs, i, arrs.length);
}
}
public static void heapify(long[] arrs, int i, int len) {
int l = getLeft(i);
int r = getRight(i);
long tmp;
int largest = i;
// 如果左子树大于根节点,则把左子树设置最大值
if (l < len && arrs[l] > arrs[largest]) {
largest = l;
}
// 如果右子树大于根节点,则把右子树设置最大值
if (r < len && arrs[r] > arrs[largest]) {
largest = r;
}
if (largest != i) {
// 只交换最大那个分支
swap(arrs, i, largest);
// 递归修正换了节点那个分支
heapify(arrs, largest, len);
}
}
/**
* 获取左子节点的索引
*
* @param i
* @return
*/
public static int getLeft(int i) {
return i * 2 + 1;
}
/**
* 获取右子节点的索引
*
* @param i
* @return
*/
public static int getRight(int i) {
return i * 2 + 2;
}
/**
* 获取父亲节点
*
* @param i
* @return
*/
public static int getParent(int i) {
// 根节点
if (i == 0) {
return -1;
}
return (i - 1) / 2;
}
/**
* 交换元素
*
* @param arrs
* @param i
* @param j
*/
public static void swap(long[] arrs, int i, int j) {
// 交换元素
long tmp = arrs[i];
arrs[i] = arrs[j];
arrs[j] = tmp;
}
}
4. 总结
构建堆的过程就是把最大值(最小值) 从最底层升到最上层, 根节点一定是最大值(最小值)
排序过程把首节点和尾节点互换,然后对之前的再次进行从上倒下构造堆