置换-选择排序

外部排序过程中,为了减少外存读写次数需要减小归并趟数(外部排序的过程中用到归并),归并趟数为:外部排序1(其中k为归并路数,n为归并段的个数)。增加k和减小n都可以达到减小归并趟数的目的。置换-选择排序就是一种减小n的、在外部排序中创建初始归并段时用到的算法。它可以让初始归并段的长度增减,从而减小初始归并段的段数(因为总的记录数是一定的)。

 

置换-选择排序是在树形选择排序的基础上得来的,它的特点是:在整个排序(得到初始归并段)的过程中,选择最小(或最大)关键值和输入、输出交叉或并行进行。它的主要思路是:用败者树从已经传递到内存中的记录中找到关键值最小(或最大)的记录,然后将此记录写入外存,再将外存中一个没有排序过的记录传递到内存(因为之前那个记录写入外存后已经给它空出内存),然后再用败者树的一次调整过程找到最小关键值记录(这个调整过程中需要注意:比已经写入本初始归并段的记录关键值小的记录不能参见筛选,它要等到本初始段结束,下一个初始段中才可以进行筛选),再将此最小关键值记录调出,再调入新的记录.......依此进行指导所有记录已经排序过。内存中的记录就是所用败者树的叶子节点。

 

假设初始待排序文件为FI,初始归并段文件为输出文件FO,内存工作区为WA,FO与WA的初始状态为空,并假设内存工作去WA的容量可容纳w个记录,则置换-选择排序的操作的过程为:

   (1)、从FI输入w个记录到工作区WA。

   (2)、从WA中选出其中关键字最小的记录,记为MINIMAX记录。

   (3)、将MINIMAX记录输出到FO中去。

   (4)、若FI不为空,则从FI输入下一个记录到WA中。

   (5)、从WA中所有关键字比MINIMAX记录关键字大的记录中选出最小关键字记录,作为新的MINIMAX记录。

   (6)、重复(3)~(5),直至WA中选不出新的MINIMAX记录为止,由此得到一个初始归并段,输出一个归并段的结束标记到FO中去。

   (7)、重复(2)~(6),直至WA为空。由此得到全部归并段。

 

在WA中选择MINIMAX记录的过程利用“败者树”来实现。就置换-排序中败者树的实现细节这里加以说明。(1)、内存空间中的记录做为败者树的外部节点,而败者树中根节点的双亲节点指示工作区中关键字最小的记录。(2)、为了便于选择MINIMAX记录,为每个记录附设一个所在归并段的序号,在进行关键字的比较时,先比较段号,段号小的为胜者;段号相同的则关键字小的为胜者;(3)、败者树的建立可以从设工作区中所有记录的段号为0开始,让后从FI逐个输入w个记录到工作区时,自下而上调整败者树,由于这些记录的段号为1,则他们对于0段的记录来说均为败者,从而逐个填充到败者树的各节点中去。

 

下面是几个置换排序中用到的函数(伪代码):

typedef struct

{

     RcdType rec;     //记录

     KetType key;    //从记录中抽取的关键字

     int rnum;       //所属归并段的段号

}RcNode,WorkArea[w];     //内存工作区,容量为w

 

typedef int LoserTree[w]; 

//在败者树ls和内存工作区wa上用置换-选择排序求初始归并段,fi为输入文件指针,fo为输出文件指针,

//两个文件均已打开

void Replace_Selection(LoserTree &ls,WorkArea &wa,FILE* fi,FILE* fo)

{

      Construct_loser(ls,wa);  //初建败者树

      rc=rmax=1; //rc指示当前生成的初始归并段的段号,rmax指示wa中关键字所属归初始归并段的最大

                 //段号,在此过程中,rmax比rc最多大1

      while(rc<=rmax)   //rc=rmax+1标志着输入文件的置换-选择排序已经完成

      {

            get_run(ls,wa);  //求得一个初始归并段

            fwrite(&RUNEND_SYMBOL,sizeof(struct RcdType),1,fo);//将段结束标记写入输出文件

            rc=wa[ls[0]].rnum;

       }

}

 

//求得一个初始归并段,fi为输入文件指针,fo为输出文件指针

void get_run(LoserTree &ls,WorkArea &wa)

{

      while(wa[ls[0]].rnum==rc)

      {

            q=ls[0];//选得的MINIMAX记录属当前段时,q指示MINIMAX记录在wa中的记录

            minimax=wa[q].key;

            fwrite(&wa[q].rec,sizeof(RcdType),1,fo);

            if(feof(fi)) //若输入文件结束,虚设记录(属rmax+1段)

            {

                  wa[q].rnum=rmax+1;

                  wa[q].key=MAXKEY;

             }

            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记录

       }

}

 

//从wa[q]起到败者树的根比较选择MINIMAX记录,并有q指示它所在的归并段

void Select_MiniMax(LoserTree &ls,WorkArea wa,int 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)

           {

                int temp=ls[t];

                ls[t]=q;

                q=temp;   //q指示新的胜利者

           }

      ls[0]=q;

}

 

//输入w个记录到内存工作区wa,建得败者树ls,选出关键字最小的记录并由s指示器在wa中的位置

void Construct_Loser(LoserTree &ls,WorkArea &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);  //调整败者

       }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值