剑指Offer面试题30最小的K个数(涉及堆或快排)

来源:互联网 发布:c语言韩信点兵题目 编辑:程序博客网 时间:2024/06/06 00:54

面试题30:最小的K个数(涉及快排与堆)

输入n个整数,找出其中最小的k个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。最简单的方法是先排序再输出,复杂度为O(nlogn),但还有更快的方法。

思路1:类似面试题29,也用快排的思想。快排一轮后检查我们选择的快排的key所在的下标是不是k-1就行了(不一定是排序的)。复杂度为O(n),但是要修改数组。

思路2:如果不允许修改数组,还有一种O(nlogK)的方法,很适合处理海量数据,即利用大根堆保存K个最小的数。

关于堆:堆是一个完全二叉树,如果按照层序遍历结果存储为数组,下标为i且根节点i=0,则满足Key[i]>=Key[2i+1]&&Key[i]>=key[2i+2]的称为大根堆,即根结点大于子结点,堆顶为最大值。

关于堆排序:第一步建堆,即将输入序列看作是层序遍历结果,然后按顺序写成完全二叉树的形式。第二步调整堆,即从最后一个非叶结点开始调整,它的数组下标为最后一个数的下标-1之后除以2,保证这个结点比子结点大。然后下标减一继续调整,交换结点之后的孩子结点有可能不满足堆的性质,继续调整直到下标为0,这里有递归和非递归两种方法。

堆排序就是根据前边建好的堆先取出根结点和最后一个结点交换,然后对前边len-1个结点进行堆调整,再取出根结点和倒数第二个结点交换,对前边len-2个结点堆调整,以此类推直到所有结点都取出。heapAdjust函数可看做每次都从当前结点走到叶子节点,数高度为log(n+1)向上取整,所以复杂度可视为O(logn),简单起见,不必考虑到底是从根节点到达叶结点还是从中间某节点到达叶结点。建堆时调用heapAdjust函数n/2次,排序时调用heapAdjust函数n-1次,得到三种情况下的复杂度都是O(logn)*(n/2)+O(logn)*(n-1),化简为O(nlogn)。空间复杂度O(1)。是不稳定的排序。

两种思路的比较:第一种复杂度O(n),比第二种快,但是缺点是修改了数组。第二种适合处理大数据,可以一次读一些数据和容器中的k个数比较,不用一次性全部读入数据。

public class GetLeastNumbers {public static void main(String[] args) {GetLeastNumbers test = new GetLeastNumbers();test.getLeastNumbers1(new int[]{4,5,1,6,2,7,3,8}, 4);test.getLeastNumbers2(new int[]{4,5,1,6,2,7,3,8}, 4);}//思路1:基于快排的void getLeastNumbers1(int[] array,int k){if(array == null || k <= 0 || k > array.length){return ;}int left = 0;int right = array.length - 1;int keyIndex = partirion(array, left, right);while(keyIndex != k-1){if(keyIndex > k-1){right = keyIndex-1;keyIndex = partirion(array, left, right);}else{left = keyIndex+1;keyIndex = partirion(array, left, right);}}for(int i = 0;i<=keyIndex;i++){System.out.println(array[i]);}}//一轮快排Integer partirion(int[] array,int left,int right){if(array == null){return null;}int i = left;int j = right;int key = array[left];while(i < j){while(i < j && array[j] >= key){j--;}array[i] = array[j];while(i < j && array[i] <= key){i++;}array[j] = array[i];}array[i] = key;return i;}//思路2:借用一个保存有k个数字的大根堆void getLeastNumbers2(int[] array,int k){if(array==null || k<=0 || k>array.length){return ;}//根据输入数组前k个数建立大根堆,从k+1个数开始与根节点比较,大于根节点则舍去,反之取代根节点并重排大根堆int[] kArray = Arrays.copyOfRange(array, 0, k);buildMaxHeapify(kArray);//建立大根堆for(int i=k;i<array.length;i++){if(array[i] < kArray[0]){//kArray[0]是堆顶kArray[0] = array[i];maxHeapify(kArray, k, 0);//替换堆顶后,执行堆排序以调整堆,保证堆顶数最大}}for(int i:kArray){System.out.println(i);}}//建堆void buildMaxHeapify(int[] array){for(int i = (array.length-2) >> 1; i >= 0; i--){//这里i是最后一个非叶结点的数组下标maxHeapify(array, array.length, i);//调整堆}}//调整堆,递归法,参数heapSize为要调整的堆的大小,参数parent为当前堆顶的数组下标void maxHeapify(int[] array,int heapSize,int parent){int left = 2*parent+1;//左孩子下标int right = left+1;//右孩子下标int largest = parent;//最大值下标if(left < heapSize && array[left] > array[parent]){largest=left;}if(right < heapSize && array[right] > array[largest]){largest=right;}//交换之后子结点可能就不满足堆的特性了,要继续调整if(largest != parent){int temp = array[parent];array[parent] = array[largest];array[largest] = temp;maxHeapify(array, heapSize, largest);}}//调整堆,循环法,参数heapSize为要调整的堆的大小,参数parent为当前堆顶的数组下标//void maxHeapify(int[] array, int heapSize, int parent) {//    int temp = array[parent]; // temp保存当前父节点//    int child = 2 * parent + 1; // 先获得左孩子//    while (child < heapSize) {//        // 如果有右孩子结点,并且右孩子结点的值大于左孩子结点,则选取右孩子结点//        if (child + 1 < heapSize && array[child] < array[child + 1]) {//            child++;//        }//        // 如果父结点的值已经大于孩子结点的值,则直接结束//        if (temp >= array[child]){//        break;//        }//        // 把孩子结点的值赋给父结点//        array[parent] = array[child];//        // 选取孩子结点的左孩子结点,继续向下筛选//        parent = child;//        child = 2 * child + 1;//    }//    // 把原先父结点的值赋给现在的结点//    array[parent] = temp;//}//堆排序,执行之前要先建堆!void heapSort(int[] array){for(int i = array.length-1;i>0;i--){int temp = array[0];array[0] = array[i];array[i] = temp;maxHeapify(array, i, 0);}}}

0 0
原创粉丝点击