寻找最小的k个数

问题描述:给定n个整数,求出其中最小的k个数。

分析:这是一个经典的问题了,存在多种解法,各个解法的效率不一样,这里我列举四种常见的解法。

         解法一:可以将所有的数进行排序,然后直接输出前k个数即可。排序算法有很多,读者可以自己选择,如快速排序。

         解法二:利用容器(可以是数组,集合等)实现,

                    我们可以先遍历k个数存入到大小为k的数组中,然后假设这k个数就是最小的k个数;

                    对这k个数,利用选择或交换排序找到这k个元素中的最大值kmax(找最大值需要遍历这k个数,时间复杂度为O(k));

                    继续遍历剩余n-k个数。假设每一次遍历到的新的元素的值为x,把x与kmax比较: 如果x<kmax ,用x替换kmax,并回到上一步重新找出k个元素的数组中最大元素kmax;如果x>kmax,则继续遍历不更新数组。

                    每次遍历,更新或不更新数组的所用的时间为O(k)或O(0)。故整趟下来,时间复杂度为n*O(k)=O(nk)。

         解法三:利用堆结构实现:其实这个方法与解法二相似,只不过这里的容器采用堆这个数据结构,之所以用堆是因为建k个节点的初始堆的时间复杂度为O(k),每维护一次的代价为O(logk),这一步比数组的时间复杂度低。

                   至于后面的都一样,因此这种方法的总时间复杂度为O(nlogk)。

                   这里我给出详细的Java代码,写法比较通用,读者可以很容易的转换为其他语言实现(代码中下标从1开始存数,下标0忽略)。

 1 import java.util.Scanner;
 2 public class Qinkxiao {
 3     public static void heapadjust(int H[],int s,int m){   //调整堆
 4         int j,k;
 5         int rc=H[s];
 6         for( j=2*s;j<=m;j*=2)
 7         {
 8             if(j<m && H[j]<H[j+1])++j;
 9             if(rc>=H[j])break;
10             H[s]=H[j];
11             s=j;
12         }
13     
14         H[s]=rc;
15         
16     }
17     public static void createdui(int array[],int k){    //建初始堆
18         for(int i=k/2;i>=1;--i)
19             heapadjust(array,i,k);
20     }
21     public static void main(String[] args) {  
22           int array[]={4,5,9,2,1,5,3,-5,20};
23           int n=array.length-1;
24           int k=3;
25           System.out.print("n个整数为:");
26           for(int i=1;i<=n;i++)
27           {  
28               System.out.print(array[i]+",");
29            }
30               System.out.println();
31               createdui(array,k);                //建k个节点的初始堆
32               for(int i=k+1;i<=n;i++)            //依次遍历后面的n-k个数
33                   if(array[i]<array[1])
34                       {
35                       array[1]=array[i];
36                       heapadjust(array,1,k);
37                       }
38               System.out.print("前"+k+"小个整数为:");    
39               for(int i=1;i<=k;i++)
40                   System.out.print(array[i]+",");
41     }
42 
43 }
View Code

输出结果为:

n个整数为:5,9,2,1,5,3,-5,20,
前3小个整数为:2,1,-5,

          解法四:借助快速排序的思想:利用快速排序中的分割函数。可以证明这种方法的时间度为O(n).

                   具体的Java代码如下:

           

 1 import java.util.Scanner;
 2 public class Qinkxiao {
 3 
 4       public static int partition(int array[],int low,int high){         //快速排序中的划分方法
 5         int q=array[low];
 6         int m;
 7         while(low<high){
 8             while(low<high && array[high]>q)high--;
 9             m=array[low];array[low]=array[high];array[high]=m;
10             while(low<high && array[low]<q)low++;
11             m=array[low];array[low]=array[high];array[high]=m;
12         }
13         return low;
14     }
15 
16     public static void main(String[] args) {  
17           int array[]={4,5,9,2,1,5,3,-5,20};
18           int n=array.length;
19           int k=3;
20           System.out.print("n个整数为:");
21           for(int i=0;i<n;i++)
22           {  
23               System.out.print(array[i]+",");
24            }
25               System.out.println();
26              
27               int start=0,end=n-1;
28               int index=partition(array,start,end);
29               while(index!=k-1)                           //判断索引位置石佛符合要求
30               {
31                   if(index>k-1 )
32                   { end=index-1;
33                       index=partition(array,start,end);
34                   }
35                   else 
36                   { start=index+1;
37                       index=partition(array,start,end);
38                   }
39               }
40               
41               System.out.print("前"+k+"小个整数为:");    
42               for(int i=0;i<k;i++)
43                   System.out.print(array[i]+",");
44     }
45 
46 }
View Code

输出结果为:

n个整数为:4,5,9,2,1,5,3,-5,20,
前3小个整数为:-5,1,2,

              上述四种解法比较:

                    解法一:思想最简单,就是将数据排序,直接输出前k个数,编程简单,但是时间复杂度较高,适合数据量少的场合。

                    解法二:编程稍微复杂,时间复杂度略低,可以用于海量数据处理。

                    解法三:比解法二好一点,时间复杂度又降低了,并且不需要交换原来数组中的顺序,可以用于n很大k很小的场合,尤其是海量数据处理。

                    解法四:时间复杂度最低,线性时间即可解决,但是编程比较复杂,而且需要交换原来数组中数据的顺序,破坏了原来数组的顺序。

                    这四种解法,要选择适当的场合使用。解法三和解法四大家要牢牢掌握,可以用于其他类似的题目。

                    除了上述集中解法,还有其他的解法,比如利用哈希表,红黑树等其他数据结构,这里不再讲述。

posted @ 2016-04-25 14:43  成功=坚持+努力+目标  阅读(186)  评论(0编辑  收藏  举报