Java实现寻找最小的k个数

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("
"+"输出数组最小的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("
"+"使用方法2进行部分排序后的结果:");
        for(int i = 0;i < A.length;i++)
            System.out.print(A[i]+" ");
        System.out.println("
部分排序选出数组中最小的"+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("
"+"使用方法3进行部分堆排序后的结果:");
        for(int i = 0;i < A.length;i++)
            System.out.print(A[i]+" ");
        System.out.println("
部分排序选出数组中最小的"+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("
"+"使用方法4进行快速选择排序后的结果:");
        for(int i = 0;i < A.length;i++)
            System.out.print(A[i]+" ");
        System.out.println("
部分排序选出数组中最小的"+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 

此处附上四种方法的完整代码

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("
"+"使用方法2进行部分排序后的结果:");
        for(int i = 0;i < A.length;i++)
            System.out.print(A[i]+" ");
        System.out.println("
部分排序选出数组中最小的"+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("
"+"使用方法3进行部分堆排序后的结果:");
        for(int i = 0;i < A.length;i++)
            System.out.print(A[i]+" ");
        System.out.println("
部分排序选出数组中最小的"+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("
"+"使用方法4进行快速选择排序后的结果:");
        for(int i = 0;i < A.length;i++)
            System.out.print(A[i]+" ");
        System.out.println("
部分排序选出数组中最小的"+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("
"+"输出数组最小的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("
C数组:");
        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);
    }
}

完整代码
原文地址:https://www.cnblogs.com/a1439775520/p/13077957.html