对海量数据进行外排序的算法
需求:一种数据包中包括int, string, long, double 四种类型的数组,数组长度均为4096.(即4096行,每行有int, string, long, double四个数据)。对1000个随机产生的数据包,按int列进行排序。将int列最小的4096个数据及其对应的其他数据保存到一个新的数据包中。要求不修改1000个数据包。
分析:由于需要排序的数据量巨大,我们选择外排序算法来处理这道题。用.TXT文件来表示需求中的数据包,利用归并算法生成已排好序的顺串文件,然后利用败者树算法来进行排序。示例代码先以10个数据包为例。
具体函数代码:
#include<iostream>
#include<fstream>
#include<sstream>
#include <string>
#include<vector>
#include <map>
using namespace std;
#define MAX_INT 0x7fffffff
const int kMaxSize = 4096;
const int kMaxWay = 10;
int buffer[kMaxSize]; //假设内存只能放4096个整型.
int heap_size;
struct Run {
int *buffer; // 每个顺串的缓冲区
int length;// 缓冲区内元素个数
int idx;// 当前所读元素下标
};
int ls[kMaxWay]; //败者树,ls[0]是最小值的位置,其余是各败者的位置
Run *runs[kMaxWay];
将vector转换为arr数组
template<class elemType> //将vector转化为arr
elemType* vec2arr(vector<elemType> vec) {
elemType* arr = new elemType [vec.size()];
for (size_t i = 0; i < vec.size(); i++)
arr[i] = vec[i];
return arr;
}
template<class elemType>
快排函数
void quickSort(elemType a[], int begin, int end) { //对array进行快速排序
if (begin < end) {
int i = begin, j = end;
elemType key = a[i];
while (i < j) {
while (i<j&&a[j]>key)
j--; //j向前找比key小的数,注意随时检查i<j
if (i < j)
a[i++] = a[j];
while (i < j&&a[i] < key)
i++; //i向后找比key大的数
if (i < j)
a[j--] = a[i];
} //将key赋值给i与j相遇的地方
a[i] = key;
quickSort(a, begin, i - 1); //两侧递归
quickSort(a, i + 1, end);
}
}
构建以及调整败者树函数
void Adjust(Run **runs, int n, int s) { //首先根据s计算出对应的ls中哪一个下标
int t = (s + n) / 2;
int tmp;
while (t > 0) {
if (s == -1)
break;
if (ls[t] == -1 || runs[s]->buffer[runs[s]->idx] > runs[ls[t]]->buffer[runs[ls[t]]->idx]) {
tmp = s;
s = ls[t];
ls[t] = tmp;
}
t /= 2;
}
ls[0] = s;
}
void CreateLoserTree(Run **rus, int n) {
for (int i = 0; i < n; i++)
ls[i] = -1;
for (int i = n - 1; i >= 0; i--)
Adjust(runs, n, i);
归并排序函数
void MergeSort(Run** runs, int num_of_runs, const char* file_out) {
//初始化Run
if (num_of_runs > kMaxWay)
num_of_runs = kMaxWay;
int length_per_run = kMaxSize / num_of_runs;
for (int i = 0; i < num_of_runs; i++)
runs[i]->buffer = buffer + i*length_per_run;
ifstream in[kMaxWay];
char file_name[20];
for (int i = 0; i < num_of_runs; i++) {
sprintf(file_name, "%d.txt", i + 1);
in[i].open(file_name);
}
// 将顺串文件的数据读到缓冲区中
for (int i = 0; i < num_of_runs; i++) {
int j = 0;
while (in[i] >> runs[i]->buffer[j]) {
j++;
if (j == length_per_run)
break;
}
runs[i]->length = j;
runs[i]->idx = 0;
}
CreateLoserTree(runs, num_of_runs);
ofstream out(file_out);
int live_runs = num_of_runs;
while (live_runs > 0) {
out << runs[ls[0]]->buffer[runs[ls[0]]->idx++] << endl;
if (runs[ls[0]]->idx == runs[ls[0]]->length) {
int j = 0;
while (in[ls[0]] >> runs[ls[0]]->buffer[j]) {
j++;
if (j == length_per_run)
break;
}
runs[ls[0]]->length = j;
runs[ls[0]]->idx = 0;
}
if (runs[ls[0]]->length == 0) {
runs[ls[0]]->buffer[runs[ls[0]]->idx] = MAX_INT;
live_runs--;
}
Adjust(runs, num_of_runs, ls[0]);
}
}
具体实现代码:
生成10个排序好的顺串文件
map<int, string> mapData;
for(int i = 0;i < 10;i++) {
string bb,fn;
bb = to_string(static_cast<long long>(i)); //将int转化为string类型
fn = "file"+bb+".txt";
fstream infile;
infile.open(fn); //读取指定行的int数据,读入vector radius
if (!infile.is_open()) {
cout << "Unable to open myfile";
system("pause");
exit(1);
}
vector<string> vec;
string temp;
while (getline(infile, temp)) { //利用getline()读取每一行,并按照行为单位放入到vector
vec.push_back(temp);
}
vector <int> radius;
for (auto it = vec.begin(); it != vec.end(); it++) {
istringstream is(*it); //用每一行的数据初始化一个字符串输入流;
string s;
int pam = 0;
string temp_str;
while (is >> s) { //以空格为界,把istringstream中数据取出放入到依次s中
int r;
if (pam == 1) { //获取第二列的数据
r = atof(s.c_str()); //做数据类型转换,将string类型转换成int
radius.push_back(r);
}
if(pam > 1) { //将后三列形成字符串存入map
string kong = " ";
string res = kong + s.c_str();
temp_str += res;
}
if(pam == 4) {
mapData.insert(pair<int, string>(r, temp_str));
temp_str.clear();
}
pam++;
}
}infile.close();
int *arr = new int[4096];
arr = vec2arr(radius);
quickSort(arr,0,4096);
fstream file;
string bbl,fnl;
map<int, string>::iterator iter;
bbl = to_string(static_cast<long long>(i+1)); //将int转化为string类型
fnl = bbl+".txt";
file.open(fnl,ios::out|ios::trunc);
for(int j = 0;j < 4096;j++) {
iter = mapData.find(arr[j]); //找到索引对应的map
if(iter != mapData.end())
file<<arr[j]<<endl;
cout<<j<<endl;
}
file.close();
}
归并排序并将结果保存在sorted.txt中
for (int i = 0; i < 10; i++)
runs[i] = new Run();
MergeSort(runs, 10, "sorted.txt");
将最小的4096个元素取出并保存在result.txt中
ifstream filet;
filet.open("sorted.txt");
if(!filet)
cout<<"error"<<endl;
vector<int> radius;
string temp;
int tempt;
while (getline(filet, temp)) { //利用getline()读取每一行,并按照行为单位放入到vector
tempt = atof(temp.c_str());
radius.push_back(tempt);
}
filet.close();
int *arr = new int[4096];
arr = vec2arr(radius);
fstream dataFile; //定义文件操作对象
dataFile.open("result.txt",ios::out);
map<int, string>::iterator iter;
for(int i = 0;i < 4096;i++) { //向result中写入最小的4096个结果
iter = mapData.find(arr[i]);
dataFile<< i<<" "<<arr[i]<<iter->second<<endl;
}
dataFile.close();
总结:本题的难点:
1.循环打开10个txt文件的方法,将i与文件名一起转化为字符串,并打开。
2.败者树的建立以及调整
3.getline来以行为单位读取TXT中的i数据,并根据空格来读取需要排序的int型数据,将其余三列数据看做字符串
4.map 根据key,value来补全排序后,int列数据后面的三列数据。