十大经典排序算法江山图java
堆排序在排序复杂性的研究中有着重要的地位,由于他是咱们所知的惟一可以同时最优的利用空间和时间的方法,当空间十分紧张的时候(例如嵌入式系统或者低成本的移动设备中)他很流行,由于他只用几行就能实现较好的性能。可是现代操做系统中不多使用他,由于他没法利用缓存,这一点很致命
。数组元素不多和相邻的其余元素进行比较,所以缓存未命中的次数要远远高于大多数比较都在相邻元素间的算法,如快速排序,归并排序,甚至是希尔排序。面试
❝堆(英语:Heap)是计算机科学中的一种特别的彻底二叉树,知足特性"给定堆中任意节点P和C,若P是C的母节点,那么P的值会小于等于(或大于等于)C的值"。 摘自维基百科。算法
❞
首先堆是一个彻底二叉树(除了最后一层,其余层的节点个数都是满的,最后一层的节点都靠左排列),这点很重要,直接决定了数组下标和左右父节点的对应关系(关系往下看)。数组
最小堆(min heap):母节点的值恒小于等于子节点的值;缓存
最大堆(max heap):母节点的值恒大于等于子节点的值;ide
堆性能
值得注意,这个地方对哦左右子节点的大小没有要求。url
并且,堆通常用数组来存储,而不是树。由于树须要为其左右子节点分配指针空间,而数组使用数组下标来表示左右子节点的位置,能够说堆是被树耽误了的数组,顶着树的光环成功劝退了一大批想学的人,结果实际上干着数组该干的事儿,算法史上最大的骗局,挂羊肉卖狗肉
。spa
父节点和左右子节点在数组中的位置关系:操作系统
parent(i) = arr((i - 1)/2)
left(i) = 2i + 1
right(i) = 2i + 2
值得注意,搜索不是堆的目的,并非每个最小(大)堆都是一个有序数组!要将堆转换成有序数组,须要使用堆排序,此处欢迎咱们的堆排序隆重登场。
正由于堆只是父子之间大小关系肯定,左右子节点直接大小不肯定,因此才要堆排序将全部的元素有序排序。
以构建大顶堆为例:
堆排序
根据数组构造大顶堆:
构造大顶堆
一次调整以后7成功上位,获得数组最大的值7,与此同时数组对应的大顶堆构造完成。
由下至上的堆有序化
交换调整
这里能够看到,交换调整以后最大的元素到了最下面,最终会是越下面的数越大,所以构造大顶堆获得的是降序,升序要构造小顶堆。
import com.sun.tools.javac.util.ArrayUtils;
/**
* @author by zengzhiqin
* 2020-12-10
*/
public class HeapSort {
public static int[] heapSort (int[] arr) {
if (arr == null && arr.length == 0) {
throw new RuntimeException("数组不合法");
}
// 构建堆(从最下面叶子结点层的上一层,即倒数第二层的第一个开始进行构建调整)
for (int i = arr.length / 2 -1; i >= 0; i--) {
adjustDown(arr, i, arr.length);
}
// 循环调整下沉
for (int i = arr.length -1; i >= 0; i--) {
swap(arr, 0, i);
adjustDown(arr, 0, i);
}
return arr;
}
/**
* 调整
* @param arr
* @param parent
* @param length
*/
public static void adjustDown (int[] arr, int parent, int length) {
// 临时元素保存要下沉的元素
int temp = arr[parent];
// 左节点的位置
int leftChild = 2 * parent + 1;
// 开始往下调整
while (leftChild < length) {
// 若是右孩子节点比左孩子大,则定位到右孩子 (父节点只是比左右孩子都大,左右孩子大小不肯定)
if(leftChild + 1 < length && arr[leftChild] < arr[leftChild + 1]) {
// 此时leftChild其实是右孩子,但始终是左右里面最大的
leftChild ++ ;
}
// 大顶堆条件被破坏了
if (arr[leftChild] <= temp) {
break;
}
// 把子节点中大的值赋值给父节点
arr[parent] = arr[leftChild];
// 大的子节点为父节点,调整它的左右子节点
parent = leftChild;
leftChild = 2 * parent + 1;
}
arr[parent] = temp;
}
/**
* 交换数组中两个元素
* @param arr 数组
* @param i 父元素
* @param index 元素2
*/
public static void swap (int[] arr, int i, int index) {
int temp = arr[i];
arr[i] = arr[index];
arr[index] = temp;
}
public static void main(String[] args) {
//int[] arr = {5, 7, 8, 3, 1, 2, 4, 6, 8};
int[] arr = {3, 1, 2, 4};
//int[] arr = {1, 2, 3};
arr = heapSort(arr);
for (int i=0; i<arr.length; i++) {
System.out.println(arr[i]);
}
}
}
不稳定。
nlog(n)。
大顶堆构造使用 O(N) 的时间,元素调整下滤须要 O(logN),须要下滤 N-1 次,因此总共须要 O(N+(N-1)logN) = O(NlogN)。从过程能够看出,堆排序,无论最好,最坏时间复杂度都稳定在O(NlogN)。
原地排序,空间复杂度O(1)