1. 程式人生 > >python兩種方法實現從1000萬個隨機數中找出top n元素(附c語言版)

python兩種方法實現從1000萬個隨機數中找出top n元素(附c語言版)

轉載請註明地址:http://blog.csdn.net/echoutopia/article/details/51731269

很早之前看到一道面試題:

有一個長度為1000w個數組,每個元素互不重複,找出其中top n元素。

我感覺重複或者不重複都差不多,所以沒管不重複這個條件,但是如果使用點陣圖排序,那麼會把重複的剔除掉。

使用點陣圖另外一個限制是點陣圖陣列的大小由最大的數決定,但是我個人有個思路就是確定一個數MAX,然後建立兩個或多個位圖陣列,大於MAX的數就減去MAX放在其他點陣圖陣列中。

我把1000w個結果放在了檔案中,方便重複利用,生成程式碼:

import random

with 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]
real	0m23.115s
user	0m22.504s
sys	0m0.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]

real	0m10.210s
user	0m9.184s
sys	0m1.012s

速度提升很多(具體時間看電腦配置,我公司的電腦就只要6秒),而且隨著N增大,第二種方法耗時不會怎麼增加,但是第一種與第二種方法耗時差距會越來越大,因為第一種方法尋找最小數開銷挺大的。

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

real	0m9.714s
user	0m9.572s
sys	0m0.124s
第一種方法:
real	2m51.504s
user	2m51.116s
sys	0m0.192s
兩者差了將近20倍,恐怖

n等於1000這裡我把print去掉了,因為print1000個會耗時不少,影響結果,所以這裡n=1000時比n=100時耗時少

使用第二種排序,如果資料不重複,那麼點陣圖陣列每個元素的沒個bit都代表一個數字,如果資料有重複,那麼有些位就空著,會有一點浪費。

本來想試試c語言,把1000w個數全放在數組裡看看有多快,但是我的c語言很菜,嫌麻煩,所以還是從檔案讀取的,而且沒用快取,直接一行一行讀取的,就更慢了,

,執行結果:

9999882
9999880
9999879
9999878
9999877
9999875
9999874
9999873
9999872
9999871
9999870
9999869
9999867
9999866
9999865
9999864
9999863
9999860
9999859


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

上面很多數字省略。

程式碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_SIZE  312501

void 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都代表一個數字,如果資料有重複,那麼有些位就空著,會有一點浪費。

本人出於個人興趣,建立了一個個人公眾號,每天篩選國外網友發現的有趣的事情推送到公眾號,歡迎關注!