zoukankan      html  css  js  c++  java
  • 外部排序总结

    如果数据量过大,超过最大的内存容量,那么一次性将所有数据读入内存进行排序是不可行的。

    例如,一个文件每一行存了一个整数,该文件大小为10GB,而内存大小只有512M,如何对这10GB的数据进行排序呢?

    外部排序就是为了解决这种问题的。

    思路

        外部排序的思路是,将超大文件分成若干部分,每一部分是可以读入内存的,例如,将10GB的文件分为40份,则每一份就只有256M。将每一份读入内存,用已知的方法进行排序(快拍,堆排等方式),再写到一个文件中。这样,我们得到了40个已序的小文件。

        再采用归并(Merge)的方式,将40个文件合并为一个大文件,则这个大文件就是我们要的结果。

    难点

        归并的方式是一个难点。最直观地思路是两路归并。

        将40个文件编号1-40,1和2归并,3和4归并...39和40归并,生成了20个文件,再将这20个文件继续两路归并。

        从40个文件变成20个文件,相当于把所有10G的数据从磁盘读出,再写到磁盘上,从20个文件到10个文件,也相当于把10G的数据从磁盘读出,再写到磁盘上。这种磁盘IO操作一共要执行6次。(2^6=64>40)

        再来考虑K路归并。所谓K路归并,就是一次比较K路数据,选出最小的。例如当K=10,则是将40个文件分成1-10,11-20,21-30,31-40。对1-10,由于已序,故只要比较出这10个文件的第一个数,看哪个最小,哪个就写到新文件,再进行下一轮的比较。这样,只要2次磁盘IO就可以了。

        假设我们将文件分为m份,使用K路归并,则磁盘IO的次数就是log K底m。我们当然是希望这个值越小越好。但是是不是K越大就越好呢?我们来看看算法的时间复杂度。

        对于总共s个数据的K路归并,每次需比较K-1次,才能得出结果中的一个数据,s个数据就需要比较(s-1)(K-1)次

        对于总共n个数据,分为m份,使用K路归并,总共需要比较 (log K底m) * (n-1)(K-1)= (logm/logK)*(n-1)(K-1) = logm*(n-1)*(K-1)/logK,当K增大时,(K-1)/logK是增大的,也即时间复杂度是上升的。因此要么K不能太大,要么找出一个新的方法,使得每次不用比较K-1次才得出结果中的一个数据。我们选择后者,这种方法就是失败树。

    失败树

        

    圆形的是失败树的节点,方形的是K路数据中每路数据的最小值,图上K=4。

    失败树用数组ls[]存储,K路数据中没路数据的最小值用b[]存储。

    失败树的性质

    失败树里的每个节点中所存的值是失败者的下标,即b[]中的一个下标。例如,b[0]和b[1]比较的失败者(我们要求出最小值,因此大的数是失败者)是b[1],ls[2]中存的就是1。

    值得注意的是,我们并不是使用失败者去进行下一轮比较,而是用成功者进行下一轮比较。例如,ls[2]中虽然存的是1,但是参与下一轮比较的是b[0],同理,ls[3]中存的是下标3,但是参加下一轮比较的是b[2]。ls[1]中存放的是b[0]和b[2]比较所产生的失败者下标。获胜者的下标则存在ls[0]中,因此,b[ls[0]]是所有比较的获胜者,即这四个数中最小的。

    因此,我们将b[ls[0]]写入新的文件,然后b[ls[0]]读入本路的下一个数据。

    读入下一个数据之后,失败树的性质可能发生变化,我们要对数进行维护。

    维护的过程

    将新的b[ls[0]] 与失败树中的父节点进行比较。一直比到b[ls[1]],然后产生新的ls[0]

    具体到本处,就是b[0]与b[ls[2]]比较,失败者为b[ls[2]],因此ls[2]不变,再将b[0]与b[ls[1]]比较,失败者为b[0],因此ls[1]更新为0,ls[0]更新为之前的ls[1]。现在新的最小值就是b[ls[0]]=b[2]=7

    #include <iostream>  
      
    using namespace std;  
      
    #define  K  4 //表示4路归并  
    #define MIN INT_MIN;  
      
    int b[K+1] = {5,13,7,9};  
    int ls[K] = {0};//记录败者的下标  
      
    void Adjust(int s)  
    {  
        int t = (s+K)/2;//t=(s+k),得到与之相连ls数组的索引  
        while(t>0)
        {
            if(b[s] > b[ls[t]])//父亲节点  
            {    
                int temp = s; //s保存获胜者的下标   
                s = ls[t];    
                ls[t]=temp;    
            }
            t=t/2; //父节点    
        }  
        ls[0] = s;//将最小节点的索引存储在ls[0]  
    }  
      
    void CreateLoser()  
    {  
        b[K] = MIN;  
        int i;  
        for(i=0;i<K;i++)ls[i]=K;    
        for(i=K-1;i>=0;i--)Adjust(i); //加入一个基点,要进行调整   
      
      
    }  
    int main()  
    {  
        CreateLoser();  
        system("pause");  
        return 0;  
    }  

    建立失败树的过程用到了一个小技巧。首先假设各路的最小值都是MIN,MIN小于各路的最小值,失败数节点里的值可以为任意值,这里统一为K。然后从i=K-1 to o,一个一个读入b[i],并对失败树进行维护,当所有的b[i]都读入后,失败树也就建立好了。

    用失败树找最小值,时间复杂度为logK,不再是K-1,因此对于总共n个数据,分为m份,使用K路归并,时间复杂度为logm * (n-1)

  • 相关阅读:
    nginx限流方案的实现(三种方式)
    Pthreads并行编程之spin lock与mutex性能对比分析(转)
    C/C++中float和double的存储结构(转)
    list_entry(ptr, type, member)——知道结构体内某一成员变量地址,求结构体地址
    stderr和stdout详细解说(转)
    结构体内存分配理解
    C中的C文件与h文件辨析(转)
    访问vector元素方法的效率比较(转)
    linux c中select使用技巧——计时器(转)
    thread::id
  • 原文地址:https://www.cnblogs.com/johnsblog/p/3943352.html
Copyright © 2011-2022 走看看