一.前提
1.为简单起见,讨论从小到大的整数排序
2.只讨论基于比较的排序(< = >有定义)
3.只讨论内部排序
4.稳定性:任意两个相等的数据,排序前后相对位置不发生变化
二.堆排序定义
堆排序(Heapsort) 是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
堆排序是选择排序的优化:选择排序
三.分类
1.算法1
(1)实现
根据传进来的数组创建一个最小堆,最小堆的第一个元素一定是最小的,所以我们每次删除根节点(数组第一个元素),把根节点放入我们新建的数组中,最后得到的新数组就是一个排好序的。
注意:每次删除操作不仅仅是删除根节点,删除之后要调整数组,使数组重新满足最小堆的要求
java提供了一个数据类型PriorityQueue-优先级队列,使用动态数组实现了最小堆,我们可以直接用。 (当然我们也可以自己实现最小堆)
//数据结构-最小堆
static int[] Heap_Sort(int[] array){
int len = array.length;
//创建最小堆,PriorityQueue内部维护的数组默认长度是11
//为防止给的数组长度大于11,进堆时要扩展内部数组的长度
//所以初始化时,确定内部数组的长度等于我们传入的数组的长度
Queue<Integer> minHeap = new PriorityQueue<>(len);
for(int e : array){
minHeap.add(e);
}
//把堆顶(最小元素)投出,存在新数组中
for(int i = 0; i < len; i++){
array[i] = minHeap.poll();
}
return array;
}
(2)时间复杂度
//nlog(n)
for(int e : array){
minHeap.add(e);
}
//nlog(n)
for(int i = 0; i < len; i++){
array[i] = minHeap.poll();
}
- 两个都是nlongn,选一个来讲
- foreach循环:T1(n) = O(n)
- add()函数: (具体可以去看堆的插入)
T2(n) = O(logn) - 总的时间复杂度:T(n) = O(nlogn)
(3)空间复杂度
Queue<Integer> minHeap = new PriorityQueue<>(len);
额外创建了一个新的数组
- 空间复杂度:S(n) = O(n)
(4)说明
1.这个算法使用最大堆也可以
2.重要的是,这个方法额外创建了一个新的数组
3.所以我们优化后的算法2就是只用一个数组
2.算法2
(1)实现
想要只用一个数组,就不能使用PriorityQueue了,我们要自己写最大堆的实现。
//因为我没新建测试类,直接在本类测试了所以方法字段前面都加了static
//当前堆的大小(不是数组的大小)
static int size = 0;
//堆排序
static void Heap_Sort(int[] array){
//数组为null或者数组元素小于等于1,不用排序
if(null == array ||1 >= array.length) return;
size = array.length;
//建堆(就是把数组元素调整为符合最大堆的顺序)
buildMaxHeap(array);
//循环
//把堆的根节点和堆的最后一个元素互换,堆的大小减一
//此时根节点的左右子树是满足最大堆的定义的,只有根节点需要重新调整
//根节点一路向下比较,直到满足最大堆的定义
//当堆的大小为1时,不用比较了
while(size > 0){
swap(array, 0, size-1);
size--;
adjustHeap(array, 0);
}
}
//建堆
static void buildMaxHeap(int[] array){
//从倒数第一个有孩子的节点开始调整
for(int i = size/2-1; i >= 0; i--){
adjustHeap(array, i);
}
}
//调整堆
//默认左右子树已经是堆了
//现在要调整根节点
static void adjustHeap(int[] array, int root) {
//循环,当根节点有左孩子的时候循环
//孩子必须在堆里,即孩子的下标不能超过size-1
//而左孩子下标为2*root+1,再计算下就可以得到循环条件
while((2*root + 2) <= size){
//根节点和左孩子比
int maxIndex = (array[root] < array[2*root+1]) ? 2*root +1 :root;
//如果有右节点,上面比的那个最大值再和右孩子比
if(2*root+1 < size-1 )
maxIndex = (array[2*root+2] > array[maxIndex]) ? (2*root + 2) : (maxIndex);
//如果根节点不是最大值,就还要循环
if(root != maxIndex){
swap(array, root, maxIndex);
root = maxIndex;
}
else break;
}
}
//交换数组元素
static void swap(int[] array, int i1, int i2) {
int temp = array[i1];
array[i1] = array[i2];
array[i2] = temp;
}
(2)时间复杂度
while(size > 0){
swap(array, 0, size-1);
size--;
adjustHeap(array, 0);
}
- while循环:T1(n) = O(n)
- adjustHeap()函数:
T2(n) = O(logn) - 总的时间复杂度:T(n) = O(nlogn)
(3)空间复杂度
没有额外创建一个新的数组
- 空间复杂度:S(n) = O(1)