用败者树实现置换-选择算法

很多排序算法,插入排序、选择排序、归并排序等,这些算法都属于内部排序算法,排序的整个过程只是在内存中完成。而当待排序的文件比内存的可使用容量还大时,文件无法一次性放到内存中进行排序,需要借助于外部存储器(例如硬盘、U盘、光盘),这时要用到外部排序算法来解决。

外部排序算法由两个阶段构成:

1、按照内存大小,将大文件分成若干长度为l的子文件(l应小于内存的可使用容量),然后将各个子文件依次读入内存,使用适当的内部排序算法对其进行排序(排好序的子文件称为归并段或者顺段),将排好序的归并段重新写入外存,为下一个子文件排序腾出内存空间。

2、对得到的归并段进行合并,直至得到整个有序的文件为止。

/**
*    实验题目:
*        用败者树实现置换-选择排序算法
*    实验目的:
*        领会外排序中置换-选择排序算法的执行过程和算法设计
*    实验内容:
*        编写程序,模拟置换-选择算法生成初始归并段的过程以求解以下问题:
*    设磁盘文件中共有18个记录,记录的关键字序列为:(15, 4, 97, 64, 17, 32, 108,
*    44, 76, 9, 39, 82, 56, 31, 80, 73, 255, 68)
*    若内存工作区可容纳5个记录,用置换-选择排序可产生几个初始归并段,每个
*    初始归并段包含哪些记录?假设输入文件数据和输出归并段数据均放在内存中。
*/

#include <stdio.h>


#define MAX_SIZE    50                          //  每个文件最多记录数
#define MAX_KEY     32767                       //  最大关键字值∞
#define W           5                           //  内存工作区可容纳的记录个数

typedef int key_type;                           //  定义关键字类型为整型
typedef int info_type;                          //  定义其他数据项的类型
typedef int loser_tree;                         //  败者树结点类型,它是完全二叉树且不含叶子,采用顺序存储结构

typedef struct
{
    key_type key;                               //  关键字项
    info_type other_info;                       //  其他数据项,具体类型在主程序中定义,这里假设为int
}rec_type;                                      //  排序文件的记录类型

typedef struct
{
    rec_type recs[MAX_SIZE];                    //  存放文件中的数据项
    int length;                                 //  存放文件中实际记录个数
    int cur_rec;                                //  存放当前位置
}file_type;                                     //  文件类型

typedef struct
{
    rec_type rec;                               //  存放记录
    int r_num;                                  //  所属归并段的段号
}work_area;                                     //  内存工作区元素类型,其容量为W

file_type fi;                                   //  定义输入文件,为全局变量
file_type fo;                                   //  定义输出文件,为全局变量

/*-------------------输入输出文件初始化---------------------*/
static void initial(void)
{
    int i;
    int n = 19;

    key_type a[] = {15, 4, 97, 64, 17, 32, 108, 44, 76, 9, 39, 82, 56, 31, 80, 73, 255, 68, MAX_KEY};
    for(i = 0; i < n; i++)                      //  将n个记录存放到输入文件中
        fi.recs[i].key = a[i];

    fi.length = n;                              //  输入文件有n个记录
    fi.cur_rec = -1;                            //  输入文件中当前位置为-1
    fo.cur_rec = -1;                            //  输出文件中当前位置为-1
    fo.length = 0;                              //  输出文件中没有任何记录
}

/*-----------------从wa[q]起到败者树的根比较选择最小关键字的记录,并由q指示它所在的归并段-----------------*/
static void select_minimax(loser_tree ls[W], work_area wa[W], int q)
{
    int p, s, t;

    for(t = (W + q) / 2, p = ls[t]; t > 0; t = t / 2, p = ls[t])
    {
        if(wa[p].r_num < wa[q].r_num || wa[p].r_num == wa[q].r_num && wa[p].rec.key < wa[q].rec.key)
        {
            s = q;
            q = ls[t];              //  q指向新的胜者
            ls[t] = s;
        }
    }
    ls[0] = q;                      // 根节点
}

/*-----------------输入W个记录到内存工作区wa,创建败者树ls,选最小的记录并由s指示其在wa中的位置-------------*/
static void construct_loser(loser_tree ls[W], work_area wa[W])
{
    int i;

    for(i = 0; i < W; i++)
        wa[i].r_num = wa[i].rec.key = ls[i] = 0;    //  工作区初始化

    for(i = W - 1; i >= 0; i--)
    {
        fi.cur_rec++;                               //  从输入文件读入一个记录
        wa[i].rec = fi.recs[fi.cur_rec];
        wa[i].r_num = 1;                            //  其段号为1
        select_minimax(ls, wa, i);                  //  调整败者
    }
}

/*-----------------求一个初始归并段--------------------*/
static void get_run(loser_tree ls[W], work_area wa[W], int rc, int &rmax)       //  引用
{
    int q;
    key_type mini_max;                          //  当前最小关键字

    while(wa[ls[0]].r_num == rc)                //  选得的当前最小记录属于当前段时
    {
        q = ls[0];                              //  q指向当前最小记录在wa中的位置
        mini_max = wa[q].rec.key;
        fo.cur_rec++;                           //  将刚选得的当前最小记录写入输出文件
        fo.length++;
        fo.recs[fo.cur_rec] = wa[q].rec;
        fi.cur_rec++;                           //  从输入文件读入下一记录
        wa[q].rec = fi.recs[fi.cur_rec];
        if(fi.cur_rec >= fi.length - 1)         //  输入文件结束,虚设记录(属rmax+1段)
        {
            wa[q].r_num = rmax + 1;
            wa[q].rec.key = MAX_KEY;
        }
        else                                    //  输入文件非空时
        {
            if(wa[q].rec.key < mini_max)        //  新读入的记录属于下一段
            {
                rmax = rc + 1;
                wa[q].r_num = rmax;
            }
            else                                //  新读入的记录属于当前段
                wa[q].r_num = rc;
        }
        select_minimax(ls, wa, q);              //  选择新的当前最小记录
    }
}

/*-----------------在败者树ls和内存工作区wa上用置换-选择排序求初始归并段--------------*/
static void replace_selection(loser_tree ls[W], work_area wa[W])
{
    int rc;
    int rmax;
    rec_type j;                                 //  j作为一个关键字最大记录,作为一个输出段结束标志

    j.key = MAX_KEY;
    construct_loser(ls, wa);                    //  初建败者树
    rc = 1;                                     //  rc指示当前生成的初始归并段的段号
    rmax = 1;                                   //  rmax指示wa中关键字所属初始归并段的最大段号
    while(rc <= rmax)                           //  rc = rmax+1标志输入文件的置换-选择排序已完成
    {
        get_run(ls, wa, rc, rmax);              //  求得一个初始归并段
        fo.cur_rec++;                           //  将段结束标志写入输出文件
        fo.recs[fo.cur_rec] = j;
        fo.length++;
        rc = wa[ls[0]].r_num;                   //  设置下一段的段号
    }
}

int main(void)
{
    int i = 0;                                  //  循环变量
    int rno = 1;                                //  归并段个数

    initial();
    loser_tree ls[W];                           //  败者树数组
    work_area wa[W];                            //  工作区数组

    printf("大文件的记录为:\n ");
    while(fi.recs[i].key != MAX_KEY)
    {
        printf("%d ", fi.recs[i].key);
        i++;
    }
    printf("\n");
    replace_selection(ls, wa);                  //  用置换-选择排序求初始归并段
    printf("产生的归并段文件的记录如下:\n");
    printf("  归并段%d:", rno);                 //  输出所有的归并段
    for(i = 0; i < fo.length; i++)
    {
        if(fo.recs[i].key == MAX_KEY)
        {
            printf("∞");
            if(i < fo.length - 1)
            {
                rno++;
                printf("\n  归并段%d:", rno);
            }
        }
        else
            printf("%d ", fo.recs[i].key);
    }
    printf("\n  共产生%d个归并段文件\n", rno);

    return 0;
}
测试结果:

大文件的记录为:
 15 4 97 64 17 32 108 44 76 9 39 82 56 31 80 73 255 68
产生的归并段文件的记录如下:
  归并段1:4 15 17 32 44 64 76 82 97 108 ∞
  归并段2:9 31 39 56 68 73 80 255 ∞
  共产生2个归并段文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值