目录
1 问题描述
有n个整数,请找出其中最小的k个数,要求时间复杂度尽可能低。
2 解决方案
2.1 全部排序法
先对这n个整数进行快速排序,在依次输出前k个数。
具体代码如下:
package com.liuzhen.array_2; public class SearchMinK { //方法1:全部排序 public void quickSort(int[] A,int start,int end){ if(end > start){ int k = LomutoPartition(A,start,end); quickSort(A,start,k-1); quickSort(A,k+1,end); } } //返回数值result,满足: 左边部分< A[result] <=右边部分 public int LomutoPartition(int[] A,int start,int end){ if(start >= end) return start; int begin = A[start]; int result = start; for(int i = start + 1;i <= end;i++){ if(A[i] < begin){ result++; swap(A,i,result); } } swap(A,start,result); return result; } //交换数组m位置和n位置上的值 public void swap(int[] arrayA,int m,int n){ int temp = arrayA[m]; arrayA[m] = arrayA[n]; arrayA[n] = temp; } //输出数组前k个元素 public void printArrayK(int[] array,int k){ for(int i = 0;i < k;i++){ System.out.print(array[i]+" "); } } public static void main(String[] args){ SearchMinK test = new SearchMinK(); int[] A = {9,8,7,5,4,3,2,1,6,3,4,5,12,32,3,2,1,4,6,34}; test.quickSort(A, 0, A.length-1); System.out.println("对数组进行排序后结果:"); for(int i = 0;i < A.length;i++) System.out.print(A[i]+" "); System.out.println("\n"+"输出数组最小的5个数:"); test.printArrayK(A, 5); } }
运行结果:
对数组进行排序后结果: 1 1 2 2 3 3 3 4 4 4 5 5 6 6 7 8 9 12 32 34 输出数组最小的5个数: 1 1 2 2 3
2.2 部分排序法
具体操作步骤如下:
(1)遍历n个数,把最先遍历到的k个数存入到大小为k的数组中,假设他们就是最小的k个数;
(2)利用选择排序或交换排序找到这k个元素中的最大值kmax;
(3)继续遍历剩余的n-k个数。假设每次遍历到的新元素的值为x,把x与kmax进行比较:如果x<kmax,则用x替换kmax,并回到第2步重新找出k个元素的数组中新的最大元素kmax;如果x>=kmax,则继续遍历,不更新数组。
具体代码如下:
package com.liuzhen.array_2; public class SearchMinK { //方法2:部分排序 public void getArrayMinK(int[] A,int k){ if(k > A.length) return; while(true){ int max = getMaxArrayK(A,k); //当前数组前k个元素中的最大值 int count = 0; for(int i = k;i < A.length;i++){ if(A[max] > A[i]) swap(A,max,i); else count++; } if(count == A.length-k) break; } System.out.println("\n"+"使用方法2进行部分排序后的结果:"); for(int i = 0;i < A.length;i++) System.out.print(A[i]+" "); System.out.println("\n部分排序选出数组中最小的"+k+"个数:"); for(int i = 0;i < k;i++) System.out.print(A[i]+" "); } //获取数组前k个元素的最大值的数组下标 public int getMaxArrayK(int[] A,int k){ int result = 0; if(k > A.length) return 0; for(int i = 0;i < k;i++){ if(A[i] > A[result]) result = i; } return result; } //交换数组m位置和n位置上的值 public void swap(int[] arrayA,int m,int n){ int temp = arrayA[m]; arrayA[m] = arrayA[n]; arrayA[n] = temp; } public static void main(String[] args){ SearchMinK test = new SearchMinK(); int[] B = {9,8,7,5,4,3,2,1,6,3,4,5,12,32,3,2,1,4,6,34}; test.getArrayMinK(B, 5); }
运行结果:
使用方法2进行部分排序后的结果: 1 1 2 2 3 9 8 7 6 5 4 5 12 32 4 3 3 4 6 34 部分排序选出数组中最小的5个数: 1 1 2 2 3
2.3 用堆代替数组法
此处思想和2.2中一致,唯一区别就是在寻找kmax时,是使用堆排序的思想。
具体代码如下:
package com.liuzhen.array_2; public class SearchMinK { //方法3:用堆来代替数组 /* * 函数功能:对数组A前k个元素进行堆排序 */ public void heapBottomUp(int[] A,int k){ for(int i = (k-1)/2;i >= 0;i--){ int temp = i; int tempV = A[temp]; boolean heap = false; while(!heap && 2*temp < k-1){ int j = 2*temp + 1; if(j < k-1){ if(A[j] < A[j+1]) j = j + 1; } if(tempV >= A[j]) heap = true; else{ A[temp] = A[j]; temp = j; } } A[temp] = tempV; } } public void getArrayMinK2(int[] A,int k){ heapBottomUp(A,k); while(true){ int count = 0; for(int i = k;i < A.length;i++){ if(A[i] < A[0]){ swap(A,i,0); heapBottomUp(A,k); } else count++; } if(count == A.length-k) break; } System.out.println("\n"+"使用方法3进行部分堆排序后的结果:"); for(int i = 0;i < A.length;i++) System.out.print(A[i]+" "); System.out.println("\n部分排序选出数组中最小的"+k+"个数:"); for(int i = 0;i < k;i++) System.out.print(A[i]+" "); } //交换数组m位置和n位置上的值 public void swap(int[] arrayA,int m,int n){ int temp = arrayA[m]; arrayA[m] = arrayA[n]; arrayA[n] = temp; } public static void main(String[] args){ SearchMinK test = new SearchMinK(); int[] D = {9,8,7,5,4,3,2,1,6,3,4,5,12,32,3,2,1,4,6,34}; test.getArrayMinK2(D, 5); } }
运行结果:
使用方法3进行部分堆排序后的结果: 3 2 2 1 1 9 8 7 6 5 4 5 12 32 4 3 3 4 6 34 部分排序选出数组中最小的5个数: 3 2 2 1 1
2.4线性选择算法
看具体代码即可理解其中蕴含的思想。
具体代码如下:
package com.liuzhen.array_2; public class SearchMinK { //返回数值result,满足: 左边部分< A[result] <=右边部分 public int LomutoPartition(int[] A,int start,int end){ if(start >= end) return start; int begin = A[start]; int result = start; for(int i = start + 1;i <= end;i++){ if(A[i] < begin){ result++; swap(A,i,result); } } swap(A,start,result); return result; } //方法4:线性选择法 public void getArrayMinK3(int[] A,int k){ int start = 0; int end = A.length - 1; int tempK = LomutoPartition(A,start,end); while(tempK != k){ if(tempK > k){ end = tempK - 1; tempK = LomutoPartition(A,start,end); } if(tempK < k){ start = tempK + 1; tempK = LomutoPartition(A,start,end); } } System.out.println("\n"+"使用方法4进行快速选择排序后的结果:"); for(int i = 0;i < A.length;i++) System.out.print(A[i]+" "); System.out.println("\n部分排序选出数组中最小的"+k+"个数:"); for(int i = 0;i < k;i++) System.out.print(A[i]+" "); } public static void main(String[] args){ SearchMinK test = new SearchMinK(); int[] E = {9,8,7,5,4,3,2,1,6,3,4,5,12,32,3,2,1,4,6,34}; test.getArrayMinK3(E, 5); } }
运行结果:
使用方法4进行快速选择排序后的结果: 1 2 2 1 3 3 3 4 5 5 4 4 6 8 6 7 9 32 12 34 部分排序选出数组中最小的5个数: 1 2 2 1 3
附4种方法完整代码:
package com.liuzhen.array_2; public class SearchMinK { //方法1:全部排序 public void quickSort(int[] A,int start,int end){ if(end > start){ int k = LomutoPartition(A,start,end); quickSort(A,start,k-1); quickSort(A,k+1,end); } } //返回数值result,满足: 左边部分< A[result] <=右边部分 public int LomutoPartition(int[] A,int start,int end){ if(start >= end) return start; int begin = A[start]; int result = start; for(int i = start + 1;i <= end;i++){ if(A[i] < begin){ result++; swap(A,i,result); } } swap(A,start,result); return result; } //交换数组m位置和n位置上的值 public void swap(int[] arrayA,int m,int n){ int temp = arrayA[m]; arrayA[m] = arrayA[n]; arrayA[n] = temp; } //输出数组前k个元素 public void printArrayK(int[] array,int k){ for(int i = 0;i < k;i++){ System.out.print(array[i]+" "); } } //方法2:部分排序 public void getArrayMinK(int[] A,int k){ if(k > A.length) return; while(true){ int max = getMaxArrayK(A,k); //当前数组前k个元素中的最大值 int count = 0; for(int i = k;i < A.length;i++){ if(A[max] > A[i]) swap(A,max,i); else count++; } if(count == A.length-k) break; } System.out.println("\n"+"使用方法2进行部分排序后的结果:"); for(int i = 0;i < A.length;i++) System.out.print(A[i]+" "); System.out.println("\n部分排序选出数组中最小的"+k+"个数:"); for(int i = 0;i < k;i++) System.out.print(A[i]+" "); } //获取数组前k个元素的最大值的数组下标 public int getMaxArrayK(int[] A,int k){ int result = 0; if(k > A.length) return 0; for(int i = 0;i < k;i++){ if(A[i] > A[result]) result = i; } return result; } //方法3:用堆来代替数组 /* * 函数功能:对数组A前k个元素进行堆排序 */ public void heapBottomUp(int[] A,int k){ for(int i = (k-1)/2;i >= 0;i--){ int temp = i; int tempV = A[temp]; boolean heap = false; while(!heap && 2*temp < k-1){ int j = 2*temp + 1; if(j < k-1){ if(A[j] < A[j+1]) j = j + 1; } if(tempV >= A[j]) heap = true; else{ A[temp] = A[j]; temp = j; } } A[temp] = tempV; } } public void getArrayMinK2(int[] A,int k){ heapBottomUp(A,k); while(true){ int count = 0; for(int i = k;i < A.length;i++){ if(A[i] < A[0]){ swap(A,i,0); heapBottomUp(A,k); } else count++; } if(count == A.length-k) break; } System.out.println("\n"+"使用方法3进行部分堆排序后的结果:"); for(int i = 0;i < A.length;i++) System.out.print(A[i]+" "); System.out.println("\n部分排序选出数组中最小的"+k+"个数:"); for(int i = 0;i < k;i++) System.out.print(A[i]+" "); } //方法4:线性选择法 public void getArrayMinK3(int[] A,int k){ int start = 0; int end = A.length - 1; int tempK = LomutoPartition(A,start,end); while(tempK != k){ if(tempK > k){ end = tempK - 1; tempK = LomutoPartition(A,start,end); } if(tempK < k){ start = tempK + 1; tempK = LomutoPartition(A,start,end); } } System.out.println("\n"+"使用方法4进行快速选择排序后的结果:"); for(int i = 0;i < A.length;i++) System.out.print(A[i]+" "); System.out.println("\n部分排序选出数组中最小的"+k+"个数:"); for(int i = 0;i < k;i++) System.out.print(A[i]+" "); } public static void main(String[] args){ SearchMinK test = new SearchMinK(); int[] A = {9,8,7,5,4,3,2,1,6,3,4,5,12,32,3,2,1,4,6,34}; test.quickSort(A, 0, A.length-1); System.out.println("对数组进行排序后结果:"); for(int i = 0;i < A.length;i++) System.out.print(A[i]+" "); System.out.println("\n"+"输出数组最小的5个数:"); test.printArrayK(A, 5); int[] B = {9,8,7,5,4,3,2,1,6,3,4,5,12,32,3,2,1,4,6,34}; test.getArrayMinK(B, 5); int[] C = {2,9,7,6,5,8}; test.heapBottomUp(C, 6); System.out.println("\nC数组:"); for(int i = 0;i < C.length;i++) System.out.print(C[i]+" "); int[] D = {9,8,7,5,4,3,2,1,6,3,4,5,12,32,3,2,1,4,6,34}; test.getArrayMinK2(D, 5); int[] E = {9,8,7,5,4,3,2,1,6,3,4,5,12,32,3,2,1,4,6,34}; test.getArrayMinK3(E, 5); } }