查找:从N个乱序数据中找出第K小的数

从N个乱序数据中找出第K小的数


如何从N个乱序数据中,快速地找出第K小的数?

如果K接近0或者接近N,用选择排序,排几个应该就可以找到了。

如果K比较大呢?或者说,就是要找N个乱序数据中的中位数呢?

思路和快速排序很像。非常像。

Review:快速排序算法


现假设数组A,有N个元素。要快速地找出第K小的元素。

基本思想还是用递归去查找。当然,在查找的过程中,也是需要排序的。但只是粗略地排序。

关键问题还是找中轴变量pivor。把中轴变量排到它应该在的位置上。

这里涉及到一个排序。比pivor小的,都放在pivor的左边,大于或等于pivor的,都放在pivor的右边。

然后比较pId+1和K的大小。 // C++数组从0开始。

1. pId==K-1的话,返回A[pId]就好。当然不愿意承认这一点…

2. pId

3. pId>K-1的话,则要对左半段的数据继续查找。


下面贴上C++代码。

// 2017年1月3日12:31:22
// 2017年1月3日12:46:49
// 熟能生巧,背背背!

#include 
#include 
using namespace std;

int findPivorIdx(int A[], int k, int low, int high) {

	int pivor = A[low];
	while (low=pivor && lowk){
			return findKMin(A,k,low,pId-1); // 对左边递归
		}
	}

	else { // 返回low
		return A[low];
	}
}


int main () {

	
	const int N = 16;
	int A[N] = {32,122,34,5,6,74,22,11,55,66,11,33,6,1,4,8};
	int B[N];
	cout<<"原始数据"<
原始数据
32 122 34 5 6 74 22 11 55 66 11 33 6 1 4 8
排序好的数据
1 4 5 6 6 8 11 11 22 32 33 34 55 66 74 122

第1小的数是1(正确的) 1 (我写的)
第2小的数是4(正确的) 4 (我写的)
第3小的数是5(正确的) 5 (我写的)
第4小的数是6(正确的) 6 (我写的)
第5小的数是6(正确的) 6 (我写的)
第6小的数是8(正确的) 8 (我写的)
第7小的数是11(正确的) 11 (我写的)
第8小的数是11(正确的) 11 (我写的)
第9小的数是22(正确的) 22 (我写的)
第10小的数是32(正确的) 32 (我写的)
第11小的数是33(正确的) 33 (我写的)
第12小的数是34(正确的) 34 (我写的)
第13小的数是55(正确的) 55 (我写的)
第14小的数是66(正确的) 66 (我写的)
第15小的数是74(正确的) 74 (我写的)
第16小的数是122(正确的) 122 (我写的)

为了检验我的代码正确与否,我先对原始数据进行了排序。

其实,这样检验,是有问题的。

因为每次为了找到这个满足要求的数,都把x排了一部分序了。x的顺序已经不是一开始定义的那个顺序了!

所以,其实应该在每次验证后,都把x的顺序重置一下。


需要注意的是,pId是pivor在原始数据A中的第pId+1小的。

因为每次排序后,pivor会出现在排序后的正确位置上。(尽管两边的数内部顺序可能不一样,但这个pivor的位置一定对了。从小到大排)

递归调用的时候,尽管只是从A的一部分中去找,但是还是找的全局第K小的数。所以参数k一直没变。

最后,当low这是必须有的终止条件。

写递归,一定要有递推式和终止条件!二者缺一不可。


那么问题来了,为什么当low>=high的时候,是return A[low],而不是A[high]?为什么不是return 别的任何一个数?

以及,该算法的复杂度是多少?有人说是O(n)...?


有道题,问如何在数组中找出”出现频率大于0.5“的数?

1. 考虑用个map去保存吧,key就是数字、value就是频次;

2. 找中位数吧;

3. 据说用个number和times,还有个cursor。下一个数不等于number,就把number更新为下一个数,同时times--。

如果times==0了,就把times重新置为1。最后这个number就是满足题目要求的数。

第3种做法技巧性太强了。我在质疑它真的有意义吗。还是这种问题,又是像高考那样,考一堆华而不实的技巧?


从N个乱序数据中找出最小的K个数


1. 找第k小的数,通过修改A[],第k小的数及其左边,就都是满足题目要求的数。O(N)。

2. 若不能修改A[],O(NlogN)。

关键思路是,用一个大小为k的容器保存最小的k个数。从n个数中,读入1个数a。

(1) 容器未满,继续插入;

(2) 容器已满,若容器中最大的数M比a大,就把M替换成a,否则忽略。

为了快速从容器中找到最大的M(甚至将其替换),据说可用(最大堆或红黑树),根节点的值总是大于其子树中任意节点的值。O(1)得到最大数,O(logk)完成删除及插入。STL中set和multiset是基于红黑树实现的。

红黑树查找、删除、插入都只需要O(logk)的时间。

关于STL的set,请见前面的文章。

Review:STL初步之set容器


你可能感兴趣的:(算法)