可見,在DOS有限的內存條件下,磁盤文件分片歸併排序是使用比較廣泛的一種外存儲器排序算法。現在計算機的物理內存一般足夠大(最小的也有256MB吧),Windows的虛擬內存更是多達4個GB(對每一個應用程序而言),這對於很多磁盤文件的內存排序應該是足夠了,況且現在的記錄文件都放在各種數據庫中,所以磁盤文件分片歸併排序算法可能沒有市場了(不過內存多路歸併排序還是有市場的)。作爲懷舊,把代碼貼在這裏,以免“失傳”!
/**************************************************************************
* 文 件 名 : MERGE.H *
* 編 制 人 : 湖北省公安縣統計局 毛 澤 發 *
* 日 期 : 1991.8 *
**************************************************************************/
#define S_IREAD 0x0100
#define S_IWRITE 0x0080
#if defined(__TINY__) || defined(__SMALL__) || defined(__MENIUM__)
#define SSIZE 25600 /* 排序緩衝區字節 */
#define NULL 0
#else
#define SSIZE 65024 /* 排序緩衝區字節 */
#define NULL 0L
#endif
#define MAXMERGE 4 /* 排序合併每趟每次最大片 */
#define MAXMEREC (SSIZE / (MAXMERGE + 1)) /* 文件最大記錄長 */
typedef int cdecl mercmpf(const void *, const void *);
/* 通用排序函數.
參 數:排序文件名;原文件名;原文件頭字節數;文件記錄長;用戶提供的比較函數.
返回值:成功 > 0;內存不夠.記錄超長返回 0;文件操作出錯 -1 */
int fmerge(char *foname, char *finame, int ftops, int lrd, mercmpf *cmpf);
/**************************************************************************
* 文 件 名 : MERGE.C *
* 編 制 人 : 湖北省公安縣統計局 毛 澤 發 *
* 日 期 : 1991.8 *
**************************************************************************/
#include <io.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include "merge.h"
static mercmpf *mercmp = NULL; /* 比較函數 */
static char *merbuf = NULL; /* 排序動態緩衝區 */
static char *filetop = NULL; /* 原文件文件頭存放動態緩衝區 */
static int filetopchs; /* 原文件文件頭長 */
static int merlrd; /* 文件記錄長 */
static int outfile(char *fname, unsigned size, int flag);
static int formerge(char *foname, char *finame, char *tmp, unsigned m);
static domerge(char *foname, char *tmp1, char *tmp2, int irun);
static void smerge(int *md, int m, char *buf[], int outf, char *outbuf, int size);
static int dopass(char *name1, char *name2, int irun);
/* 通用排序函數.
參 數:排序文件名;原文件名;原文件頭字節數;文件記錄長;用戶提供的比較函數.
返回值:成功 > 0;內存不夠.記錄超長返回 0;文件操作出錯 -1 */
int fmerge(char *foname, char *finame, int ftops, int lrd, mercmpf *cmpf)
{
char tmp1[68], tmp2[68];
int irun;
unsigned size;
if(lrd > MAXMEREC) return 0; /* 記錄超長 */
merlrd = lrd;
size = (SSIZE / lrd) * lrd; /* 排序緩衝區實際長 */
if((merbuf = (char *)malloc(size)) == NULL) return 0; /* 分配動態緩衝區 */
if(ftops && (filetop = (char *)malloc(ftops)) == NULL) return 0;
filetopchs = ftops;
mercmp = cmpf;
strcpy(tmp1, "&&&1"); /* 臨時文件名 */
strcpy(tmp2, "&&&2");
irun = formerge(foname, finame, tmp1, size); /* 分片排序 */
if(irun > 1) /* 如果排序片大於 1 */
irun = domerge(foname, tmp1, tmp2, irun); /* 合併排序片 */
free(merbuf);
if(filetopchs) free(filetop);
return irun;
}
/* 寫一排序片文件 */
static int outfile(char *fname, unsigned size, int flag)
{
int h, c;
if((h = open(fname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IWRITE)) == -1)
return -1;
if(flag && filetopchs) /* 如果是最終文件同時原文件有文件頭 */
write(h, filetop, filetopchs); /* 寫入文件頭內容 */
c = write(h, merbuf, size); /* 寫排序片到文件 */
close(h);
return c;
}
/* 分片排序 */
static int formerge(char *foname, char *finame, char *tmp, unsigned m)
{
unsigned irun, ret;
int f, flag = 0;
char tmpname[68];
if((f = open(finame, O_RDONLY | O_BINARY)) == -1) return -1;/* 打開原文件 */
if(filetopchs) /* 如有文件頭,保存其內容到緩衝區 */
read(f, filetop, filetopchs);
irun = 0;
do{
ret = read(f, merbuf, m); /* 讀一排序片到排序緩衝區 */
if(ret == 0 || ret == 0xffff) break; /* 原文件結束或出錯,退出 */
qsort(merbuf, ret / merlrd, merlrd, mercmp); /* 排序 */
if(ret == m || irun > 0) /* 如原文件長大於或等於一排序片長 */
sprintf(tmpname, "%s.%03d", tmp, irun); /* 採用臨時文件名 */
else{ /* 否則,直接用排序文件名 */
strcpy(tmpname, foname);
flag = 1; /* 最終文件標記 */
}
ret = outfile(tmpname, ret, flag); /* 寫排序片 */
irun ++;
}while(ret == m);
close(f);
if(ret == 0xffff) return ret; /* 出錯返回 -1 */
return irun; /* 返回排序片數 */
}
/* 分配每一合併趟不同臨時文件名;控制合併趟數 */
static domerge(char *foname, char *tmp1, char *tmp2, int irun)
{
char *p;
while(irun > 1){
if(irun <= MAXMERGE) strcpy(tmp2, foname);
irun = dopass(tmp1, tmp2, irun);
p = tmp1;
tmp1 = tmp2;
tmp2 = p;
}
return irun;
}
/* 執行合併趟,計算.分配每次合併所需文件數,緩衝區大小,控制每次合併的執行 */
static int dopass(char *name1, char *name2, int irun)
{
int fi, i, nrun, m, size;
char oname[68], inname[68], *p[MAXMERGE], *q;
int md[MAXMERGE], fo;
size = SSIZE / merlrd; /* 合併緩衝區容納記錄數 */
nrun = 0;
for(fi = 0; fi < irun; fi += MAXMERGE){
m = irun - fi; /* 每次合併實際排序片數 */
if(m > MAXMERGE) m = MAXMERGE;
for(i = 0; i < m; i ++) p[i] = merbuf + (i * merlrd); /* 分配讀緩衝區 */
if(irun <= MAXMERGE) strcpy(oname, name2); /* 最終合併形成排序文件 */
else sprintf(oname, "%s.%03d", name2, nrun);/* 中間合併採用臨時文件 */
if((fo = open(oname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, S_IWRITE)) == -1)
break; /* 打開寫文件 */
i = 0;
do{ /* 分別打開讀文件 */
sprintf(inname, "%s.%03d", name1, fi + i);
md[i] = open(inname, O_RDONLY | O_BINARY);
}while(md[i ++] != -1 && i < m);
if(i != m){
close(fo);
for(fi = 0; fi < i; fi ++) close(md[fi]);
break;
}
if(irun <= MAXMERGE && filetopchs) /* 最終合併寫文件頭(如有) */
write(fo, filetop, filetopchs);
q = merbuf + (m * merlrd); /* 分配寫緩衝區 */
smerge(md, m, p, fo, q, size - m); /* 合併 */
for(i = 0; i < m; i ++){ /* 刪除各排序片文件 */
close(md[i]);
sprintf(inname, "%s.%03d", name1, fi + i);
unlink(inname);
}
close(fo);
nrun ++;
}
if(nrun != (irun + MAXMERGE - 1) / MAXMERGE) return -1;
return nrun;
}
/* 執行實際排序片合併 */
static void smerge(int *md, int m, char *buf[], int outf, char *outbuf, int size)
{
int i, j, n = merlrd, w = merlrd * size;
char *s = buf[0], *p, *q = outbuf, *end = q + w;
for(i = 0; i < m; i ++) /* 從各片文件中讀第一條記錄 */
read(md[i], buf[i], n);
while(1){
if(n == merlrd){ /* 如各片文件均有記錄,各片記錄反向插入排序 */
for(i = 1; i < m; i ++){
for(p = buf[i], j = i - 1; j >= 0 && mercmp(p, buf[j]) > 0; j --)
buf[j + 1] = buf[j];
buf[j + 1] = p;
}
}
else m --; /* 一片文件內容結束 */
if(!m){ /* 如所有片文件結束,寫緩衝區殘餘記錄,退出 */
if(q != outbuf) write(outf, outbuf, q - outbuf);
break;
}
if(q == end){ /* 刷新一次寫緩衝區到文件 */
if(write(outf, outbuf, end - outbuf) != w) break;
q = outbuf;
}
i = m - 1;
j = (buf[i] - s) / merlrd;
memmove(q, buf[i], merlrd); /* 將各片記錄中值最小(大)者移入寫緩衝區 */
q += merlrd;
n = read(md[j], buf[i], merlrd); /* 從該片中讀下一記錄,繼續 */
}
}
可以看到,上面2個文件時間是1991年的,真是老古董了,如MERGE.H文件開頭就沒有什麼諸如#ifndef __MERGE_H......的代碼,我記得那個時候好像沒這個寫法的。函數裏面當初也作了很詳細的註釋,所以算法就不再講了(要講我還得先分析代碼,早忘記了 ^_^ )。
爲了示範該函數的使用方法,我還是用BCB6寫了一個簡單的演示程序,如果你想試一下老古董,不妨也寫一個?可以將MERGE.H文件中的排序緩衝區加大一些,可提高排序速度。
#include <stdio.h>
#include <stdlib.h>
#include "merge.h"
#pragma hdrstop
#define TOPSTRING "湖北省公安縣統計局 毛 澤 發"
#define TOP_SIZE 30
#define RECORD_SIZE 53
#define RECORD_COUNT 10000
//---------------------------------------------------------------------------
/* 爲了方便觀察,隨機生成了一個RECORD_COUNT行的文本文件 */
void MakeFile(char *filename)
{
int i, j;
long v[4];
FILE *f;
f = fopen(filename, "w");
fprintf(f, "%s ", TOPSTRING);
randomize();
for (i = 0; i < RECORD_COUNT; i ++)
{
for (j = 0; j < 4; j ++)
v[j] = random(0x7fffffff);
fprintf(f, "%12ld %12ld %12ld %12ld ", v[0], v[1], v[2], v[3]);
}
fclose(f);
}
int cdecl CompRecord(const void *ra, const void *rb)
{
int a[4], b[4];
int i, n;
sscanf((char*)ra, "%ld%ld%ld%ld", &a[0], &a[1], &a[2], &a[3]);
sscanf((char*)rb, "%ld%ld%ld%ld", &b[0], &b[1], &b[2], &b[3]);
for (n = 0, i = 0; i < 4 && n == 0; i ++)
n = a[i] - b[i];
return n;
}
#pragma argsused
int main(int argc, char* argv[])
{
printf("正在隨機制造一個文本文件d:/test.txt... ");
MakeFile("d:/test.txt");
printf("正在進行磁盤文件排序,排序文件d:/sort.text... ");
fmerge("d:/sort.txt", "d:/test.txt", TOP_SIZE, RECORD_SIZE, CompRecord);
printf("磁盤文件排序完畢! ");
system("pause");
return 0;
}
//---------------------------------------------------------------------------
如有錯誤,或者你有什麼好的建議請來信:[email protected]
發現代碼貼上去總是走樣,文件路徑‘//’也成了‘/’,‘/n’也沒了,MakeFile的2句寫記錄語句應該分別是,不然,測試會出問題:
fprintf(f, "%s/n", TOPSTRING);
和
fprintf(f, "%12ld %12ld %12ld %12ld/n", v[0], v[1], v[2], v[3]);