/
外部归并排序 - 分治.cpp
273 lines (241 loc) · 5.77 KB
/
外部归并排序 - 分治.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
// 对 2 亿个数字进行排序, 约 10 G 的文件, 每个数字 int 能表示
// 算法流程
// 将 10 G 的文件散列到 300 个文件中, 每个文件大约 35 MB
// 对 35 MB 的小文件内部排序, 或者分发到多台计算机中, 并行处理 MapReduce
// 最后使用最小堆, 进行 300 路归并排序, 合成大文件
// 再写一个算法判断 2 亿个数字是否有序
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <io.h>
#include <queue>
#define FILE_NUM 300 // 哈希文件数
#define HASH(a) (a % FILE_NUM)
int num = 6000000; // 2 亿个数字, 手动改
char path[20] = "c:\\data.dat"; // 待排文件
char result[20] = "c:\\result.dat"; // 排序后文件
char tmpdir[100] = "c:\\hashfile"; // 临时目录
// 随机生成 2 亿个数字
int write_file(void)
{
FILE *out = NULL;
int i;
printf("\n正在生成 %d 个数字...\n\n", num);
out = fopen(path, "wt");
if (out == NULL) return 0;
unsigned int s, e;
e = s = clock();
for (i=0; i<num; i++)
{
e = clock();
if (e - s > 1000) // 计算进度
{
printf("\r处理进度 %0.2f %%\t", (i * 100.0) / num);
s = e;
}
fprintf(out, "%d\n",
(rand() % 31623) * (rand() % 31623));
}
fclose(out);
return 1;
}
// 对 2 亿个数字进行哈希, 分散到子文件中
// 入口参数: path, tmpdir
int map(void)
{
FILE *in = NULL;
FILE *tmp[FILE_NUM + 5];
char hashfile[512]; // 哈希文件地址
int data, add;
int i;
printf("\r正在哈希 %s\n\n", path);
in = fopen(path, "rt");
if (in == NULL) return 0;
for (i=0; i<FILE_NUM; i++) tmp[i] = NULL;
// 开始哈希, 核心代码要尽可能的加速
unsigned int s, e;
e = s = clock();
i = 0;
while (fscanf(in, "%d", &data) != EOF)
{
add = HASH(data);
if (tmp[add] == NULL)
{
sprintf(hashfile, "%s\\hash_%d.~tmp", tmpdir, add);
tmp[add] = fopen(hashfile, "a");
}
fprintf(tmp[add], "%d\n", data);
i++;
e = clock(); // 计算进度
if (e - s > 1000)
{
printf("\r处理进度 %0.2f %%\t", (i * 100.0) / num);
s = e;
}
}
for (i=0; i<FILE_NUM; i++)
if (tmp[i]) fclose(tmp[i]);
fclose(in);
return 1;
}
// 对 300 个文件逐个排序, 采用堆排序 STL 的优先队列
void calc(void)
{
int fileexist(char *path); // 判断文件存在
std::priority_queue<int> q; // 堆排序
char hashfile[512];
FILE *fp = NULL;
int i, data;
// 逐个处理 300 个文件, 或者将这些文件发送到其它计算机中并行处理
for (i=0; i<FILE_NUM; i++)
{
sprintf(hashfile, "%s\\hash_%d.~tmp", tmpdir, i);
if (fileexist(hashfile))
{
printf("\r正在排序 hash_%d.~tmp\t", i);
// 小文件从磁盘加入内存中
fp = fopen(hashfile, "rt");
while (fscanf(fp, "%d", &data) != EOF)
{
q.push(data);
// 优先队列默认是大顶堆, 即降序排序
// 要升序需要重载 () 运算符
}
fclose(fp);
// 排序后再从内存写回磁盘
fp = fopen(hashfile, "wt"); // 覆盖模式写
while (!q.empty())
{
fprintf(fp, "%d\n", q.top());
q.pop();
}
fclose(fp);
}
}
}
typedef struct node // 队列结点
{
int data;
int id; // 哈希文件的编号
bool operator < (const node &a) const
{ return data < a.data; }
}node;
// 将 300 个有序文件合并成一个文件, K 路归并排序
int reduce(void)
{
int fileexist(char *path);
std::priority_queue<node> q; // 堆排序
FILE *file[FILE_NUM + 5];
FILE *out = NULL;
char hashfile[512];
node tmp, p;
int i, count = 0;
printf("\r正在合并 %s\n\n", result);
out = fopen(result, "wt");
if (out == NULL) return 0;
for (i=0; i<FILE_NUM; i++) file[i] = NULL;
for (i=0; i<FILE_NUM; i++) // 打开全部哈希文件
{
sprintf(hashfile, "%s\\hash_%d.~tmp", tmpdir, i);
if (fileexist(hashfile))
{
file[i] = fopen(hashfile, "rt");
fscanf(file[i], "%d", &tmp.data);
tmp.id = i;
q.push(tmp); // 初始化队列
count++; // 计数器
printf("\r入队进度 %0.2f %%\t", (count * 100.0) / FILE_NUM);
}
}
unsigned int s, e;
e = s = clock();
while (!q.empty()) // 开始 K 路归并
{
tmp = q.top();
q.pop();
// 将堆顶的元素写回磁盘, 再从磁盘中拿一个到内存
fprintf(out, "%d\n", tmp.data);
if (fscanf(file[tmp.id], "%d", &p.data) != EOF)
{
p.id = tmp.id;
q.push(p);
count++;
}
e = clock(); // 计算进度
if (e - s > 1000)
{
printf("\r处理进度 %0.2f %%\t", (count * 100.0) / num);
s = e;
}
}
for (i=0; i<FILE_NUM; i++)
if (file[i]) fclose(file[i]);
fclose(out);
return 1;
}
int check(void) // 检查是否降序排序
{
FILE *in = NULL;
int max = 0x7FFFFFFF;
int data;
int count = 0;
printf("\r正在检查文件正确性...\n\n");
in = fopen(result, "rt");
if (in == NULL) return 0;
unsigned int s, e;
e = s = clock();
while (fscanf(in, "%d", &data) != EOF)
{
if (data <= max) max = data;
else
{
fclose(in);
return 0;
}
count++;
e = clock(); // 计算进度
if (e - s > 1000)
{
printf("\r处理进度 %0.2f %%\t", (count * 100.0) / num);
s = e;
}
}
fclose(in);
return 1;
}
// 判断文件存在
int fileexist(char *path)
{
FILE *fp = NULL;
fp = fopen(path, "rt");
if (fp)
{
fclose(fp);
return 1;
}
else return 0;
}
int main(void)
{
char cmd_del[200]; // 删除目录
char cmd_att[200]; // 设置隐藏
char cmd_mkdir[200]; // 建立目录
// 初始化 cmd 命令, 建立工作目录
sprintf(cmd_del, "rmdir /s /q %s", tmpdir);
sprintf(cmd_att, "attrib +h %s", tmpdir);
sprintf(cmd_mkdir, "mkdir %s", tmpdir);
if (access(path, 0) == 0) system(cmd_del);
system(cmd_mkdir); // 建立工作目录
system(cmd_att); // 隐藏目录
// 随机生成 2 亿个数字
if (!write_file()) return 0;
map(); // 对 2 亿个数字进行哈希, 即 Map
calc(); // 对 300 个文件逐个排序
reduce(); // 最后将 300 个有序文件合并成一个文件, 即 reduce
if (check()) printf("\r排序正确!\t\t\t\n\n");
else printf("\r排序错误!\t\t\t\n\n");
system(cmd_del); // 删除哈希文件
remove(path); // 删除 2 亿数字文件
remove(result); // 删除排序后的文件
return 0;
}