您的位置:首页 > 编程语言 > C语言/C++

关于k小的实现及优化(c)

2016-03-14 20:20 274 查看
在分治中有一个经典案例是求中项及第k小的元素。先不管算法书里面是怎么解决这个问题的,如果让我们自己来想,这个问题应该怎么解决?惯性思维肯定是先排序然后求出第k小的值。

排序算法我们首推肯定是快速排序,用快速排序来求第k小的值的算法如下(c):

#include "stdafx.h"

void quickSort(int a[],int s,int k){
int i,j,tmp;
if(s<k){
i = s;j=k,tmp = a[s];
while(i<j){
while(i<j&&tmp<=a[j])
j--;
a[i] = a[j];
while(i<j&&a[i]<=tmp)
i++;
a[j] = a[i];
}
a[i] = tmp;
quickSort(a,s,i-1);
quickSort(a,i+1,k);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int a[] = {8,7,6,5,4,3,2,1},k;
quickSort(a,0,7);
for(int  i  = 0;i<8;i++)
printf("%d",a[i]);
printf("\n");
scanf("%d",&k);
printf("k小:%d\n",a[k-1]);
system("pause");
return 0;
}


更进一步,我们发现其实如果单纯的求k小并不需要把整个数组都排好序。当一趟划分完之后,如果i小于k,那么k小在后半部分,前半部分就不需要递归排序了;同理,如果i大于k,那么k小在前半部分,后半部分就不需要递归排序了。

修改后的算法如下:

#include "stdafx.h"

void quickSort(int a[],int s,int k,int x){
int i,j,tmp;
if(s<k){
i = s;j=k,tmp = a[s];
while(i<j){
while(i<j&&tmp<=a[j])
j--;
a[i] = a[j];
while(i<j&&a[i]<=tmp)
i++;
a[j] = a[i];
}
a[i] = tmp;
if(i>x-1){
quickSort(a,s,i-1,x);
}else if(i<x-1){
quickSort(a,i+1,k,x);
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int a[] = {8,7,6,5,4,3,2,1},k;
scanf("%d",&k);
quickSort(a,0,7,k);
printf("k小:%d\n",a[k-1]);
system("pause");
return 0;
}


很明显,第二种写法比第一种简单了许多。上述两种方法的时间复杂度均为 nlogn。接下来看看算法书上的方法,与上述的思想类似,也是从递归调用的途中舍弃掉一部分无关的值,不过比上述方法要更加复杂一些:

void quickSort2(int a[],int s,int k){
int i,j,tmp;
if(s<k){
i = s;j=k,tmp = a[s];
while(i<j){
while(i<j&&tmp<=a[j])
j--;
a[i] = a[j];
while(i<j&&a[i]<=tmp)
i++;
a[j] = a[i];
}
a[i] = tmp;
quickSort2(a,s,i-1);
quickSort2(a,i+1,k);
}
}
//寻找第k小的元素,但会破坏原数组的顺序
int select(int * A, int low, int high, int k) {
int result = 0;
int p = high-low+1;
if (p < 6/*44*/) {
quickSort2(A, low, high);
return A[k-1];
}
int q = p / 5;
int * M = new int [q];
for (int i = 0; i < q; i++) {
quickSort2(A, i*5, i*5+4);
M[i] = A[i*5+2];
}
int mm = select(M, 0, q-1, int(ceil(q/2.0)));

int * A1 = new int [p];
int * A2 = new int [p];
int * A3 = new int [p];
int count1 = 0, count2 = 0, count3 = 0;
for (int i = low; i <= high; i++) {
if (A[i] < mm) {
A1[count1++] = A[i];
} else if (A[i] == mm) {
A2[count2++] = A[i];
} else {
A3[count3++] = A[i];
}
}
if (count1 >= k) {
result = select(A1, 0, count1-1, k);
} else if (count1+count2 >= k) {
result = mm;
} else if (count1+count2 < k) {
result = select(A3, 0, count3-1, k-count1-count2);
}
return result;
}


A中总共q个数,分成q/5组,每组5个元素。每组分别取中项,组成一个大小为q/5的数组M,求M的中项mm(若M中元素数大于6则一直递归,否则通过快排求出mm)。将A分i成三份A1,A2,A3。分三种情况讨论:若k小在A1,将A2,A3舍弃,A=A1;若k小在A2,则直接输出k=mm;若k小在A3,将A1,A2舍弃,A=A3。继续递归,一直到求出k小为止。

这种算法的时间复杂度可以达到 n。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 k小 分治