大数据问题汇总

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做“门”,门的输入是多路流,即所有的归并段,输出端是一个流,即目标文件。每次把堆顶元素写入输出流。这里元素除了包含原始数据,还要包含管道信息,即是从哪个输入流来的,并且从那个流输入一个数。这种方法的问题是,系统是否允许打开那么多文件,这取决于原始归并段数。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值