python两种方法实现从1000万个随机数中找出top n元素(附c语言版)

来源:互联网 发布:node excelexport 编辑:程序博客网 时间:2024/06/04 18:14

转载请注明地址:http://blog.csdn.net/echoutopia/article/details/51731269

很早之前看到一道面试题:

有一个长度为1000w个数组,每个元素互不重复,找出其中top n元素。

我感觉重复或者不重复都差不多,所以没管不重复这个条件,但是如果使用位图排序,那么会把重复的剔除掉。

使用位图另外一个限制是位图数组的大小由最大的数决定,但是我个人有个思路就是确定一个数MAX,然后创建两个或多个位图数组,大于MAX的数就减去MAX放在其他位图数组中。

我把1000w个结果放在了文件中,方便重复利用,生成代码:

import randomwith open("random_number.txt","w") as f:    for i in range(1,10000000):        f.write("%s\n" % random.randint(1,10000000))

看大家讨论大概有两种实现方式:

第一种,设置一个n个元素的数组,这里把要排序的数组称为大数组,n个元素的数组称为小数组,

在遍历大数组时,淘汰掉小数组里最小的数,最后将小数组排序便是排序结果。代码:

def stack_sort():    n_list = []    with open("random_number.txt","r") as f:        for i in f:            number = int(i.strip())            if len(n_list) == 100:                min_element = min(n_list)                if number > min_element:                    n_list[n_list.index(min_element)] = number            else:                n_list.append(number)    print n_list
执行代码:

time python sort_10_million.py
结果:


[10000000, 9999998, 9999997, 9999995, 9999994, 9999992, 9999991, 9999989, 9999988, 9999987, 9999985, 9999984, 9999983, 9999982, 9999981, 9999978, 9999975, 9999974, 9999973, 9999970, 9999969, 9999967, 9999965, 9999964, 9999963, 9999961, 9999959, 9999958, 9999957, 9999956, 9999955, 9999952, 9999951, 9999950, 9999949, 9999948, 9999947, 9999944, 9999943, 9999941, 9999939, 9999937, 9999936, 9999935, 9999934, 9999932, 9999929, 9999927, 9999925, 9999924, 9999923, 9999922, 9999920, 9999917, 9999915, 9999914, 9999913, 9999912, 9999911, 9999909, 9999908, 9999907, 9999906, 9999905, 9999904, 9999903, 9999902, 9999901, 9999900, 9999899, 9999897, 9999895, 9999894, 9999893, 9999892, 9999890, 9999889, 9999887, 9999884, 9999883, 9999879, 9999878, 9999875, 9999873, 9999871, 9999870, 9999869, 9999868, 9999867, 9999866, 9999865, 9999864, 9999862, 9999861, 9999858, 9999856, 9999855, 9999854, 9999853, 9999852, 9999851, 9999850, 9999847, 9999846, 9999845, 9999843, 9999841]
real0m23.115suser0m22.504ssys0m0.564s
凭感觉时间主要花在了字符串处理(转换为int)和寻找小数组最小元素和最小元素的数组下标去了

第二种,是使用位图来实现排序。

一个4字节的int有32个bit,每一bit都可以表示一个数字。具体实现:

使用一个数组,每个元素都是4字节(python的int类型长度可变,我们只要使用4个字节就行了),长度为要排序的数字个数(我们这1000w个)整除32再加1,这样就能把1000w个数字都存在这个数组里面了。怎么存呢?使用整除和余数。将大数组里面的每个数字整除32,得到的数字决定放到小数组哪个元素里面去,等于小数组的下标,再用这个数字除以32得到的余数来决定具体放到那个元素的哪个bit。例如:

big_list[0] = 72,那么index= 72 // 32 = 2。72 % 32 = 8,所以 small_list[2] = small_list[2] | (1 << 8 & 1)。

将大数组遍历一遍,这样就能为每个数字分配一个bit了,而且大的数字在小数组中下标比较大,而在同一元素内,位数越高的bit所代表的数字越大。这就意味着遍历一次就将所有的数字排序了,想取top多少都行,而且时间复杂度为O(N)。只是内存占用稍大,1000w个数字,小数组就得有312501个元素,每个元素4个字节,大概占1.2M内存,感觉还行(python不止这么多,python的列表实现还有很多额外的开销。)

代码:

def map_sort(n):      map_list = []      list_len = 10000000//32+1      for i in range(0,list_len):          map_list.append(0)      # print len(map_list)      with open("random_number.txt","r") as f:          for i in f:              number = int(i.strip())              integer = number // 32              mod = number % 32              map_list[integer] = map_list[integer] | (1 << mod)        sorted_list = []        for i in range(list_len-1,-1,-1):          for j in range(31,-1,-1):              if (map_list[i] >> j) & 1 == 1 and len(sorted_list) < 100:                  origin_number = i*32 + j                  sorted_list.append(origin_number)      print sorted_list  
执行代码:
time python sort_10_million.py
执行结果:
[10000000, 9999998, 9999997, 9999995, 9999994, 9999992, 9999991, 9999989, 9999988, 9999987, 9999985, 9999984, 9999983, 9999982, 9999981, 9999978, 9999975, 9999974, 9999973, 9999970, 9999969, 9999967, 9999965, 9999964, 9999963, 9999961, 9999959, 9999958, 9999957, 9999956, 9999955, 9999952, 9999951, 9999950, 9999949, 9999948, 9999947, 9999944, 9999943, 9999941, 9999939, 9999937, 9999936, 9999935, 9999934, 9999932, 9999929, 9999927, 9999925, 9999924, 9999923, 9999922, 9999920, 9999917, 9999915, 9999914, 9999913, 9999912, 9999911, 9999909, 9999908, 9999907, 9999906, 9999905, 9999904, 9999903, 9999902, 9999901, 9999900, 9999899, 9999897, 9999895, 9999894, 9999893, 9999892, 9999890, 9999889, 9999887, 9999884, 9999883, 9999879, 9999878, 9999875, 9999873, 9999871, 9999870, 9999869, 9999868, 9999867, 9999866, 9999865, 9999864, 9999862, 9999861, 9999858, 9999856, 9999855, 9999854, 9999853, 9999852, 9999851, 9999850, 9999847, 9999846, 9999845, 9999843, 9999841]real0m10.210suser0m9.184ssys0m1.012s

速度提升很多(具体时间看电脑配置,我公司的电脑就只要6秒),而且随着N增大,第二种方法耗时不会怎么增加,但是第一种与第二种方法耗时差距会越来越大,因为第一种方法寻找最小数开销挺大的。

比如top n n取1000,第二种方法

real0m9.714suser0m9.572ssys0m0.124s
第一种方法:
real2m51.504suser2m51.116ssys0m0.192s
两者差了将近20倍,恐怖

n等于1000这里我把print去掉了,因为print1000个会耗时不少,影响结果,所以这里n=1000时比n=100时耗时少

使用第二种排序,如果数据不重复,那么位图数组每个元素的没个bit都代表一个数字,如果数据有重复,那么有些位就空着,会有一点浪费。

本来想试试c语言,把1000w个数全放在数组里看看有多快,但是我的c语言很菜,嫌麻烦,所以还是从文件读取的,而且没用缓存,直接一行一行读取的,就更慢了,

,执行结果:

9999882999988099998799999878999987799998759999874999987399998729999871999987099998699999867999986699998659999864999986399998609999859real<span style="white-space:pre"></span>0m0.676suser<span style="white-space:pre"></span>0m0.580ssys<span style="white-space:pre"></span>0m0.084s

上面很多数字省略。

代码:

#include <stdio.h>#include <stdlib.h>#include <string.h>#define MAX_SIZE  312501void main(){    unsigned long small_array[MAX_SIZE] = {0};    int sorted_array[100];    char number[10];    FILE *fp;    int integer,mod,tmp,i,j;    int k=0;    if(NULL == (fp = fopen("random_number.txt","r"))){        printf("error\n");        exit(1);    }    while (!feof(fp)){        fgets(number,10,fp);        tmp = atoi(number);        integer = tmp/32;        mod = tmp%32;        small_array[integer] = small_array[integer] | (1 << mod);    }    for(i=MAX_SIZE-1;i>=0;i--){        for (j=31;j>=0;j--){            if ((((small_array[i] >> j) & 1) == 1) &&sorted_array[99] == 0){                sorted_array[k] = i*32 + j;                k++;            }        }    }    for(i=0;i<100;i++){        printf("%d\n",sorted_array[i]);    }}


使用第二种排序,如果数据不重复,那么位图数组每个元素的没个bit都代表一个数字,如果数据有重复,那么有些位就空着,会有一点浪费。
0 0
原创粉丝点击