問題:一個文件有大量的數,現要對文件排序,但內存無法一次讀取完全,而磁盤空間足夠,要如何排序。
學習了幾篇博客:
1. july大神的海量數據排序(他的其他博客都很值得看)
2. 對july大神的算法進行改進不用選擇法而是敗者樹的博客
3. 以及另一篇但不知道是否爲原創的博客
4. 還有生成不重複亂序m-n的數的博客(先生成m-n的數,然後洗牌算法)
以上幾篇博客寫得很完全了,看懂了思路,自己臨摹寫一個簡單的測試 ….
先用生成隨機數的代碼生成data.txt待排序大文件:
(照搬的前面貼的博客的,加了個取低31位,因爲我的跑出來老是段錯誤,gdb調試生成的隨機索引爲負數,太困了不想深究原因了 …)
//生成隨機的不重複的測試數據
#include <iostream>
#include <stdio.h>
#include <time.h>
#include <assert.h>
#include <stdlib.h> // RAND_MAX
using namespace std;
//產生[i,u]區間的隨機數
int randint(int l, int u)
{
int a = RAND_MAX * rand();
int b = rand();
//取低31位
int c = ( a + b ) & (0x7fffffff) % ( u - l + 1 );
int d = l + c;
return d;
}
const int size = 10000000;
// const int size = 10;
int num[size];
int main()
{
srand((int)time(NULL));
int i, j;
FILE *fp = fopen("data.txt", "w");
assert(fp);
for (i = 0; i < size; i++)
num[i] = i+1;
// printf("rand_max:%d\n", RAND_MAX);
for (i = 0; i < size; i++)
{
j = randint(i, size-1);
// printf("%d ", j);
fflush(stdout);
int t = num[i]; num[i] = num[j]; num[j] = t;
//swap(num[i], num[j]);
}
// printf("\n");
for (i = 0; i < size; i++)
fprintf(fp, "%d\n", num[i]);
fclose(fp);
return 0;
}
對data.txt文件開始外排序:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#define TEMP_PREFIX "ftemp_"
#define OUTPUT_FILE "out_data.txt"
#define MAX_WAYS 100
// 無窮大,用於某一路文件或緩衝區讀到尾了
// 敗者樹產生一個註定失敗的結點
#define INFINITY 1000000000
int *buf;
int lst[MAX_WAYS];
void read_data(FILE *fp, int *buf)
{
if ( fscanf(fp, "%d ", buf) == EOF )
*buf = INFINITY;
}
int partition( int *arr, int p, int r )
{
int x = arr[r];
int i = p - 1;
int j = 0, temp;
for ( j = p; j <= r - 1; j++ ) {
if ( arr[j] <= x ) {
i += 1;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
temp = arr[i + 1];
arr[i + 1] = arr[r];
arr[r] = temp;
return i + 1;
}
void quick_sort( int *arr, int p, int r )
{
if ( p < r ) {
int q = partition( arr, p, r );
quick_sort( arr, p, q - 1 );
quick_sort( arr, q + 1, r );
}
}
void adjust( int k, int s )
{
// t爲結點s在敗者樹數組的父結點,
// 例如64路歸併,輸入63/62,他們
// 的父結點均爲 63
int t = ( k + s ) / 2;
while ( t > 0 ) {
if ( s == -1 ) {
break;
}
// 第一趟,輸入一個葉子結點s,讓s與父結點的值比較
// (這裏父結點一定保存上一次比較的較大者),如果s
// 大於父結點的值,表示s爲新的敗者,將表示s結點的
// 索引放到父結點處,勝者(父結點)繼續到更上層的
// 父結點進行比較,這樣比較完後,頂點一定放置的最
// 小的值
// 將敗者樹想象爲一場淘汰賽,假如有8個參賽者入口,
// 8個參賽者編號1-8,第一輪(即初始化敗者樹),假
// 設產生了2/4/6/8四強敗者,放置到8個入口上一層(
// 即父結點),然後再產生兩強敗者5/7,放置到更上一
// 層的結點,然後產生最後的失敗者1,而剩餘的8就是最
// 終的勝者,這樣一棵敗者樹就初始化好了,
// 8個入口,之後我們可以隨便哪個入口加入一個參賽者,
// 這個參賽者只需要與父結點進行比較,敗者留下,勝者
// 可以往更高的父結點去參加比賽....這樣每一輪進入一
// 個參賽者,每次能得到一個新的冠軍(最小值),然後寫入
// 文件末尾
// 這裏的8其實就是8路歸併,這個入口的參賽者每次就去
// 讀取8個排好序的文件或緩衝區
if ( buf[s] > buf[lst[t]] ) {
int temp = s;
s = lst[t];
lst[t] = temp;
}
t >>= 1;
}
// 以2^n次方來算,頂層敗者編號爲1,所以敗者樹數組lst[0]一定
// 沒存東西,可以用來存放最後的冠軍
lst[0] = s;
}
void create_loser_tree(int k)
{
int i = 0;
for( i; i < k; i++ ) {
lst[i] = -1;
}
for( i = k - 1; i >= 0; i-- ) {
adjust(k, i);
}
}
void k_merge(
int k )
{
int i = 0;
FILE **ftemp = ( FILE * )malloc( sizeof(FILE *) * k );
FILE *fout = NULL;
// 歸併路數大小的數組,每個數組值存放每一個歸併路文件讀取的
// 一個值,某一個索引的值寫入輸出文件,又讀取對應文件下一個
// 值補充
buf = ( int * )malloc( sizeof(int) * k );
fout = fopen( OUTPUT_FILE, "w+" );
for ( i; i < k; i++ ) {
char file_name[20] = {0};
snprintf( file_name, sizeof(file_name), TEMP_PREFIX"%d", i );
ftemp[i] = fopen( file_name, "r" );
// 讀取每個排序好的臨時文件第一個數
fscanf( ( FILE * )ftemp[i], "%d ", buf + i );
}
// 以排好序文件第一個數的數組來創建敗者樹,
// 樹結點產生敗者,這樣以後的每輪比較只需要
// 去文件或緩衝區讀取下一個值加入敗者樹入口即可
create_loser_tree( k );
// 開始歸併, 哪一個入口產生的冠軍,先把冠軍寫入輸出文件,
// 然後冠軍所屬的文件或緩衝區再讀入一個數進行比賽,如果某一路
// 文件或緩衝區讀到尾了,那麼這個入口的參賽者爲無限大,這樣
// 與之共有一個父結點的兄弟結點每次讀取的值都能成爲勝者,參加
// 父結點以上的比較,到所有節點都讀完時,最終敗者結點,即lst[1]
// 爲無窮大,再加入一個參賽者,lst[0]也爲無窮大了,
while ( buf[lst[0]] != INFINITY ) {
// 讀取冠軍的值
int q = lst[0];
// 將冠軍寫入輸出文件
fprintf(fout, "%d\n", buf[q]);
// 讀取冠軍所屬隊列(文件或緩衝區)的下一個值
read_data(ftemp[q], &buf[q]);
// 加入了一個新參賽者,調整敗者樹
adjust(k, q);
}
// 清理
free( buf );
for ( i = 0; i < k; i++ ) {
fclose(ftemp[i]);
}
}
void memory_sort_small_file(
FILE *fp,
int num, // 待排序數的數量
int k )
{
int i = 0;
int num_per_ways = num / k; // 每一路多少個數
int *buf = NULL;
FILE **ftemp = ( FILE * )malloc( sizeof(FILE *) * k );
buf = ( int * )malloc( sizeof(int) * num_per_ways + 1000 );
// for ( i = 0; i < k; i++ ) {
// char temp_buf[20] = {0};
// snprintf( temp_buf, sizeof(temp_buf), TEMP_PREFIX"%d", i);
// ftemp[i] = fopen( temp_buf, "w+" );
// if ( ftemp[i] == NULL ) {
// printf("[%s:%d],error occured!!(%s)\n", __func__, __LINE__, strerror(errno));
// exit( 0 );
// }
// }
// 先不處理最後一個,可能總數/k路帶餘數,多餘的
// 留到最後一個文件處理
k--;
while ( k > 0 ) {
char temp_buf[20] = {0};
snprintf( temp_buf, sizeof(temp_buf), TEMP_PREFIX"%d", k);
ftemp[k] = fopen( temp_buf, "w+" );
if ( ftemp[k] == NULL ) {
printf("[%s:%d],error occured!!(%s)\n", __func__, __LINE__, strerror(errno));
exit( 0 );
}
i = 0;
memset( buf, 0, sizeof(buf) );
for ( i; i < num_per_ways; i++ ) {
fscanf(fp, "%d ", &buf[i]);
}
printf("%s:%d, K:%d\n", __func__, __LINE__, k);
quick_sort( buf, 0, num_per_ways - 1 );
for ( i = 0; i < num_per_ways; i++ ) {
fprintf(ftemp[k], "%d ", buf[i]);
}
fclose( ftemp[k] );
k--;
}
// 處理剩餘的最後一個待排序文件
char temp_buf[20] = {0};
snprintf( temp_buf, sizeof(temp_buf), TEMP_PREFIX"%d", 0);
ftemp[0] = fopen( temp_buf, "w+" );
if ( ftemp[0] == NULL ) {
printf("[%s:%d],error occured!!(%s)\n", __func__, __LINE__, strerror(errno));
exit( 0 );
}
i = 0;
while ( fscanf(fp, "%d ", &buf[i]) != EOF ) i++;
printf("%s:%d, K:%d\n", __func__, __LINE__, 0);
quick_sort( buf, 0, i );
int j = 0;
for ( j = 0; j <= i; j++ ) {
fprintf(ftemp[0], "%d ", buf[j]);
}
free( buf );
fclose( ftemp[0] );
}
int main(
int argc,
char **argv )
{
if ( argc != 3 ) {
printf("usage:\n\t./xxx file_name k ways to merge\n");
exit( 0 );
}
int k = atoi( argv[2] );
char *file_name = argv[1];
FILE *fp = fopen(file_name, "r");
if ( fp == NULL ) {
printf("[%s:%d],error occured!!(%s)\n", __func__, __LINE__, strerror(errno));
exit( 0 );
}
time_t t1 = time(NULL), t2, t3;
memory_sort_small_file( fp, 10000000, k );
t2 = time(NULL);
k_merge( k );
t3 = time(NULL);
printf("---------------------------finish-----------------------------\n");
printf("\tmemory sort & ouput to temp file cost: %ds\n", (int)(t2 - t1));
printf("\tk_merge & ouput to file cost: %ds\n", (int)(t3 - t2));
printf("\ttotal cost time: %ds\n", (int)(t3 - t1));
printf("--------------------------------------------------------------\n");
fclose( fp );
return 0;
}
64路歸併排序1000w個數用時:
生成文件:
排序後的文件頭和尾:
代碼註釋寫了很多了,以後忘了回頭看看也能記起來 …..
好睏 ________________________