1最基本的,一个数据流(文件),求top k biggest.
solution:维护大小为K的最小堆,和堆顶比,大于堆顶的加入堆,堆顶相当于准入门槛。如果size 超过K,移除堆顶。
vector<int> topK(string fileName, int k) {
ifstream is(fileName);
priority_queue<int, vector<int>, greater<int>> pq;
for (int x; is >> x;) {
pq.push(x);
if (pq.size() > k) pq.pop();
}
vector<int> ans;
for (; !pq.empty(); pq.pop()) {
ans.push_back(pq.top());
}
reverse(ans.begin(), ans.end());
return ans;
}
2一般的是Top K frequent,需要先统计每个数据的个数(频率)。主要看全部unique的数据size(乘上计数值size)是否可以装进内存,可以的话,直接symbol table计数,然后再维护key为频率,大小为K的最小堆。
如果全部unique的数据内存装不下,通过值域划分进行Data Partition,一个区间用一个文件存,这实际就是一个shuffle过程。读一遍原数据,生成若干区间文件,然后每个区间可以独立统计频率,进而用堆求TOP k,还是用一个大小为k的小顶堆。
void shuffle(string fileName, int bucketNum) {
ifstream is(fileName);
for (int x; is >> x;) {
ofstream os(fileName + to_string(abs(x % bucketNum)), ios::app);
os << x << endl;
}
}
vector<int> topKFrequent(string fileName, int k) {
ifstream is(fileName);
int bucketNum = 10;
shuffle(fileName, bucketNum);
typedef pair<int, int> P;
priority_queue<P, vector<P>, greater<P>> pq;
for (int i = 0; i < bucketNum; ++i) {
map<int, int> count;
ifstream is(fileName + to_string(i));
for (int x; is >> x; ++count[x]);
for (auto &entry : count) {
pq.push(make_pair(entry.second, entry.first));
if (pq.size() > k) pq.pop();
}
}
vector<int> ans;
for (; !pq.empty(); pq.pop()) { ans.push_back(pq.top().second); }
reverse(ans.begin(), ans.end());
return ans;
}
3 两个大文件求intersection,
如果一个文件可以装进内存,则可以生成一个unique 的set, 另一个文件扫描一遍,set中有的就是交集的元素。但一般是放不下的。也是先shuffle,data partition, 值域划分。字符串数据用hash code作为值。相同的值一定在同一个区间,然后在每个值域集合上求交集
void shuffle(string fileName, int bucketNum) {
ifstream is(fileName);
for (int x; is >> x;) {
ofstream os(fileName + to_string(abs(x % bucketNum)), ios::app);
os << x << endl;
}
}
set<int> intersect(string file1, string file2) {
int bucketNum = 10;
shuffle(file1, bucketNum);
shuffle(file2, bucketNum);
set<int> ans;
for (int i = 0; i < bucketNum; ++i) {
set<int> s;
ifstream is(file1 + to_string(i));
for (int x; is >> x;) {
s.insert(x);
}
is = ifstream(file2 + to_string(i));
for (int x; is >> x;) {
if (s.find(x) != s.end()) ans.insert(x);
}
}
return ans;
}
4,大文件,找一个不在文件中的数,
1)位图, int类型的数可以用4G * 1bit = 512M byte大小的位图
2)如果内存连位图都放不下,还是data partition, 值域划分,在每个值域子集上用位图做
int findNotIn(string fileName) {
vector<char> bitMap1(1024 * 1024 * 256), bitMap2(1024 * 1024 * 256 + 1);
ifstream is(fileName);
for (int x; is >> x;) {
if (x >= 0)
bitMap1[x / 8] |= 1 << (x % 8);
else {
unsigned int y = -x;
bitMap2[y / 8] |= 1 << (y % 8);
}
}
for (int i = 0; i < bitMap1.size(); ++i) {
for (int j = 0; j < 8; ++j) {
if (((bitMap1[i] >> j) & 1) == 0) return i * 8 + j;
}
}
for (int i = 0; i < bitMap2.size(); ++i) {
for (int j = 0; j < 8; ++j) {
if (((bitMap1[i] >> j) & 1) == 0) return i * 8 + j;
if (i == bitMap2.size() - 1 && j > 0) break;
}
}
}
5大文件中查询所有的只出现一次的数
1)2位的位图,00代表不存在,01出现一次,10出现多次,11不用
2)整个值域的位图放不下的话,进行值域划分,每个值域子集上做。
vector<int> findOccurOnce(string fileName) {
vector<char> bitMap(1024 * 1024 * 1024); //2位的位图,00 not exit, 01 once, 10 more than once, 11 not used
ifstream is(fileName);
for (int x; is >> x;) {
int v = (bitMap[x / 4] >> ((x % 4) * 2)) & 3;
if (v == 0)
bitMap[x / 4] |= 1 << (x % 4) * 2; //00 -> 01
else if (v == 1)
bitMap[x / 4] ^= 3 << (x % 4) * 2; // 01 -> 10
}
vector<int> ans;
for (int i = 0; i < bitMap.size(); ++i) {
for (int j = 0; j < 4; ++j) {
if (((bitMap[i] >> j * 2) & 3) == 1) ans.push_back(i * 4 + j);
}
}
return ans;
}
6大文件求中位数
也是值域划分(桶划分),遍历一遍文件得到每个partition/bucket里的数的个数,以及总个数n。然后从第一个区间开始累加个数,当加进某个区间总个数超过n/2,则中位数就在这个区间。再扫一遍文件,这次对那个区间的每个数计数,从这个区间的第一个数开始继续累加,使得总数超过n/2的那个数就是中位数。
int medium(string fileName) {
ifstream is(fileName);
int buckeSize = 1000, N = 0;
vector<int> count(INT_MAX / buckeSize);
for (int x; is >> x;) {
count[x / buckeSize] += 1;
++N;
}
int i = 0, num = 0;
for (;; ++i) {
if (num + count[i] >= (N + 1) / 2) break;
num += count[i];
}
is = ifstream(fileName);
vector<int> count2(buckeSize);
for (int x; is >> x;) {
if (x / buckeSize == i) count2[x % buckeSize] += 1;
}
for (int j = 0; j < buckeSize; ++j) {
if (num + count2[j] >= (N + 1) / 2) {
return i * buckeSize + j;
}
num += count2[j];
}
}
7 流维护中位数
维护一个最大堆代表左半部分,一个最小堆代表右半部份。来一个数,比最大堆堆顶小,放入左堆,否则放入右堆,然后确保两堆大小相差不超过1,
8 多台机器求中位数
slave 实现 int partition(int pivot):给定一个轴进行partition, 并返回比轴小的元素个数
master首先询问各台机器上有多少数,得出总共有多少数N,中位数是第N/2个
master 随机从slave 选择一个数作为轴,让调用各台slave机器上的partition,并累加得到比pivot小的元素总数K,如果K == N/2, 则pivot就是所求。如果K < N/2, 则令每台机器舍弃前半部分,否则舍弃后半部分。然后重复这个过程。
9 外排序
1)生成归并段(待归并的段)
输入原文件流,输出一个个有序小文件,小文件大小主要依赖内存大小,可以进行内排序。
2)merge
a)可以2路归并或多路归并,生成一系列更大的一点的归并段,重复这个过程,直到只有一个归并段。这种方法会产生很多中间文件。
b) 也可以进行全路归并,也就是从初始归并段集合直接生成目标文件,使用一个小顶堆的priority_queue做“门”,门的输入是多路流,即所有的归并段,输出端是一个流,即目标文件。每次把堆顶元素写入输出流。这里元素除了包含原始数据,还要包含管道信息,即是从哪个输入流来的,并且从那个流输入一个数。这种方法的问题是,系统是否允许打开那么多文件,这取决于原始归并段数。