美文网首页
基于losetree+kmerge+最佳归并树的外部排序(cpp

基于losetree+kmerge+最佳归并树的外部排序(cpp

作者: 小幸运Q | 来源:发表于2020-05-16 23:36 被阅读0次

步骤:

  1. 生成500MB左右的单个文件,一行一个数字
  2. 利用置换-选择算法,基于败者树进行分段按从小到大的顺序写入多个对应段号(从1开始)的文件
  3. 对多个有序段号的文件进行归并排序为一个有序文件

置换-选择排序的部分:

defination.h

#define MAXNUM 0xfffff
#define w 1073741824
// 1GB=1024*1024*1024=1073741824 w的值基本上不影响内存
int k=6;
// 500MB情况下 最高支持142路归并排序(暂时不清楚为什么,理论应该不止这个数)

readfile.h

int readline(FILE*fp){
       int buff;
       if (fscanf(fp, "%d", &buff) > 0){
              // 判断是否读到文件终止符了
              //printf("in:%d\n",buff);
              return buff;
       }
       else{
              return EOF;
       }
}

writefile.h

void writeline(FILE*&fp,int input){
    fprintf(fp,"%d\n",input);
    fflush(fp);
    // 必须fflush要不然缓冲区要溢出
    //printf("w:%d\n",input);
    counts++;
}

generatefile.h

// 生成随机数,一行一个数
int generate_num(){
    int a;
    a = rand();
    return a;
}
// 根据k值生成对应数量的文件,根据weight设置k个文件之和的大小,每个文件大小均匀
void generatefile(int k,int weight){
    int i,j;
    srand((unsigned)time(NULL));
    for(i=0;i<k;i++){
        FILE *fp = NULL;
        string s=".txt";
        s=to_string(i)+s;
        fp = fopen(s.c_str(), "w+");
        for(j=0;j<w/k;j++){
                fprintf(fp, "%d\n",generate_num()%1000+1);
        }
        fclose(fp);
    } 
}

losetree.h

class losetree{
    public:
        // 叶节点
        int *key;
        // 非叶节点
        typedef struct{
            int value;
            // 存放具体数值
            int scope;
            // 属于哪个块
        }leaf;
        leaf*l;
        // 二叉败者树的非叶节点,负责索引到对应叶节点,大小为k
        losetree(int k){
            l=new leaf[k];
            key=new int(k);
        }
        ~losetree(){
            delete[] l;
            delete[] key;
        }
        // 用于置换-选择排序(分段)的初始化,必须从后往前推,因为要保证0这个点最后被覆盖
        void build(FILE*fp){
            // 初始化叶节点
            int i;
            // memset(key,0,k*sizeof(int));
            // 读取k个数字填充败者树
            for(i=k-1;i>=0;i--){
                key[i]=0;
                l[i].scope=0;
                l[i].value=0;
            }            
            for(i=k-1;i>=0;i--){
                l[i].scope=1;
                insert(readline(fp),i);
            }
        }
        void insert(int input,int position){
            // position 在叶节点上的位置(为了方便,我们允许它的值大于k,
            // 从而实现leaf还有losertree两个不同类型数据的操作共享)
            // now从叶节点循着父节点(now/2)往上冒泡
            int now=k+position;
            // 比较胜者点之前的value与当前插入的值,从而决定是否对scope进行修改
            if(input>=l[key[0]].value){
                l[position].scope;
            }
            else{
                l[position].scope+=1;
            }
            // 用输入对胜者点进行替换(需要放在scope修改之后,因为key[0]在大部分操作中=position,要不然永远相等了)
            l[position].value=input;

            // position存放胜出值对应的leaf点位置,now则是树非叶节点的位置
            while(now!=0){
                // 考虑我(position)失败的情况,即父节点段号更小或者段号相等但是值更小,将胜者position用父节点指向的节点位置覆盖
                if(l[key[(now/2)]].scope<l[position].scope||(l[key[(now/2)]].scope==l[position].scope&&l[key[(now/2)]].value<l[position].value)){
                    int temp=key[now/2];
                    key[now/2]=position;
                    position=temp;
                }
                // 考虑我胜利的情况,啥都不用做
                else{

                }
                now/=2;
            }
            key[0]=position;
        }
        int get_block(FILE* fp,int pre_scope){
            FILE* out=NULL;
            if(l[key[0]].value==MAXNUM){
                return 0;
            }
            else{
                string s=".txt";
                string folder="./txt/";
                string filename=(folder+to_string(pre_scope)+s);
                //printf("%s\n",filename.c_str());
                out=fopen(filename.c_str(),"w+");
                // 每次都要先摘掉tree头部的min值然后再insert知道下个段的min值替代了tree的头
                writeline(out,l[key[0]].value);              
            }
            int i;
            while(1){
                // 不断从文件读取数据输入败者树,
                // 如果段号更新则输出前一个段号的所有数据
                int input=readline(fp);
                //printf("input:%d\n",input);
                // 发现读到初始文件句尾了
                if(input==EOF){           
                    // 填充MAXNUM把里面残余的数据挤出来         
                    // 先修改scope,反正读到MAXNUM就退出
                    l[key[0]].scope=MAXNUM;
                    insert(MAXNUM,key[0]);          
                }
                else{       
                    // 正常插入
                    insert(input,key[0]);                    
                }
                // scope更新说明已经刷新到下一个段了
                if(l[key[0]].scope>pre_scope){
                    if(l[key[0]].value==MAXNUM){
                        fclose(out);
                        // 返回0表示已经读完最后一个段了
                        return 0;
                    }
                    pre_scope=l[key[0]].scope;
                    break;
                }
                writeline(out,l[key[0]].value);
            }
            fclose(out);
            // 返回1表示还有别的段没读完
            return 1;
        }
};

depart.cpp

#include <stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string>
#include<vector>
using namespace std;

int counts=0;
// 统计写进去的数字个数,避免读取的初始文件数据在计算中缺失没有写进各个段文件中

// 把宏定义统一写到一块更方便
#include "defination.h"
#include "generatefile.h"
// 生成初始文件
#include "readfile.h"
#include "writefile.h"
// 败者树
#include "losetree.h"
/*
本程序负责生成一组(0,1000]的随机整数并写入txt文件中,并通过置换排序算法生成多个子文件,
生成的文件中数据已经按从小到大顺序排好序
*/
int main(){
    // generatefile(1,w); 生成1个含w个数字的文件
    FILE*fp=fopen("1GB.txt", "r+");
    // 4GB的没办法读直接返回-1
    int i,j;
    /*处理代码*/
    losetree ls(k);
    // 初始化
    ls.build(fp);
    /*初始化结束*/
    // 初始化段号
    int pre_scope=1;
    while(ls.get_block(fp,pre_scope++)){
        //printf("ok\n"); 少打点省内存
    }
    printf("\nfilenum:%d\n",pre_scope-1);
    printf("counts:%d",counts);
    fclose(fp);
}
  • 小结:
  1. 因为用的是系统自带的int(4B),最大能表示2^{31}=2147483648,k值在windows系统下最高设置为142(不清楚具体的原因理论上应该可以设置为内存大小),所以理论上最多能对2^{31}*(142*2)=609885356032个数字(略大于6098个亿)进行排序。如果是wb以二进制写入的话,用空格(占1B)隔开,可能要占用2840GB大小的空间。

  2. i5-8250U的CPU占用40%,内存占用极低基本上可以忽略不计。每次循环的FILE*建议不要重复使用,要不然可能无法释放之前读取的数据,造成内存溢出。

  3. 一次性打开/读/写的文件大小一般有限制,fopen的缓冲区一般最大2个GB。用fwrite函数写数据的时候,fwrite先将数据写到内存中的缓冲区内,等程序结束(理论上是fclose)后才会将数据由缓冲区写入文件。所以每次fwrite之后必须fflush。

  4. 读写文件如果是wb这种理论上更节约空间,w+是以文本方式打开文件,wb是二进制方式打开文件,以文本方式打开文件时,fwrite函数每碰到一个0xdata时,就在它的前面加入0x0D,要额外占用硬盘空间。

  • linux操作系统是32位操作系统文件大小只能是4G

k路平衡归并算法

defination.h

#define MAXNUM 0xfffff
#define w 1073741824
// 1GB=1024*1024*1024=1073741824 w的值基本上不影响内存
int k=6;
// 500MB情况下 最高支持142路归并排序(暂时不清楚为什么,理论应该不止这个数)

// 文件信息
typedef struct{
    int scope;
    int size;
}fileinfo;

// 文件大小堆排序
struct cmp{
    bool operator()(fileinfo&a, fileinfo&b){
        if(a.size == b.size)return a.size>b.size;
        return a.size>b.size;
    }
};

getFileName.h

// 遍历文件夹下的文件,获取文件的段名和文件大小的信息
void getAllFileNames(priority_queue<fileinfo,vector<fileinfo>,cmp>&filesinfo,const string& folder_path)
{
    _finddata_t file;
    long flag;
    string filename = folder_path + "\\*";//遍历制定文件夹内的jpg文件
    if ((flag = _findfirst(filename.c_str(), &file)) == -1)//目录内找不到文件
    {
        cout << "There is no such type file" << endl;
    }
    else
    {
        //通过前面的_findfirst找到第一个文件.和第二个文件..
        string name = folder_path + "\\" + file.name;//file.name存放的是遍历得到的文件名
        //依次寻找以后的文件
        _findnext(flag, &file);
        while (_findnext(flag, &file) == 0)
        {
            string name = string(folder_path + "\\" + string(file.name));
            //cout << file.name << endl;
            fileinfo f;
            f.scope=atoi(file.name);
            f.size=txt_file_size(name.c_str());
            filesinfo.push(f);
        }
    }
    _findclose(flag);
}

txtSize.h

// 获取文件大小
#include <sys/stat.h>
int txt_file_size(const char* filename)
{
    struct stat statbuf;
    stat(filename,&statbuf);
    int size=statbuf.st_size;
 
    return size;
}
int get_size_by_int(int t){
    string folder="./txt/";
    string name = string(folder + to_string(t));
    return txt_file_size(name.c_str());   
}

losetree.h

class losetree{
    public:
        // 叶节点
        int *key;
        // 非叶节点
        typedef struct{
            int value;
            // 存放具体数值
            int scope;
            // 属于哪个块
        }leaf;
        leaf*l;
        // 二叉败者树的非叶节点,负责索引到对应叶节点,大小为k
        losetree(int k){
            l=new leaf[k];
            key=new int(k);
            int i;
            for(i=k-1;i>=0;i--){
                key[i]=0;
                l[i].scope=0;
                l[i].value=0;
            }   
            //memset(&l,0,k*sizeof(leaf));
            //memset(&key,0,k*sizeof(int));
        }
        // 用于置换-选择排序(分段)的初始化,必须从后往前推,因为要保证0这个点最后被覆盖
        void build(FILE*fp){
            // 初始化叶节点
            int i;
            // memset(key,0,k*sizeof(int));
            // 读取k个数字填充败者树         
            for(i=k-1;i>=0;i--){
                l[i].scope=1;
                insert(readline(fp),i);
            }
        }
        void insert(int input,int position){
            // position 在叶节点上的位置(为了方便,我们允许它的值大于k,
            // 从而实现leaf还有losertree两个不同类型数据的操作共享)
            // now从叶节点循着父节点(now/2)往上冒泡
            int now=k+position;
            // 比较胜者点之前的value与当前插入的值,从而决定是否对scope进行修改
            if(input>=l[key[0]].value){
                l[position].scope;
            }
            else{
                l[position].scope+=1;
            }
            // 用输入对胜者点进行替换(需要放在scope修改之后,因为key[0]在大部分操作中=position,要不然永远相等了)
            l[position].value=input;

            // position存放胜出值对应的leaf点位置,now则是树非叶节点的位置
            while(now!=0){
                // 考虑我(position)失败的情况,即父节点段号更小或者段号相等但是值更小,将胜者position用父节点指向的节点位置覆盖
                if(l[key[(now/2)]].scope<l[position].scope||(l[key[(now/2)]].scope==l[position].scope&&l[key[(now/2)]].value<l[position].value)){
                    int temp=key[now/2];
                    key[now/2]=position;
                    position=temp;
                }
                // 考虑我胜利的情况,啥都不用做
                else{

                }
                now/=2;
            }
            key[0]=position;
        }
        int get_block(FILE* fp,int pre_scope){
            FILE* out=NULL;
            if(l[key[0]].value==MAXNUM){
                return 0;
            }
            else{
                string folder="./txt/";
                string filename=(folder+to_string(pre_scope));
                //printf("%s\n",filename.c_str());
                out=fopen(filename.c_str(),"w+");
                // 每次都要先摘掉tree头部的min值然后再insert知道下个段的min值替代了tree的头
                writeline(out,l[key[0]].value);              
            }
            int i;
            while(1){
                // 不断从文件读取数据输入败者树,
                // 如果段号更新则输出前一个段号的所有数据
                int input=readline(fp);
                //printf("input:%d\n",input);
                // 发现读到初始文件句尾了
                if(input==EOF){           
                    // 填充MAXNUM把里面残余的数据挤出来         
                    // 先修改scope,反正读到MAXNUM就退出
                    l[key[0]].scope=MAXNUM;
                    insert(MAXNUM,key[0]);          
                }
                else{       
                    // 正常插入
                    insert(input,key[0]);                    
                }
                // scope更新说明已经刷新到下一个段了
                if(l[key[0]].scope>pre_scope){
                    if(l[key[0]].value==MAXNUM){
                        fclose(out);
                        // 返回0表示已经读完最后一个段了
                        return 0;
                    }
                    pre_scope=l[key[0]].scope;
                    break;
                }
                writeline(out,l[key[0]].value);
            }
            fclose(out);
            // 返回1表示还有别的段没读完
            return 1;
        }
        // 简化版的基于最佳归并树BMT的k路归并排序 返回最终的resfile名字
        int kmerge(priority_queue<fileinfo,vector<fileinfo>,cmp>&q){
            // 每次轮回前检查以下待排序的文件数量filenum有没有>=k个,不足的就记录下来用MAXNUM灌水,
            // 写入的文件名=filenum+1
            // 如果发现输出了MAXNUM说明结束了,返回
            // 如果没有输出MAXNUM则接着读,如果有个别分支读不出来数据但是又没读到MAXNUM就用MAXNUM替代-1然后就不用管了
            // fclose()
            int i,j;
            int resfile=-1;
            int end=q.size();
            string folder="./txt/";
            for(i=1;;i++){
                //int size=q.size();
                if(q.size()==1){
                    break;
                }
                FILE*out=fopen((folder+to_string(end+i)).c_str(),"w+");
                //FILE*leafs=(FILE*)malloc(k*sizeof(FILE*));
                FILE*leafs[k];
                int blank=0;
                if(q.size()>=k){
                    j=k;
                    while(j){
                        leafs[j-1]=fopen((folder+to_string(q.top().scope)).c_str(),"r+");
                        j--;
                        q.pop();
                    }        
                    // build
                    for(j=k-1;j>=0;j--){
                        l[j].scope=1;
                        insert(readline(leafs[j]),j);
                    }
                    // circle read
                    while(l[key[0]].value!=MAXNUM){
                        writeline(out,l[key[0]].value);
                        int newinput=readline(leafs[key[0]]);
                        if(newinput==EOF){
                            // 如果其中一个段读到底了就用MAXNUM封一下
                            insert(MAXNUM,key[0]);
                        }
                        else{
                            insert(newinput,key[0]);
                        }
                    }       
                    for(j=0;j<k;j++){
                        fclose(leafs[j]);
                    }
                }
                else{
                    j=q.size();
                    blank=k-q.size();
                    while(j){
                        leafs[j-1]=fopen((folder+to_string(q.top().scope)).c_str(),"r+");
                        j--;    
                        q.pop();                    
                    }
                    //build
                    for(j=k-1;j>=k-blank;j--){
                        l[j].scope=1;
                        insert(MAXNUM,j);                        
                    }
                    for(j=k-blank-1;j>=0;j--){
                        l[j].scope=1;
                        insert(readline(leafs[j]),j);
                    }
                    // circle read
                    while(l[key[0]].value!=MAXNUM){
                        writeline(out,l[key[0]].value);
                        int newinput=readline(leafs[key[0]]);
                        if(newinput==EOF){
                            // 如果其中一个段读到底了就用MAXNUM封一下
                            insert(MAXNUM,key[0]);
                        }
                        else{
                            insert(newinput,key[0]);
                        }
                    }
                    for(j=0;j<q.size();j++){
                        fclose(leafs[j]);
                    }
                }
                //delete[]leafs;
                fclose(out);                
                // out 写完记得加到priority_queue的队列里面去
                fileinfo f;
                f.scope=end+i;
                f.size=get_size_by_int(f.scope);
                q.push(f);
                // 每次losetree用完都要重新置为0
                for(j=k-1;j>=0;j--){
                    key[j]=0;
                    l[j].scope=0;
                    // l[j].value=0;
                }                   
            }
            return q.top().scope;
        }
};

相关文章

  • 基于losetree+kmerge+最佳归并树的外部排序(cpp

    步骤: 生成500MB左右的单个文件,一行一个数字 利用置换-选择算法,基于败者树进行分段按从小到大的顺序写入多个...

  • java归并排序

    归并排序什么是归并排序:图解归并排序归并排序有两种实现方式,一是基于递归,而是基于迭代1)基于递归的归并排序: 基...

  • 数据结构与算法--归并排序

    数据结构与算法--归并排序 归并排序 归并排序基于一种称为“归并”的简单操作。比如考试可能会分年级排名和班级排名,...

  • 外部排序-归并排序

    1.外部排序定义 指的是大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存,需要在内存和...

  • 并归算法,深入学习递归实现,(二叉树排序。)

    归并排序,递归深入学习(二叉树排序,了解二叉树!) 分析归并排序之前,先回顾一下冒泡排序。 最开始梳理的冒泡排序的...

  • 排序算法(五)归并排序

    排序算法(五 )归并排序 1.算法思路  归并排序(Merge-Sort)是一种基于二叉堆及分而治之思想的排序算法...

  • 外部排序

    http://data.biancheng.net/view/77.html 多路归并排序: 对于外部排序算法来说...

  • PHP常用算法

    基于选择的排序算法 常见的基于选择的排序算法有:冒泡排序、插入排序、选择排序、归并排序和快速排序,我们在选在排序算...

  • 快速排序和归并排序(golang实现)

    快排和归并是使用比较广泛的两种排序算法,他们的性能都可以达到O(nlgn),这也是基于排序的算法能达到的最佳的性能...

  • 排序—归并排序

    归并排序 思路: 归并排序属于外部排序,因为在合并时,需要一个额外的数组。将待排序数据分成两部分,继续将两个子部分...

网友评论

      本文标题:基于losetree+kmerge+最佳归并树的外部排序(cpp

      本文链接:https://www.haomeiwen.com/subject/wlfjghtx.html