大数据取前x个最大值方法:
小顶堆,这是一种完全二叉树的顺序储存结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择元素。也可使用大顶堆来获取最小值。
void HeapAdjust(int data[],int nStart, int nLen)
{
int pos;
int Temp;
while ((2*nStart+1) < nLen)
{
//比较左子树和右子树,记录小的Index
if ( (2*nStart+2) > nLen)
{
pos = 2*nStart+1; //无右子树
}
else
{
pos = data[2*nStart+1] > data[2*nStart+2] ? 2*nStart+2 : 2*nStart+1;
}
//change data
if (data[nStart] > data[pos])
{
//交换nStart与pos的数据
Temp = data[nStart];
data[nStart] = data[pos];
data[pos] = Temp;
//堆被破坏,需要重新调整
nStart = pos;
}
else
{
//比较左右孩子均小则堆未破坏,不再需要调整
break;
}
}
}
void HeapSort(int data[],int nLen)
{
int i;
int nTemp;
//建立堆
for (i = nLen/2-1; i >= 0; i--)
{
HeapAdjust(data, i, nLen);
}
for (i = nLen-1; i > 0; i--)
{
//交换堆顶元素和最后一个元素
nTemp = data[0];
data[0] = data[i];
data[i] = nTemp;
HeapAdjust(data, 0, i);
}
}
大数据排序方法:
既然是大量的数据,不可能一次将所有数据读入,只能分段读取到内存再处理。
方法一:bit位排序。
其思想是创建一个bit数组,数组大小为实际所排序数据的大小范围,用一个bit 位来标记某个元素对应的Value ,而Key 即是该元素,然后用01表示其值存在与否。将文件遍历一遍,将相应bit位的值置1,然后将bit数组遍历一遍,打印出值为1的对应代表值(实际数据范围的值)即可完成排序。
优点:1 byte = 8 bit , 内存使用非常小。
缺点:01只能代表存在与否,如果实际需要是有重复数据则不能满足需求。故,可以用int数组0表示没有,1234等代表存在个数,同样的思想可解决重复问题,但内存使用会更大。
char str[255];
long int fir;
long int sed;
int flag[Length];
memset(&flag, 0, sizeof(flag));
for (int j1 = 0; j1 < 100; j1++) {
temp[j1].resize(Length);
}
//读取的文件
FILE *fp=fopen([path UTF8String], "r+");
while(feof(fp)==0)
{
std::fgets(str, 255, fp);
fir = atol(str)/Length;
sed = ( atol(str) - fir*Length );
temp[fir][sed] = temp[fir][sed] + 1;
}
long int readdone = gettime();
printf("read done!%ld\n",readdone - start);
fclose(fp);
//输出文件
FILE *wt = fopen("/Users/mac/Desktop/sorttest/data-bitsoet1.txt", "w+");
for (int i2 = 0; i2 < 100; i2++) {
for (int j2 = 0; j2 < Length; j2++) {
if (temp[i2][j2] > 0) {
for (int x2 = 0; x2 < temp[i2][j2]; x2++) {
fprintf(wt, "%d\n",i2 * Length + j2);
}
}
}
}
方法二:外部排序后归并排序。
外部排序实质上是一个分段排序的方法,将大数据分段,每一段排序后另存到一个文件去。待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,以达到排序整个文件的目的。
-(void)Filesplit:(NSString *)path
{
char str[255];
vector<long int>data;
FILE *fp=fopen([path UTF8String], "r+");
for (int i=Length;feof(fp)==0;i+=Length) {
do{
fgets(str, 255, fp);
data.push_back(atol(str));
// Index.push_back(ftell(fp));
}while (feof(fp)==0&&data.size()<Length);
sort(data.begin(), data.end());
saveFile(data,i,0);
data.clear();
}
}
void saveFile(std::vector<long int>a,int i,int mode){
char filepath[255];
sprintf(filepath, "data-%d-%d.txt",i,mode);
FILE *fp=fopen(filepath, "w+");
for (int j=0; j<a.size(); j++) {
fprintf(fp, "%ld\n", a[j]);
}
fclose(fp);
}
归并排序是将两个有序数组组合成一个有序数组,即是将外部排序产生的文件进行多次归并后集合成一个文件。
//将有序序列a[low..mid]和a[mid+1..high]归并到a[low..high]。
void Merge(int a[], int low, int mid, int high)
{
// 归并到b[]
int i = low;
int j = mid+1;
int k = 0;
int b[high-low+1];
while (i <= mid && j <= high)
{
if (a[i] <= a[j]) { b[k++] = a[i++]; }
else { b[k++] = a[j++]; }
}
// 归并剩余元素
while (i <= mid){
b[k++] = a[i++];
}
while (j <= high) {
b[k++] = a[j++];
}
// 从b[]复制回a[]
for(i = 0; i <= high-low; ++i){
a[low+i] = b[i];
}
}
问题在于在最后一次归并的时候,其数组大小是等于最开始的大数据文件的大小,这对内存消耗过大。
所以可用一个固定大小x的数组,从两个有序数组中各读x/2到数组中,对比排序后写入文件一部分(如x/2),然后再各读x/4到数组补充缺少的,如此循环直至读完两个文件。可根据实际需求改变读取的大小、写入的大小。
多路归并,一次将n(n>2)个文件归并成一个大文件t。思想是建立一个数组a[n],用n个指针指向各个文件内最小的数,对比取出a[n]内最小的值,然后这一个值写入大文件t,相应的指针再指向对应文件的下一个值,如此循环取值对比。
这种方法对比次数较多,文件操作也会因数据变大而变多,不如第一种方法好。
未完待续