寻找最小的k个数

来源:互联网 发布:境外网络电视直播.apk 编辑:程序博客网 时间:2024/06/09 20:14

题目:输入n个整数,输出其中最小的k个
思路一、快速排序+遍历
先对n个整数快速排序,平均所费的时间为nlogn,之后再遍历序列中前k个元素的输出
(总的时间复杂度为O(nlogn+k) = O(nlogn))
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
思路二、选择排序+遍历
1、遍历n个数,把最先遍历到的k个数存入到大小为k的数组中,假设它们即使最小的k个数;
2、对这k个数利用交换排序找出这k个元素中的最大值kmax,时间复杂度为O(k);
3、继续遍历n-k个数,假设每一次遍历到的新的元素的值为x,把x与kmax比较:如果 x < kmax ,用x替换kmax,并回到第二步重新找出k个元素的数组中最大元素kmax‘;如果 x >= kmax ,则继续遍历不更新数组。每次遍历更新或不更新数组所用的时间为O(k),故整趟下来,时间复杂度为nO(k)=O(nk)
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
思路三、维护容量为k的最大堆
1、用容量为k的最大堆存储最先遍历到的k个数,同样假设它们即是最小的k个数;
2、堆中元素是有序的,令k1<k2<...<kmax(kmax设为最大堆中的最大元素)
3、遍历剩余n-k个数。假设每一次遍历到的新的元素的值为x,把x与堆顶元素kmax比较:如果 x < kmax ,用x替换kmax,然后更新堆(用时logk);否则不更新堆。总的时间复杂度为O(nlogk)
与思路二的区别在于堆中进行查找和更新的时间复杂度均为O(logk),但是在数组中找出最大元素,时间复杂度为O(k).



注意一个误区:并不是取出堆顶元素之后,把原来堆顶元素的儿子送到堆顶,而是如图所示,16被删除之后,堆中的最后一个元素1代替16成为了根结点,之后1下沉,14上移到堆顶(这个是算法导论中的分析,不一样的是这里是最大堆的堆排序过程)
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
参考代码:

//找出最小k个数.cpp#include <iostream>const int MAXLEN = 8;const int K = 4;using namespace std;//建立k个元素最大堆void HeapAdjust(int array[], int i, int Length){    int child,temp;    for(temp = array[i]; 2*i+1 < Length; i = child)    {        child = 2 * i + 1;        if(child < Length - 1 && array[child + 1] < array[child])            child ++;        if(temp > array[child])            array[i] = array[child];        else            break;        array[child] = temp;    }}//交换void Swap(int *a, int *b){    *a = *a ^ *b;    *b = *a ^ *b;    *a = *a ^ *b;}//得到最小int GetMin(int array[], int Length, int k){    int min = array[0];    Swap(&array[0], &array[Length - 1]);    int child,temp;    int i = 0,j = k-1;    for(temp = array[0]; j > 0 && 2*i+1 < Length; --j,i = child)    {        child = 2*i+1;        if(child < Length - 1 && array[child + 1] < array[child])            child++;        if(temp > array[child])            array[i] = array[child];        else            break;        array[child] = temp;    }    return min;}void Kmin(int array[], int Length, int k){    for(int i = Length/2 - 1; i >= 0; --i)        //初始建堆,时间复杂度为O(n)        HeapAdjust(array, i, Length);    int j = Length;    for(int i = k; i > 0; --i,--j)        //k次循环,每次循环的复杂度最多为k次交换,复杂度为O(k^2)    {        int min = GetMin(array,j,i);        cout << min << endl;    }}int main(){    int array[MAXLEN] = {13, 38, 49, 76, 97, 27, 65, 49};    //for(int i = MAXLEN; i > 0; --i)    //    array[MAXLEN - i] = i;    Kmin(array, MAXLEN, K);    return 0;}

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
思路四、类似快速排序的partition过程的分治算法
——《数据结构与算法分析--c语言描述》一书,第7章第7.7.6节中快速选择算法
1)类似快速排序的划分方法N个数存储在数组S中,再从数组中随机选取一个数X作为枢纽元,2)把剩下的数组划分为Sa和Sb两个部分,Sa<=X<=Sb,
3)如果要查找的k个元素小于等于Sa的元素个数,那么第k个最小元肯定在Sa中则返回Sa中较小的k个元素
4)否则第k个最小元在Sb中,返回Sa中所有元素+Sb中小的k-|Sa|个元素
参考代码:

//采用类似快速排序的partition过程的分治算法.cppvoid q_select(int a[], int k, int left, int right){    int i,j;    int pivot;    if(left + CUTOFF <= right)    {        pivot = median3(a,left,right);//取三个中值作为枢纽元        i = left;        j = right - 1;        for(; ;)        {            while(a[++i] < pivot);            while(a[--j] > pivot);            if(i < j)                swap(&a[i], &a[j]);            else                break;        }        //重置枢纽元        swap(&a[i], &a[right-1]);        if(k <= i)            q_select(a, k, left, i - 1);        else if(k > i+1)            q_select(a, k, i+1, right);    }    else        insert_sort(a + left, right - left + 1);}

注意:快速排序与快速选择一样,如果枢纽元选择不当,以至于S1或S2中有一个序列是空的,则依然会有最坏的运行时间O(N^2)的情况发生,上述使用三数中值作为枢纽元的方法可以避免这个情况发生,可以做到O(N)的复杂度
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
思路五、随机选取数列中的一个元素作为主元+遍历输出k个小的元素
——《算法导论》
每次都是随机选取数列中的一个元素作为主元,在O(n)的时间内找到第k小的元素,之后遍历输出前面的K个小的元素,比较一下熟知的快速排序,是以固定的第一个或最后一个元素作为主元,每次递归划分都是不均等的,最后的平均时间复杂度是O(nlogn)
思路六、五分化中项的中项法
——《算法导论》
像RANDOMIZED-SELECT一样,SELECTT通过输入数组的递归划分来找出所求元素,但是,该算法的基本思想是要保证对数组的划分是个好的划分。SECLECT采用了取自快速排序的确定性划分算法partition,并做了修改,把划分主元元素作为其参数
具体思路:
1)将输入数组的n个元素划分成n/5组,每组5个元素,且最后一个组存放n%5个元素。
2)寻找n/5个组中每一组的中位数,首先对每组中的元素(至多为5个)进行插入排序,之后选出中位数
3)对第2)步中找出的n/5个中位数,递归调用SELECT以找到其中位数x
4)按中位数的中位数x对输入数组进行划分,让k比划分低区的元素多1,所以x是第k小的元素,并且有n-k个元素在划分的高区
5)如果i = k,则返回x,否则如果i<k,在低区递归调用SELECT以找出第i小的元素;如果i>k,则在高区找第i-k个最小的元素。

0 0
原创粉丝点击