堆排序
- 一、堆排序
-
-
- 1、什么是堆结构?
- 2、堆分类
- 3、堆排序实现思路(以大根堆为例)
- 4、实现代码
- 5、借助对数器测试代码正确性
- 6、测试结果
- 7、时间复杂度
- 二、堆排序扩展题目
-
一、堆排序
1、什么是堆结构?
- 简单来说,堆结构就是使用数组实现的完全二叉树。
- 完全二叉树在满足二叉树的一些要求后,还需要满足只有最后一层允许孩子不满的情况出现,且孩子必须满足先有左孩子再有右孩子。
- 我们常说的优先级队列,实际上就是堆结构。
2、堆分类
- 大根堆:完全二叉树中每一个子树的 最大值 均是该子树的根节点,满足该要求的称为大根堆。
- 小根堆:与大根堆刚好相反,完全二叉树中每一个子树的 最小值 均是该子树的根节点。
3、堆排序实现思路(以大根堆为例)
- 建堆,先让整个数组呈现出大根堆的结构。
- 将堆的最大值(即完全二叉树的根节点)与堆末尾元素互换,同时将堆元素个数减 1 ,互换之后会打破大根堆结构,调整堆结构使其再次呈现出大根堆结构,再将调整后堆的最大值与堆的末尾元素交换,重复上述操作,直至堆中没有元素,排序完成。
4、实现代码
public class HeapSort { public static void heapInsert(int[] arr,int index){ while (arr[index] > arr[(index-1) / 2]){ swap(arr,index,(index - 1) / 2); index = (index - 1) / 2; } } public static void heapify(int[] arr,int index,int size){ int left = index * 2 + 1; while (left < size){ int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left; largest = arr[largest] > arr[index] ? largest : index; if (largest == index){ break; } swap(arr,largest,index); index = largest; left = (index * 2) + 1; } } public static void heapSort(int[] arr){ if (arr == null || arr.length < 2){ return; } for (int i = 0; i < arr.length; i++){ heapInsert(arr,i); } int size = arr.length; swap(arr,0,--size); while (size > 0){ heapify(arr,0,size); swap(arr,0,--size); } } public static void swap(int[] arr,int i,int j){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }}
- 在堆排序过程中,存在两个非常重要的操作:建堆和调整堆。
5、借助对数器测试代码正确性
public class HeapSort { public static void main(String[] args) { int testTime = 500000; int maxSize = 100; int maxValue = 100; boolean succeed = true; for (int i = 0; i < testTime; i++){ int[] arr1 = MyComparator.generateRandomArray(maxSize, maxValue); int[] arr2 = MyComparator.copyArray(arr1); heapSort(arr1); MyComparator.comparator(arr2); if (!MyComparator.isEqual(arr1,arr2)){ succeed = false; MyComparator.printArray(arr1); MyComparator.printArray(arr2); break; } } System.out.println(succeed ? "恭喜你,测试成功了!" : "很遗憾,失败了,代码存在问题!"); int[] arr = MyComparator.generateRandomArray(maxSize, maxValue); System.out.println("排序前数组:"); MyComparator.printArray(arr); heapSort(arr); System.out.println("排序后数组:"); MyComparator.printArray(arr); }}
6、测试结果
7、时间复杂度
- 时间复杂度 O(N*logN)。
- 分析堆排序的时间复杂度,只需要分析建堆和调整堆过程的时间复杂度即可。
二、堆排序扩展题目
1、题目描述
- 已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过 K ,并且 K 相对于数组来说比较小。
- 请选择一个合适的排序算法针对这个数组进行排序。
2、解题思路
- 因为题目说了每个元素移动的距离不超过 K ,那我们就可以取出下标从 0 到 K 的元素,将这些元素建立小根堆,得到的对顶元素即为整个数组的最小值(为什么其就是最小值呢?因为每个元素移动的距离是不超过 K 的,所以,下标为 K + 1 的元素不可能移动到下标为 0 的位置,故前 K 个元素一定能够找到最小值)。
- 寻找到最小值之后,堆顶元素弹出,将 K + 1 后的元素入堆,调整为小根堆,找到当前堆中的最小值,弹出,继续添加后面元素,每次添加进去一个。
- 如果最后没有元素添加,则不添加,堆顶元素弹出后,将末尾元素移至堆顶,继续调整,直至堆中没有元素存在,排序结束。
3、实现代码
public class DistanceLessK { public static void distanceLessK(int[] arr,int k){ PriorityQueue<Integer> heap = new PriorityQueue<>(); int index = 0; for (; index < Math.max(arr.length, k); index++){ heap.add(arr[index]); } int i = 0; for (; index < arr.length; i++, index++){ heap.add(arr[index]); arr[i] = heap.poll(); } while (!heap.isEmpty()){ arr[i++] = heap.poll(); } }}
- 当在排序过程中没有特殊要求时,可以使用 Java 提供的 PriorityQueue 类,该类的实质就是小根堆,里面提供了许多封装好的方法,我们可以直接调用使用,如:add 方法,poll 方法,我们我们再手动实现。