堆排序
1. 算法概述
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。
堆分两种
- 大顶堆:每个节点的值都大于或等于其子节点的值
- 小顶堆:每个节点的值都小于或等于其子节点的值
- 每一个节点下,左右元素大小关系不做要求
其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。堆排序的平均时间复杂度为 Ο(nlogn)。
2. 算法原理
- 将初始待排序关键字序列(R1,R2…Rn)构建成大顶堆,此堆为初始的无序区
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,…Rn-1)和新的有序区(Rn)
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,…Rn-1)调整为新堆
- 然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2…Rn-2)和新的有序区(Rn-1,Rn)。
- 不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成
3. 动图演示
4. 代码实现
创建最大堆代码
private void buildMaxHeap(int[] arr, int len) {
/*
任意一节点指针 i(parent):父节点:i==0 ? null : (i-1)/2
左孩子(left):2*i + 1
右孩子(right):2*i + 2
n个节点的完全二叉树array[0,...,n-1],最后一个节点n-1是第(n-1-1)/2个节点的孩子。
对第(n-1-1)/2个节点为根的子树调整,使该子树称为堆。
*/
for (int i = (len - 1) >> 1; i >= 0; i--) {
heapify(arr, i, len);
}
}
/**
* 重新组装堆,将数组符合堆的要求
*
* @param arr
* @param parent
* @param len
*/
private void heapify(int[] arr, int parent, int len) {
int left = 2 * parent + 1;
int right = 2 * parent + 2;
//最大数保存位置
int largest = parent;
//如果左节点 > 父节点,记录最大值为左节点
if (left < len && arr[left] > arr[largest]) {
largest = left;
}
//如果有节点 > 上步记录的节点,记录最大值为右节点
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
//else 最大值为父节点
//如果最大值不为父节点,则交换最大值所在节点和父节点的位置
if (largest != parent) {
//交换位置
swap(arr, parent, largest);
//重复上述步骤,递归建堆
heapify(arr, largest, len);
}
}
排序代码实现
@Test
public void sort() {
arr = new int[]{2, 1, 4, 3, 7, 5, 6};
int len = arr.length;
//建立堆
buildMaxHeap(arr, len);
for (int i = len - 1; i > 0; i--) {
//移除堆顶部最大值到尾部节点
swap(arr, 0, i);
//删除最后排序序的尾节点
len--;
//重新构造堆
heapify(arr, 0, len);
}
log.info("排序后:{}", JSON.toJSONString(arr));
}