主题:[原创]第k大的数
goal00001111
[专家分:4030] 发布于 2006-06-04 10:21:00
/*
Name:
Copyright:
Author:goal00001111
Date: 02-06-06 13:21
Description:
有一整数序列,求其序列第k大的数。已知序列每个数不大于1000,
长度不超过1000,有可能有重复数字。
如:10 50 2 31 7 6 16第二大的数为6。(k以1为起点)
函数接口为:
int KthNumOfList(int List[],int len,int k);
int List[]为整型序列
int len为序列长度
int k 为要求第几大
返回值为第k大的数
*/
/*
算法介绍:
(1) 设置一个最小长度least,当len <= least时,直接对数组按递增排序,第k个元素即为所求元素;否则,转步骤(2).
(2) 把元素划分为lenp = len/5 组, 每组5个元素,不足5个元素的那组先不予考虑.
(3) 取每组的中值元素,构成一个长度为lenp的数组p[].
(4) 对数组p[]递归地执行本算法,得到其中值元素m.
(5) 把原数组List[]划分成p,q,r三组,使得小于m的元素存放在p[],等于m的元素存放在q[],大于m的元素存放在r[].
(6) 如果lenp > k, 对p[]递归地执行本算法,并把最后返回的值存储到m;否则,转步骤(7).
(7) 如果lenp + lenq < k,对r[]递归地执行本算法,并把最后返回的值存储到m.
(8) 很明显,此时的m就是所要选择的元素, 销毁p,q,r, 并返回m.
*/
#include <iostream>
using namespace std;
int KthNumOfList(int List[],int len,int k);
static int Compare(const void *p1, const void *p2);
void Mid(int a[], int i, int p[]);
void Swap(int & a, int & b);
int main()
{
int a[8] = {10,20,30,40,5,6,7,8};
int n = KthNumOfList(a, 8, 2);
cout << n << "\n";;
getchar();
return 0;
}
int KthNumOfList(int List[], int len, int k)
{
const int least = 64; //设置一个最小长度least
if (len <= least)
{
qsort(List, len, sizeof(int), Compare); //对数组按递增排序
return List[k-1];
}
int *p = new int[3*len/4];
int lenp = len/5; //把元素划分为lenp = len/5 组, 每组5个元素,不足5个元素的那组先不予考虑.
for (int i=0; i<lenp; i++)
{
Mid(List, i, p); //取每组的中值元素,构成一个长度为lenp的数组p[].
}
int m = KthNumOfList(p, lenp, lenp/2 + lenp%2);//对数组p[]递归地执行本算法,得到其中值元素m.
int *q = new int[3*len/4];
int *r = new int[3*len/4];
int lenq = 0;
int lenr = 0;
lenp = 0;
//把原数组List[]划分成p,q,r三组,使得小于m的元素存放在p[],等于m的元素存放在q[],大于m的元素存放在r[].
for (int i=0; i<len; i++)
{
if (List[i] < m)
p[lenp++] = List[i];
else if (List[i] == m)
q[lenq++] = List[i];
else
r[lenr++] = List[i];
}
if (lenp > k)
{
m = KthNumOfList(p, lenp, k);
}
else if(lenp + lenq < k)
{
m = KthNumOfList(r, lenr, k-lenp-lenq);
}
//很明显,此时的m就是所要选择的元素, 销毁p,q,r, 并返回m.
delete []p;
delete []q;
delete []r;
return m;
}
static int Compare(const void *p1, const void *p2)
{
return (*(int *)p1) - (*(int *)p2);
}
/*
函数介绍:从数组a[]中,每5个元素为一组,取第i组的中值元素作为数组p[]的第i个元素.
输入: 数组a[].
组号 i .
输出:存放中值元素的数组p[].
*/
void Mid(int a[], int i, int p[])
{
int k = 5 * i;
if (a[k] > a[k+2])
Swap(a[k], a[k+2]);
if (a[k+1] > a[k+3])
Swap(a[k+1], a[k+3]);
if (a[k] > a[k+1])
Swap(a[k], a[k+1]);
if (a[k+2] > a[k+3])
Swap(a[k+2], a[k+3]);
if (a[k+1] > a[k+2])
Swap(a[k+1], a[k+2]);
if (a[k+4] > a[k+2])
p[i] = a[k+2];
else if (a[k+4] > a[k+1])
p[i] = a[k+4];
else
p[i] = a[k+1];
}
void Swap(int & a, int & b)
{
int temp = a;
a = b;
b = temp;
}
回复列表 (共17个回复)
沙发
euc [专家分:4310] 发布于 2006-06-04 18:26:00
有个问题,好像这个程序求的是有序数列的第k个数,例如:1,1,(100个1)...,2,2这个数列,如果让求第2大的数,答案应该是'2'.而这个好像得出'1'.也许有不同的理解吧.
板凳
goal00001111 [专家分:4030] 发布于 2006-06-04 21:27:00
是啊,是这样的,我都没有考虑到呢?
谢谢!
3 楼
euc [专家分:4310] 发布于 2006-06-05 17:08:00
这么‘怪异’的方法是你原创的吗,不管怎么说真是极品~
还有个问题:为什么要new int[3*len/4];?
4 楼
goal00001111 [专家分:4030] 发布于 2006-06-05 21:17:00
不是我的原创,我从书<<算法设计与分析>>(郑宗汉 郑晓明 编著 清华大学出版社)上看到,然后自己修改了一下.
new int[3*len/4];是根据概率统计得来的,即比m大,小,或等于的数数量不超过new int[3*len/4];其实我觉得还是写成new int[len];比较保险.
5 楼
hmlb [专家分:90] 发布于 2006-06-06 17:34:00
我写了一个
/************************************************************
有一整数序列,求其序列第k大的数。已知序列每个数不大于1000,
长度不超过1000,有可能有重复数字。
如:10 50 2 31 7 6 16第二大的数为6。(k以1为起点)
************************************************************/
#include <stdio.h>
int KthNumOfList(int* pList, int nLen, int k);
int main(int argc, char* argv[])
{
int pList[] = {10, 50, 2, 31, 7, 6, 16};
int n = KthNumOfList(pList, 7, 2);
if(n <= 1000)
printf("The %dth number: %d\n",
2, n);
else
printf("Can't find the number\n");
return 0;
}
/********************************************
pList:数组入口地址
nLen:数组长度
k:第几大
返回值:返回第k大的数
********************************************/
int KthNumOfList(int* pList, int nLen, int k)
{
//有效性校验超出范围,肯定不对
if((k > nLen) || (k <= 0))
return 1001;
//索性拿选择排序试试
int nRet;
int nIndex = 0; //目前到了第几大
for(int i=0; i<nLen; i++)
{
int nLowIndex = nLen -1;
for(int j=nLen-1; j >= i; j--)
{
if(pList[j] < pList[nLowIndex])
nLowIndex = j;
}
//交换
int temp = pList[nLowIndex];
pList[nLowIndex] = pList[i];
pList[i] = temp;
if(i == 0)
{
nIndex++;
nRet = pList[i];
}
else
{
if(pList[i] > pList[i-1])
{
nIndex++;
nRet = pList[i];
}
}
if(nIndex == k)
break;
}
if(nIndex == k)
return nRet;
else
return 1001;
}
6 楼
euc [专家分:4310] 发布于 2006-06-06 20:07:00
不好意思,goal15的这个方法华而不实,它费了半天劲分5组,new [],递归,目的不还是先求中位数(好像还是近似的),为了进行2分?求近似的中位数可以用平均数替代,也可以分段取样。我的意思是,这个方法用太多精力去求划分值了,按照分治的思想,它就像快排,划分值的选择是很灵活的,应该简单些,而且不用动态分配。
7 楼
goal00001111 [专家分:4030] 发布于 2006-06-06 22:28:00
当数据量很大的时候,找到那个中位数可以减少递归的次数啊.
8 楼
rickone [专家分:15390] 发布于 2006-06-12 14:38:00
我没看懂这个算法,那个m什么时候冒出来的?我觉得这和求前k大的数是一样的,我觉得用堆排序最好,理由是:因为不用对整体进行排序,选择法是最好的,其他类型的都是对整体的排序。
首先判断以下k靠那边,第n大的也就是第1小的。
9 楼
if007 [专家分:650] 发布于 2006-06-12 20:00:00
楼主的算法似乎有问题:
(1) 设置一个最小长度least,当len <= least时,直接对数组按递增排序,第k个元素即为所求元素;否则,转步骤(2).
(2) 把元素划分为lenp = len/5 组, 每组5个元素,不足5个元素的那组先不予考虑.
(3) 取每组的中值元素,构成一个长度为lenp的数组p[].
(4) 对数组p[]递归地执行本算法,得到其中值元素m.
假设len=30,求第7大。 那么lenp=6, 若least=8,那么求不了第k个元素,若least=1,那么再递归后,这样这样最后,就是会出len<=least, 这时还是不会有第K个元素给你求(甚至可能下标越界了), 而那个M在又那里冒出来呢?
其次上面几步,也许我没理解,分那么多次求的M不就是他的中间值吗?那似乎效率不高,单是每个分组求它的中间值就是一场排序,求出中间值后,又再求中间值的中间值, 那我还不如直接每组就求它的第K大,然后再求第K大里的第K大。。。
最后,程序的终止条件是: lenp<=k<=lenp+lenp, (看起来好像归并的模式哦,呵呵), 不过这时的K是否就是答案呢。。。我觉得按以上的算法,并不能表白。
若然求第K大,我觉得最合适的就是用直接选择排序,因为它不用对整个数组进行比较, 用快速和堆需要对整个数组进行; 而直接插入排序则可能涉及移到空间太长。
顺便一说,求第K大在考研的卷子上常有(以前有做了下,所以有印象), 是选择题,答案好像也是用直接选择排序,日子太久了,有些忘了。。。不过我是选择选择排序的。
10 楼
boxertony [专家分:23030] 发布于 2006-06-12 23:47:00
[quote]
若然求第K大,我觉得最合适的就是用直接选择排序,因为它不用对整个数组进行比较, 用快速和堆需要对整个数组进行; 而直接插入排序则可能涉及移到空间太长。
[/quote]
还是用堆排序更合适些。
对于直接选择排序,需要比较次数为(2n-k-1)k/2,如果k取1~n中数概率相等的话,则平均k=n/2,所以平均比较次数为(2n-n/2-1)*n/4 = (3n-2)n/8
当然,当k<c*log2(n)时确实比快速排序好些,但从平均来说就不如快排了。至于堆排序由于用不着对所有数据完全排序,所以应该比快排更快些。
我来回复