外部排序

外部排序是指待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。

外部排序的时间分析

一般情况下,外部排序所需总的时间 =
内部排序(产生初始归并段)所需的时间(m×tIS
+外存信息读写的时间(d×tIO
+内部归并所需的时间(s×utmg

tIS:得到一个初始归并段进行内部排序所需时间的均值
m:经过内部排序之后得到的初始归并段的个数
tIO:进行一次外存读/写时间的均值
d:总的读/写次数
utmg:对u个记录进行内部归并排序所需时间
s:为归并的趟数

多路归并排序

外部排序最常用的算法是归并排序,而多路归并排序的流程与思想也比较简单,在此不再赘言。
但值得注意的是其重要的子算法

  1. 置换-选择排序
    n个记录分为m个规模较小的记录段,如果被划分的每个小记录段规模不够小,仍然无法完全读入内存,则无法用内排序得到初始归并段,因此需要一种适用于初始归并段规模的,高效的且不受内存空间限制的排序算法,即置换-选择排序。
  2. 最佳归并树
    将当前的m组(每组含有k个有序记录段)记录归并为m个有序记录段的过程称为一趟归并,可见每个记录在每趟归并中需要两次I/O操作(读写操作各一次)。读写操作是非常耗时的,可见减少归并次数可以提高效率。为了使得归并次数最少,需用到最佳归并树。
  3. 败者树
    归并排序算法中有一个多次出现的步骤是从当前k个值中用某种算法选出最值,可见提高选最值的效率也是整个归并排序算法效率提高的关键。这就需要用到败者树。

置换-选择排序

/**
*置换-选择排序的简单描述
*/
typedef struct{
    RcdType    rec;     //记录
    KeyType    key;    //从记录中抽取的关键字
    int    rnum;    //所属归并段的段号
}RcdNode,  WorkArea[w];    //内存工作区,容量为w

void  Replace_Selection(LoserTree  &ls,WorkArea  &wa,  FILE  *fi,  FILE  *fo){
    //  在败者树ls和内存工作区wa上用置换-选择排序求初始归并段,fi为输入文件
    //(只读文件)指针,fo为输出文件(只写文件)指针,两个文件均已打开
    Construct_Loser(ls,wa);    //初建败者树
    rc = rmax = 1;      //rc指示当前生产的初始归并段的段号
                             //rmax指示wa中关键字所属初始归并段的最大段号
    while(rc <= rmax){    //"rc = rmax+1" 标志输入文件的置换-选择排序已完成
      get_run(ls,wa);    //求得一个初始归并段
      fwrite(&RUNEND_SYMBOL,sizeof(struct RcdType),1,fo);  //将段结束标志输入输出文件
      rc = wa[ls[0]].rnum;    //设置下一段的段号
    }
}

void get_run (LoserTree &ls,WorkArea &wa){
    //求得一个初始归并段,fi为输入文件指针,fo为输出文件指针
    while(wa[ls[0]].rnum == rc){    //选得的MINIMAX记录属当前段时
        q = ls[0];    //q知识MINIMAX记录在wa中的位置
        minimax = wa[q].key;
    }
    fwrite(&wa[q].rec,sizeof(RcdType),1,fo);    //将刚选好的MINIMAX记录写入输出文件
    if(feof(fi))    {wa[q].rnum = rmax +1; wa[q].key = MAXKEY;}
    //输入文件结束,虚设记录(属"rmax+1"段)
    //输入文件非空时
    else{
        fread(&wa[q].rec,sizeof(RcdType),1,fi);    //从输入文件读入下一记录
        wa[q].key = wa[q].rec.key;      //提取关键字
        if(wa[q].key <minimax){     //新读入的记录属下一段
            rmax = rc +1;
            wa[q].rnum = rmax;
        }
        else
            wa[q].rnum = rc;    //新读入的记录属当前段
        }
          Select_MiniMax(ls,wa,q);    选择新的MINIMAX记录
    }
    
void Select_MiniMax(LoserTree &ls,WorkArea wa,int q){
        //从wa[q]起到败者树的根比较选择MINIMAX记录,并由q指示它所在的归并段
        for(t = (w+q)/2,p = ls[t];t>0; t= t/2,p = ls[t]){
            if(wa[p].rnum <wa[q].rnum || wa[p].rnum == wa[q].rnum && wa[p].key <wa[q].key){
                q <-->ls[t]; //q指示新的胜利者
            }
        }
        ls[0] = q;
    }
}

void Construct_Loser(LoserTree &ls,WorkArea &wa){
    //输入w个记录到内存工作区wa,建得败者树ls,选出关键字最小的记录并由s指示
    //其在wa中的位置
    for(i = 0 ; i <w; ++i){
        wa[i].rnum = wa[i].key = ls[i] = 0;    //工作区初始化
    }
    for(i = w-1;i>=0; -- i){
        fread(&wa[i].rec,sizeof(RcdType),1,fi);    //输入一个记录
        wa[i].key = wa[i].rec.key;    // 提取关键字
        wa[i].rnum = 1;    //其段号为"1"
        Select_MiniMax(ls,wa,i);    //调整败者
    }
}



扫雪机模型:若不计输入,输出时间,则对n个记录的文件而言,生产所有初始归并段所需时间为O(nlogw).

最佳归并树

归并的过程可以用一棵树来形象地描述,这棵树称为归并树,
为了优化归并树的带权路径长度,可以运用哈弗曼树。

败者树

为了突出如何利用败者树进行归并,在算法中避开了外存信息存取的细节,可以认为归并段已在内存。

typedef int LoserTree[k];  //败者树是完全二叉树且不含叶子,可采用顺序存储结构
typedef struct{
    KeyType key;
}ExNode,External[k+1];    //外结点,只存放待归并记录的关键字

void K_Merge(LoserTree &ls,External &b){
      /*利用败者树ls将编号从0到k-1的k个输入归并段中的记录归并到输出归并段。
b[0]至b[k-1]为败者树上的k个叶子结点,分别存放k个输入归并段中当前记录的关键字。
*/
    for(i = 0;i<k; ++ i) input(b[i].key);       //分别从k个输入归并段读入该段当前第一个记录的关键字到外结点
    CreateLoserTree(ls);    //建败者树ls,选得最小关键字为b[ls[0]].key
    while(b[ls[0]].key != MAXKEY){
      q = ls[0];    //q指示当前最小关键字所在归并段
      output(q);  //将编号为q的归并段中当前(关键字为b[q].key)的记录写至输出归  并段
      input(b[q].key,q);// 从编号为q的输入归并段中读取下一个记录的关键字
      Adjust(ls,q);    //调整败者树,选择性的最小关键字
    }//while    
    output(ls[0]);    //将含最大关键字MAXKEY的记录写至输出归并段
}


void CreateLoserTree(LoserTree &ls){
      //已知b[0]到b[k-1]为完全二叉树ls的叶子结点存在k个关键字
//,沿从叶子到根的k条路径将ls调整为败者树
    
    b[k].key  = MINKEY;    //设MINKEY为关键字可能的最小值
    for(i = 0;i < k;++i){
          ls[i] = k;    //设置ls中"败者"的初值
    }
    for(i = k-1; i>=0; - - i) Adjust(ls,i);   
 //依次从b[k-1],b[k-2],……,b[0]出发调整败者
   
}

while Adjust(LoserTree &ls,int s){
    //沿从叶子结点b[s]到根结点ls[0]的路径调整败者树
    t = (s+k)/2;
    while(t >0){
      if(b[s].key > b[ls[t]].key)  s<-->ls[t];    //s指示新的胜者
      t=t/2;
    }
    ls[0] = s;
}

由序号构造结点的好处是,不仅可以找到记录值,还可以找到其所在的归并段,以便于下一个记录读入内存取代刚选出的最值。

为什么得用败者树,而不是用堆?:

时间空间复杂度分析

外部排序的时间复杂度涉及很多方面,且分析较为复杂,一般考试不会让考试分析整个流程中与时间复杂度相关的每一个细节,因此只需要注意以下几点即可:

  1. m个初始归并段进行k路归并,归并的趟数为[logkm]
  2. 每一次归并,所有记录都要进行两次I/O操作。
  3. 置换-选择排序这一步中,所有记录都要进行两次I/O操作
  4. 置换-选择排序中,选最值那一步的时间复杂度要根据考试要求的选择算法而定
  5. k路归并的败者树的高度为[log2k]+1,因此利用败者树从k个记录中选出最值需要进行[log2k]此比较,即时间复杂度O(log2k)
  6. k路归并的败者树的建树时间复杂度为O(klog2k)
    注意k路归并败者树,不是k叉败者树,而是一颗二叉树

空间复杂度
显然所有步骤中的空间复杂度都是常量,因此是O(1)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354