題目
題目描述:尋找最小的k個元素
題目:輸入n個整數,輸出其中最小的k個
例如輸入1,2,3,4,5,6,7,8,則最小的4個數是1,2,3,4
思路
一,最容易想到的就是排序,然後輸出前k個元素,快速排序,排序時間是n*logn,再加上遍歷輸出前k個元素,總的時間複雜度是n*logn+k = O(n*logn)
二,其實題目中只要求輸出前k個數,沒有要求這些數是有順序的,而且不必對n個數進行排序。從n個數中先取出前k個數放到一個數組中,然後對這k個數使用選擇排序或者交換排序,找到kmax這k個數中最大的數,用時O(k)。然後從剩下的n-k個數中依次取出一個數x,與kmax比較,if x > kmax, 跳過,if x < kmax ,kmax= x,然後再對這k個數進行選擇排序找到最大的kmax,總的時間複雜度平均下來爲n*O(k),利用的就是要找的是最小的前k個數,如果比最大的大,肯定不在裏面,如果比最大的小,可能在裏面,因爲有新的元素加入,再次進行排序找最大,又重複比較。
三,我們可以對思路二中的k個數的操作進行改進,就是找到k個數中的kmax,可以使用最大堆,維護k個元素的最大堆,建堆耗時O(k),此時堆頂的根節點是最大的kmax,然後遍歷剩下的n-k個元素,if x>kmax ,跳過,if x < kmax ,kmax = x,更新最大堆,時間複雜度爲O(logk),總的時間複雜度爲O(n*logk)。
四,我們的目的就是找到最小的k個數,如果有一種方法正好把這k個數放到左邊,右邊的都是比它們大的,直接輸出就好了,這個其實就是快速排序的思路。n個數存在數組S中,隨機選取一個數X作爲樞紐元素,將數組劃分爲Sa和Sb兩個部分,Sa<=X <= Sb,如果要查找的個數k小於sizeof(Sa),就返回Sa中前k個元素,否則返回Sa中所有元素+Sb中小的k-sizeof(Sa)個元素。這個算法的關鍵就是這個樞紐元素的選擇,隨機選取樞紐元素,可以做到線性期望時間O(n)。我們通常所熟知的快速排序是以固定的第一個或者最後一個做爲樞紐元,每次遞歸劃分都是不均等的,最後的平均時間複雜度爲O(n * logn),RANDOMIZED-SELECT(數據結構與算法分析-c語言描述P185)提出的對於選擇的線性期望時間,與普通的快排不同的是,每次都是隨機的,隨機的方法有“中位數的中位數”,“五分化中項的中項”。
五,可以使用線性時間排序,計數排序,時間複雜度達到O(n)
六,既然是找最小的前k個數,我們可以使用最小堆,將n個元素所在數組,建立最小堆,用時O(n),從堆頂取k次數,取完一次就要重新排列最小堆,保證最小堆的性質,每次平均用時logn,,總的時間複雜度爲O(n+k*logn),這種方法與二比較,時間複雜度小,但是空間複雜度爲O(n),最大堆的空間複雜度爲O(k)
七,思路和六是一樣的,只不過是在取完堆頂元素後,重新排列時,換到堆頂的元素只需要下移最多k次就足夠了,此時堆頂的元素已經是我們要找的第二小的元素,然後取出第二小的元素,再次把堆中的最後一個元素送到堆頂,又經過k-1次下移後,此後下移次數逐漸減少,重複k-1次,不斷取出的堆頂元素是我們要找的最小的k個數,不過需要注意,算法中斷後的堆已經不再是最小堆了,思路六中每次提取都要logn,這個需要k,總的時間複雜度爲O(n+k^2)
實現
代碼都是通過編譯調試的,記錄下來方便以後再回過頭來複習,不過感覺還是寫的比較low。加油吧。
思路一:快速排序
/*************************************************************************
> File Name: quicksort_kmin.cpp
> Author: zxl
> mail: [email protected]
> Created Time: 2016年04月07日 星期四 21時01分26秒
************************************************************************/
#include <iostream>
#define MAX 20000
#define K 100
using namespace std;
int partion(int A[],int start,int end)
{
int x = A[end];
int i = start-1;
int j;
for(j = start;j<end;j++)
{
if(A[j] <= x)
{
i++;
swap(A[i],A[j]);
}
}
swap(A[i+1],A[end]);
return i+1;
}
void quicksort(int A[],int start,int end)
{
if(start < end)
{
int mid = partion(A,start,end);
quicksort(A,start,mid-1);
quicksort(A,mid+1,end);
}
}
void Kmin(int A[],int length,int k)
{
int i;
quicksort(A,0,length);
for(i = 0;i<k;i++)
{
cout << A[i] << endl;
}
}
int main()
{
int A[MAX];
int i;
for(i = 0;i< MAX;i++)
A[i] = MAX-i;
Kmin(A,MAX-1,K);
return 0;
}
思路二:
/*************************************************************************
> File Name: kmin_2.cpp
> Author: zxl
> mail: 857317335@qq.com
> Created Time: 2016年04月07日 星期四 22時12分24秒
************************************************************************/
#include <iostream>
#define MAX 200
#define K 10
using namespace std;
void swap(int * a,int *b)//c++有自帶的swap函數
{
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
//找到最大值放到數組末尾
int FindMax(int buf[],int length)
{
int i;
for(i = 0;i<length;i++)
{
if(buf[i] > buf[length-1])
{
swap(buf+length-1,buf+i);
}
}
return buf[length-1];
}
void Kmin(int A[],int length,int k)
{
int B[k];
int i;
int max;
for(i = 0;i<k;i++)
{
B[i] = A[i];
}
max = FindMax(B,k);
for(i = length-k-1;i<length;i++)
{
if(A[i] >= max)
continue;
else
{
swap(B+k-1,A+i);
max = FindMax(B,k);
}
}
for(i = 0;i< k;i++)
cout << B[i] << endl;
}
int main(void)
{
int A[MAX];
int i;
for(i=0;i< MAX;i++)
A[i] = MAX-i;
Kmin(A,MAX,K);
return 0;
}
思路三:維護k個元素的最大堆,適用於處理海量數據
/*************************************************************************
> File Name: kmin_3.cpp
> Author: zxl
> mail: [email protected]
> Created Time: 2016年04月08日 星期五 15時11分55秒
************************************************************************/
#include <iostream>
#define MAX 10
#define K 3
using namespace std;
int Parent(int i)
{
return i/2;
}
int LEFT(int i)
{
return i*2;
}
int RIGHT(int i)
{
return 2*i+1;
}
void Max_heapify(int A[],int size,int i)
{
int l = LEFT(i);
int r = RIGHT(i);
int largest;
if(l <= size && A[l]> A[i])
largest = l;
else
largest = i;
if(r<= size && A[r] > A[largest])
largest = r;
if(largest != i)
{
swap(A[i],A[largest]);
Max_heapify(A,size,largest);
}
}
void Build_Max_heap(int A[],int size)
{
int i;
for(i = size/2;i>0;i--)
Max_heapify(A,size,i);
}
void Kmin(int A[],int length,int k)
{
int i,j;
int B[k];
for(i = 1;i<= k;i++)
B[i] = A[i];
Build_Max_heap(B,k);
for(j = k+1;j<=length;j++)
{
if(A[j] > B[1])
continue;
else
{
swap(A[j],B[1]);
Max_heapify(B,k,1);
}
}
for(i = 1;i<=k;i++)
cout << B[i] << endl;
}
int main()
{
int A[MAX];
int i;
for(i = 1;i<=MAX;i++)
{
A[i]= MAX-i;
}
Kmin(A,MAX,K);
return 0;
}
思路四:在選擇樞紐元時採用三數求中值法,快速選擇只做一次遞歸調用而不是兩次,能保證平均時間爲O(N)。
/*************************************************************************
> File Name: kmin_4.cpp
> Author: zxl
> mail: 857317335@qq.com
> Created Time: 2016年04月11日 星期一 09時48分33秒
************************************************************************/
#include <iostream>
#define CTUOF 3
#define MAX 100
#define K 30
using namespace std;
// 三數中值分割方法
/*將A[left],A[right],A[center]排序,保證最小的在A[left],最大的在A[right],把樞紐元放在A[right-1],並在分割階段將i和j初始化到left+1,和right-2。
*/
int Median3(int A[],int left ,int right)
{
int center = (left + right)/2;
if(A[left] > A[center])
swap(A[left],A[center]);
if(A[left] > A[right])
swap(A[left],A[right]);
if(A[center] > A[right])
swap(A[center],A[right]);
swap(A[center],A[right -1]);
return A[right-1];
}
void Insertsort(int A[],int N)
{
int i,j;
int temp;
for(i = 1;i < N;i++)
{
temp = A[i];
for(j = i;j > 0 && A[j-1] > temp;j--)
A[j] = A[j-1];
A[j] = temp;
}
}
void Qselect(int A[],int k ,int left ,int right)
{
int i,j;
int pivot;
if(left + CTUOF <= right)
{
i = left;
j = right-1;
pivot = Median3(A,left,right);
while(true)
{
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)
Qselect(A,k,left,i-1);
else if(k > i+1)
Qselect(A,k,i+1,right);
}
else
Insertsort(A+left,right-left+1);
}
int main()
{
int i;
int A[MAX];
for(i = 0;i< MAX;i++)
A[i] = MAX-i;
Qselect(A,K,0,MAX-1);
for(i = 0;i<K ;i++)
cout << A[i] << endl;
}
思路四之二:出自算法導論P120,期望爲線性時間的選擇算法,期望運行時間爲O(N)。樞紐元爲隨機選擇。
/*************************************************************************
> File Name: kmin_4_2.cpp
> Author: zxl
> mail: [email protected]
> Created Time: 2016年04月11日 星期一 15時56分16秒
************************************************************************/
#include <iostream>
#include <stdlib.h>
#include <time.h>
#define MAX 1000
#define K 10
using namespace std;
int random(int a,int b)
{
srand((unsigned)time(NULL));
return (rand() %(b-a+1))+a;
}
int Partion(int A[],int start,int end)
{
int x = A[end];
int i,j;
i = start-1;
for(j = start;j< end;j++)
{
if(A[j]<= x)
{
i++;
swap(A[i],A[j]);
}
}
swap(A[i+1],A[end]);
return i+1;
}
int RandomPartion(int A[],int start,int end)
{
int i;
i = random(start,end);
swap(A[end],A[i]);
return Partion(A,start,end);
}
void RandomSelect(int A[],int start,int end,int k)
{
if(start == end)
return;
int q;
q = RandomPartion(A,start,end);
int cout = q-start+1; //低區加主員;
if(cout == k)
return;
else if(k < cout)
RandomSelect(A,start,q-1,k);
else
RandomSelect(A,q+1,end,k-cout);
}
int main()
{
int A[MAX];
int i;
for(i = 0;i<MAX;i++)
A[i] = MAX-i;
RandomSelect(A,0,MAX-1,K);
for(i = 0;i< K;i++)
cout << A[i] << endl;
return 0;
}
思路五:線性時間排序,計數排序,O(N),但是限制比較多,一般不常用
/*************************************************************************
> File Name: kmin_5.cpp
> Author: zxl
> mail: [email protected]
> Created Time: 2016年04月11日 星期一 16時41分54秒
************************************************************************/
#include <iostream>
#define MAX 1000
#define K 10
using namespace std;
void cout_sort(int A[],int length,int k)
{
int B[length];
int C[length];
int i,j;
for(i = 0;i<length;i++) //這個地方c種元素的個數,初始化時假定爲max,無重複的。
C[i] = 0;
for(j = 1;j<=length;j++)
C[A[j]] = C[A[j]]+1;
for(i = 0;i<length;i++)
C[i] = C[i-1]+ C[i];
for(j = length;j>1;j-- )
{
B[C[A[j]]] = A[j];
C[A[j]] = C[A[j]]-1;
}
for(i = 1;i<=k;i++)
cout << B[i] << endl;
}
int main(void)
{
int A[MAX];
int i;
for(i = 1;i<= MAX;i++)
A[i] = MAX-i;
cout_sort(A,MAX,K);
return 0;
}
思路六:最小堆排序,然後依次取出堆頂元素。
/*************************************************************************
> File Name: kmin_6.cpp
> Author: zxl
> mail: [email protected]
> Created Time: 2016年04月11日 星期一 17時07分33秒
************************************************************************/
#include <iostream>
#define MAX 1000
#define K 10
using namespace std;
int LEFT(int i)
{
return 2*i;
}
int RIGHT(int i)
{
return 2*i+1;
}
void Min_heapify(int A[],int size,int i)
{
int l = LEFT(i);
int r = RIGHT(i);
int small;
if(l <= size && A[l] < A[i])
small = l;
else
small = i;
if(r <= size && A[r] < A[small])
small = r;
if(small != i)
{
swap(A[i],A[small]);
Min_heapify(A,size,small);
}
}
void Build_Min_heap(int A[],int size)
{
int i;
for(i = size/2;i> 0;i--)
{
Min_heapify(A,size,i);
}
}
void Kmin(int A[],int size,int k)
{
Build_Min_heap(A,size);
int i;
int length = size;
for(i = length;i>length-k;i--)
{
swap(A[1],A[i]);
cout << A[i] << endl;
size = size-1;
Min_heapify(A,size,1);
}
}
int main(void)
{
int A[MAX];
int i;
for(i = 0;i< MAX;i++)
A[i] = MAX-i;
Kmin(A,MAX,K);
return 0;
}