《编程之美》读书笔记07: 2.5 寻找最大的K个数
问题:
从n个数中找出最大的k个数。
两种思路:
1 保存目前找到的最大k个数,每访问一个数,就与这k个数中的最小值比较,决定是否更新这k个数。储存k个数的数据结构可采用:败者树、二叉查找树、最小堆。
C++ STL提供了multiset和priority_queue容器,另外还提供了make_heap,push_heap,pop_heap方便手动构建堆结构。(测试发现,手工建堆的效率最高,当n和k增大到一定值时,采用红黑树的multiset的效率极差。手动建堆的效率相比priority_queue有略微提高。)
2 修改排序方法,去除不必要的过程。
选择排序: 只要选k次。
冒泡排序: 只要冒泡k次即可。
堆排序: 构建好最大堆后,取 k次最大值
快速排序: 分区时,根据数P将数组分为两部分,设大于P的数个数为a,小于P的数的个数为b。如果,a>=k,则从这a个数取最大的k个数,若a<k,则从b个数取最大的k-a-1个。
归并排序: 当待合并的两个数组,两数组长度和大等于k时,合并时只取前k个。或者:以(k+1)/2个数为一组,将数组分成几个组,对每组进行排序(可以采用任何一种高效的排序方法)后,两两合并时只取前k个。
计数排序: 如果都是整数,先扫描一遍找出最大值max,最小值min,再扫一遍,将每个值减去min,对这个值计数,最后从max-min开始统计,找出最大的k个数。另外,也可采用桶排序。
桶排序: 可以不对桶内的数据进行排序。
基数排序: 可以采用最高关键字比较方法,并免去相关的排序。
STL中的nth_element就是基于对intorsort的修改(introtsort是对快速排序的改进,当递归深度达到一定值时,可切换到堆排序),而partial_sort和partial_sort_copy是基于堆排序的修改,因而在k很小时,其效率可能会高于nth_element。遗憾的是:STL没有提供完全基于堆排序的nth_element。
从下面的测试结果,可以看出:在M不是很大,M/N很小时,partial_sort和partial_sort_copy尽管多了“对堆结构进行排序”这个不必要的操作,其效率仍然高于nth_element,但相差不多。而在其它情况下nth_element的效率则比其它的几种方法要高很多。
如果源数据都是整数,多数情况下(即使允许修改源数据),桶排序方法(结合计数方法)的效率比nth_element高。桶排序只需256K的内存,效率很高。在M和N至少有一个大于当前内存大小的情况下,桶排序是最佳选择,其性能远高于其它方法。
如果源数据是浮点数,根据浮点数在内存中的表示,可以对桶排序方法进行适当修改,使之对浮点数也适用。
测试程序相关说明:
① 测试程序要求不得改变源数据,某些方法要多一个复制源数据操作,可以从partial_sort_copy和partial_sort效率的差异,看出这个复制操作的影响。
② 桶排序方法对应nth_count;
③ 对堆结构的调整,采用三种途径(分别对应三个程序):利用push_heap和pop_heap、只用pop_heap、手写代码调整。(
④ multiset和heapsort方法,在相同N和M情况下,所用时间起伏很大,即所用时间对原始数据依赖性很高。
2 #include < iostream >
3 #include < vector >
4 #include < queue >
5 #include < set >
6 #include < utility >
7 #include < algorithm >
8 #include < ctime >
9 #include < cassert >
10 #include < cstdlib >
11 using namespace std;
12
13 const int check_result = 0 ; // 是否检查结果的正确性
14
15 template < typename T > void nth_mset( const T * , size_t, T * , size_t);
16 template < typename T > void nth_queue( const T * , size_t, T * , size_t);
17 template < typename T > void nth_stl( const T * , size_t, T * , size_t);
18 template < typename T > void nth_partial( const T * , size_t, T * , size_t);
19 template < typename T > void nth_partial_copy( const T * , size_t, T * , size_t);
20 template < typename T > void nth_heap_1( const T * , size_t, T * , size_t);
21 template < typename T > void nth_heap_2( const T * , size_t, T * , size_t);
22 template < typename T > void nth_heap_3( const T * , size_t, T * , size_t);
23 template < typename T > void nth_heapsort( const T * , size_t, T * , size_t);
24
25 // 使用桶排序法,仅适用于32位整数且 整数的最大重复次数小于2^32
26 void nth_count( const int * src, size_t src_size, int * dst, size_t dst_size);
27
28 // 从N个数(取值范围(-2^BITS,2^BITS) )取M个最大的数
29 template < typename T, typename R, size_t size >
30 void test(R ( & ff)[size], size_t count = 1 , size_t N = 1e8,
31 size_t M = 1e4, size_t BITS = 31 );
32
33 class Rand_num {
34 int mask;
35 public:
36 Rand_num(int i=31){ if(i>31 || i<1) i=31; mask=((1<<i)-1)&(1<<31);}
37 int operator()(){ return (((rand()&3)<<30)+(rand()<<15)+rand())&mask;}
38} ;
39
40 struct Func {
41 const char *name;
42 void (*func)(const int *, size_t, int *, size_t);
43} ;
44
45 int main()
46 {
47 Func ff[]={
48 {"nth_elmemnt",nth_stl},
49 {"nth_count",nth_count},
50 {"priority_queue",nth_queue},
51 {"partial_sort",nth_partial},
52 {"partial_sort_copy",nth_partial_copy},
53 {"heap(pop/push) ",nth_heap_1},
54 {"heap(pop/copy)",nth_heap_2},
55 {"heap(custom_pop)",nth_heap_3},
56 {"multiset",nth_mset},
57 {"heap_sort",nth_heapsort},
58 };
59 test<int>(ff,1,1e6,8e5);
60 test<int>(ff,1,1e8,1e4,10);
61 test<int>(ff,1,1e8,1e4);
62 test<int>(ff,1,1e8,1e5,10);
63 test<int>(ff,1,1e8,1e5);
64 test<int>(ff,1,1e8,1e6,10);
65 test<int>(ff,1,1e8,1e6);
66 test<int>(ff,1,1e8,5e6,10);
67 test<int>(ff,1,1e8,5e6);
68}
69
70 template < typename T >
71 void nth_heapsort( const T * src, size_t src_size, T * dst, size_t dst_size)
72 {
73 assert( dst_size >0 && dst_size <= src_size);
74 vector<T> cache(src,src+src_size);
75 T *first=&cache[0];
76 T *last=first+src_size;
77 T *pp=last;
78 make_heap(first, last);
79 for (size_t i=0; i<dst_size; ++i) pop_heap(first, pp--);
80 copy(last-dst_size,last, dst);
81}
82
83
84 template < typename T >
85 void nth_mset( const T * src, size_t src_size, T * dst, size_t dst_size)
86 {
87 assert( dst_size >0 && dst_size <= src_size);
88 assert(src);
89 assert(dst);
90 multiset<T> tmp(src, src+dst_size);
91 const T *p=src + dst_size -1;
92 const T *end=src + src_size;
93 T min=*(tmp.begin());
94 while ( ++p < end){
95 if (*p > min){
96 tmp.insert(*p);
97 tmp.erase(tmp.begin());
98 min=*(tmp.begin());
99 }
100 }
101 copy(tmp.begin(),tmp.end(),dst);
102}
103
104 template < typename T >
105 void nth_queue( const T * src, size_t src_size, T * dst, size_t dst_size)
106 {
107 assert( dst_size >0 && dst_size <= src_size);
108 assert(src);
109 assert(dst);
110 typedef greater<T> CMP;
111 CMP cmp;
112 priority_queue<T, vector<T>, CMP > tmp(src, src+dst_size);
113 const T *p=src + dst_size -1;
114 const T *end=src + src_size;
115 T toppest=tmp.top();
116 while ( ++p < end){
117 if ( cmp(*p,toppest) ){
118 tmp.pop();
119 tmp.push(*p);
120 toppest=tmp.top();
121 }
122 }
123 copy( &(tmp.top()), &(tmp.top()) + dst_size,dst);
124}
125
126
127 template < typename T >
128 void nth_stl( const T * src, size_t src_size, T * dst, size_t dst_size)
129 {
130 assert( dst_size >0 && dst_size <= src_size);
131 assert(src);
132 assert(dst);
133 vector<T> tmp(src, src+src_size);
134 greater<T> cmp;
135 nth_element(tmp.begin(),tmp.begin()+dst_size, tmp.end(), cmp);
136 copy(tmp.begin(),tmp.begin()+dst_size,dst);
137}
138
139 template < typename T >
140 void nth_partial( const T * src, size_t src_size, T * dst, size_t dst_size)
141 {
142 assert( dst_size >0 && dst_size <= src_size);
143 assert(src);
144 assert(dst);
145 vector<T> tmp(src, src+src_size);
146 greater<T> cmp;
147 partial_sort(tmp.begin(),tmp.begin()+dst_size, tmp.end(), cmp);
148 copy(tmp.begin(),tmp.begin()+dst_size,dst);
149}
150
151 template < typename T >
152 void nth_partial_copy( const T * src, size_t src_size, T * dst, size_t dst_size)
153 {
154 assert( dst_size >0 && dst_size <= src_size);
155 assert(src);
156 assert(dst);
157 greater<T> cmp;
158 partial_sort_copy(src,src+src_size,dst,dst+dst_size,cmp);
159}
160
161
162 template < typename T >
163 void nth_heap_1( const T * src, size_t src_size, T * dst, size_t dst_size)
164 {
165 assert( dst_size >0 && dst_size <= src_size);
166 greater<T> cmp;
167 copy(src, src+dst_size, dst);
168 const T *p=src + dst_size -1;
169 const T *end=src + src_size;
170 T * const first=dst;
171 T * const last=dst+dst_size;
172 make_heap( first, last, cmp);
173 T toppest=*first;
174 while ( ++p < end){
175 if ( cmp(*p, toppest) ){
176 pop_heap( first, last, cmp);
177 *(last-1)=*p;
178 push_heap( first,last, cmp);
179 toppest=*first;
180 }
181 }
182}
183
184
185 template < typename T >
186 void nth_heap_2( const T * src, size_t src_size, T * dst, size_t dst_size)
187 {
188 greater<T> cmp;
189 assert( dst_size >0 && dst_size <= src_size);
190 vector<int> tmp(src, src+dst_size+1);
191 const T *p=src + dst_size -1;
192 const T *end=src + src_size;
193 T * const first=&tmp[0];
194 T * const last=first+dst_size;
195 make_heap( first, last, cmp);
196 T toppest=*first;
197 while ( ++p < end){
198 if ( cmp(*p, toppest) ){
199 *last=*p;
200 pop_heap( first, last+1, cmp);
201 toppest=*first;
202 }
203 }
204 copy(first, last,dst);
205}
206
207
208 template < typename T >
209 void nth_heap_3( const T * src, size_t src_size, T * dst, size_t dst_size)
210 {
211 assert( dst_size >0 && dst_size <= src_size);
212 copy(src, src+dst_size, dst);
213 const T *p=src + dst_size -1;
214 const T *end=src + src_size;
215 size_t i,left;
216 make_heap( &dst[0],&dst[0]+dst_size, greater<T>() );
217 T min=dst[0];
218 while ( ++p < end){
219 if (*p > min){
220 dst[0]=*p;
221 i=0;
222 while( (left=(i<<1)+1)<dst_size-1){
223 if (dst[left] > dst[left+1]) ++left;
224 if (dst[left] >= *p) break;
225 dst[i]=dst[left];
226 i=left;
227 }
228 if (left==dst_size-1 && dst[left]<*p){
229 dst[i]=dst[left];
230 i=left;
231 }
232 dst[i]=*p;
233 min=dst[0];
234 }
235 }
236}
237
238
239 void nth_count( const int * src, size_t src_size, int * dst, size_t dst_size)
240 {
241 assert( dst_size >0 && dst_size <= src_size);
242 assert(sizeof(int)==4);
243 const unsigned TOTAL=0x10000; //桶总数
244 const unsigned MIDDLE= TOTAL >> 1; //0到TOTAL的中间位置
245 unsigned count[TOTAL]={0};
246 unsigned * const mid=count+MIDDLE;
247 size_t sum=0;
248 const int *p=src, *end=src+src_size;
249 for (; p<end; ++p) ++mid[ *p>>16];
250 unsigned pos=TOTAL;
251 while (sum < dst_size) sum += count[--pos];
252 size_t new_dst_size = count[pos]+ dst_size - sum;
253 int *q=dst;
254 int high16=pos-MIDDLE;
255
256 if (new_dst_size == 0) {
257 for (p=src,q=dst; p<end; ++p)
258 if ( (*p>>16)>= high16) *q++=*p;
259 return ;
260 }
261
262 fill(count, count+TOTAL, 0);
263
264 for (p=src,q=dst; p<end; ++p){
265 if ((*p>>16)>high16) *q++=*p;
266 else if ((*p>>16)==high16) ++count[*p&0xFFFF];
267 }
268
269 int low16=TOTAL, high16_v=high16<<16, value=0, number=0;
270 sum=0;
271 while (1){
272 number=count[--low16];
273 if (number) {
274 sum += number;
275 value=high16_v+ low16;
276 if (sum<new_dst_size){
277 for (; number>0; --number) *q++=value;
278 }else{
279 for (number -= sum - new_dst_size; number>0; --number) *q++=value;
280 return;
281 }
282 }
283 }
284}
285
286
287 template < typename T, typename R, size_t size >
288 void test(R ( & ff)[size], unsigned count, unsigned N, unsigned M, unsigned BITS)
289 {
290 assert( N>=M && M>0);
291 assert( size>0);
292 assert(ff);
293 size_t j=0, i=0;
294 vector<T> arr(N),result(M),tmp(M);
295 greater<T> cmp;
296 cout<<"\nN,M: "<<N<<" "<<M<<" "<< 1.0*M/N<< endl;
297 clock_t tb;
298 srand(time(0));
299 Rand_num rand_num(BITS);
300 for( j=0; j <count; ++j ){
301 tb=clock();
302 generate(arr.begin(),arr.end(), rand_num);
303 tb=clock()-tb;
304 cout<<"Randomizing: "<<tb<<" ms\n";
305 if(check_result){
306 tb=clock();
307 partial_sort_copy(arr.begin(), arr.end(), tmp.begin(), tmp.end(), cmp);
308 tb=clock()-tb;
309 cout<<"partial_sort_copy: "<<tb<<" ms\n";
310 }
311 for(i=0; i<size; ++i){
312 tb=clock();
313 ff[i].func(&arr[0],N,&result[0],M);
314 tb=clock()-tb;
315 cout<< ff[i].name<<" "<< tb<<" ms\n";
316 if(check_result){
317 sort(result.begin(),result.end(), cmp);
318 if( !equal(result.begin(), result.end(), tmp.begin())) {
319 cout<< ff[i].name<< " failed to pass!\nresult:\n";
320 }
321 }
322 }
323 cout<<"\n";
324 }
325}
326
327