将一个几乎有序的数组arr,排序好,几乎就是:要挪动i位置,最远不会挪动k个位置
提示:这是一个经典的使用堆排序的案例
本题的基础知识:
(1)堆,系统堆结构,手动改写堆结构,堆结构远比比堆排序更重要
题目
将一个几乎有序的数组arr,排序好,几乎就是:要挪动i位置,最远不会挪动k个位置(k<<远小于N)
一、审题
示例:6 1 2 3 4 5 7 8
0 1 2 3 4 5 6 7
不妨设k=5;也就是说,任意位置i的数,想排序挪动后前面后面的话,最多移动k次
比如本例,0位置的6,想移动冒泡到5后面,不断跟1 2 3 4 5交换,最大交换次数是k=5;
这就是几乎排序好的数组。
二、解题
此时如果我们说给你的缓存就很少,加之,你不能用系统排序函数来排,因为复杂度会高达o(n*log(n))
所以需要我们另做设计
(1)既然是最大移动k次,那我们想,要是将arr前面k+1个都放入小根堆heap,
岂不是就自然用log(k)的复杂度排序好了,k很小,自然这log(k)都可以忽略不计。
而且,我们不用把整个N全部都排序排一遍
(2)然后,我们怎么做呢,把heap弹出1个,让arr的0位置开始收集
(3)每次弹出1个,立马从arr中没进过堆的最左边那个位置放一个进堆,保证排序可能会移动的位置最大不会超过k
(4)不断循环整个过程,最终所有arr都进过堆,把堆全部弹出放入arr,完成排序!!!
(1)最开始k+1个,6个数,全放入heap中,第一次6做堆顶,依次来了1 2 3 4 5,然后6下沉了,1坐了堆顶,得到了绿色那个树
(2)弹出1,到arr0位置,将7放入小根堆heap,得到了蓝色那个树
(3)弹出2,到arr1位置,将8放入小根堆heap,得到了橘色那个树
(4)弹出3,到arr2位置,弹出4,去3位置,弹出5去4位置,弹出6去5位置,弹出7去6位置,弹出8去7位置,完成arr
arr几乎有序了,通过这种方式,每次加一个数,排序log(k)
咱们加N个数,所以用了o(n*log(k))<<o(nlog(n)) 【系统排序算法复杂度】
而且,既然只用了有限个额外空间,忽略不计,空间复杂度当o(1)
明白了吧,
手撕一下:
//复习:将一个几乎有序的数组arr,排序好,几乎就是:要挪动i位置,最远不会挪动k个位置(k<<远小于N)
public static void sortAlmostSortedArray(int[] arr, int k){
if (arr == null || arr.length < 2) return;
int N = arr.length;
//题目说了k<<N所以不用考虑N太短的事情
//当然了,左神也考虑过,如果k比N还大
//那堆就提前结束放数
PriorityQueue<Integer> heap = new PriorityQueue();
//从arr的index=0开始放入,这个index用到最后
int index = 0;
for(;index < Math.min(k, N); index++){
//考虑k可能大于N的话,--这不可能
heap.add(arr[index]);
}
//堆放了k个
//循环弹出,arr加入
int i = 0;//弹出的数,放哪里呢?放i=0开始,覆盖arr原有的数
for(; index < N; index++, i++){
//每一次index随着增加,最后到N停止,与此同时,每次i++
arr[i] = heap.poll();
heap.add(arr[index]);
}
//直到arr加完了,剩下的只余下弹了
while (!heap.isEmpty()){
arr[i++] = heap.poll();
}
//此时arr有序了
}
验证一下:
//对数器之构建随机数组
public static int[] createArray(int arrSize, int maxValue){
int[] arr = new int[arrSize];
for (int i = 0; i < arrSize; i++) {
arr[i] = (int)(maxValue * Math.random());//0-N-1的随机数
}
return arr;
}
public static void checker(){
//生成检验数组
int[] arr = {6,1,2,3,4,5,7,9,8,10,11,12};
//这个数组几乎就是排序好的,k==5,最多不会移动超过5个
int[] arr2 = new int[arr.length];//赋值同样一个数组arr
for (int i = 0; i < arr.length; i++) {
arr2[i] = arr[i];//copy即可
}
int[] arr3 = new int[arr.length];//赋值同样一个数组arr
for (int i = 0; i < arr.length; i++) {
arr3[i] = arr[i];//copy即可
}
//绝对的正确方法——暴力方法,或系统函数,操作arr
Arrays.sort(arr);
//优化方法,操作arr2
sortedAlmostArrLessK(arr2, 5);
//复习优化方法,操作arr3
sortAlmostSortedArray(arr3, 5);
//然后两个数组对位校验
boolean isTrue = true;
for (int i = 0; i < arr.length; i++) {
if(arr[i] != arr2[i]) isTrue = false;
}
System.out.println(isTrue == false ? "oops,wrong!" : "right!");
isTrue = true;
for (int i = 0; i < arr.length; i++) {
if(arr[i] != arr3[i]) isTrue = false;
}
System.out.println(isTrue == false ? "oops,wrong!" : "right!");
}
public static void main(String[] args) {
checker();
// test();
}
没问题:
right!
right!
总结
提示:重要经验:
1)几乎有序:就要考虑到可能会用堆来搞定,未来我们会遇到当资源空间受限时,你也要用堆解决问题
2)堆结构远比堆排序重要