`
chriszeng87
  • 浏览: 717454 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

外部排序技术之多路归并

阅读更多

转自:http://blog.chinaunix.net/uid-25324849-id-2182916.html

 

重点:败者树的创建调整函数

1.外部排序概述

外部排序指的是大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,以达到排序整个文件的目的。外部排序最常用的算法是多路归并排序,即将原文件分解成多个能够一次性装人内存的部分,分别把每一部分调入内存完成排序。然后,对已经排序的子文件进行归并排序。

 

2. 多路归并的实现

 

 

2.1 胜者树

 

胜者进入下一轮,直至决出本次比赛的冠军。决出冠军之后,充分利用上一次比赛的结果,使得更快地挑出亚军、第三名  ……  。

示例:我们这里以四路归并为例,假设每个归并段已经在输入缓冲区如下图。

 

每路的第一个元素为胜利树的叶子节点,(5,7)比较出5胜出成为其根节点,(29,9)比较9胜出成为其节点,一次向上生成一棵胜利树,然后我们可以得出5为冠军,将第一路归并段的元素5放入输出缓冲区,然后将第一路第二个元素放到胜利树中如下:

 

由第一次得到的胜利树知,我们这里只改变了第1路的叶子节点,所有根节点7的右子树不用再比较,(16,7)比较7胜出,然后7和右子树的胜利者比较7胜出得到亚军,只进行了2次比较。

所以我们知道:

 决出第一名需比较:   k - 1     次

 决出第二名需比较:       次

 决出第三名需比较:       次 .............

 

2.2 败者树

与胜利树相类似,败者树是在双亲节点中记录下刚刚进行完的这场比赛的败者,让胜者去参加更高一层的比赛。

示例:我们这里以四路归并为例,假设每个归并段已经在输入缓冲区如下图。

每路的第一个元素为胜利树的叶子节点,(5,7)比较出5胜出7失败成为其根节点,(29,9)比较9胜出29失败成为其节点,胜者(5,9)进行下次的比赛7失败成为其根节点5胜出输出到输出缓冲区。由第一路归并段输出,所有将第一路归并段的第二个元素加到叶子节点如下图:

 

加入叶子节点16进行第二次的比较,跟胜利树一样,由于右子树叶子节点没有发生变化其右子树不用再继续比较。

 

2.3 败者树程序实现

 

在创建败者树的时候初始化b[...]和ls[...],b[0]~b[k-1]为k路的第一个元素,即为败者树的叶子节点,ls[0]~ls[k-1]存储的为每次比赛的失败者。

/**  

* 已知b[0]到b[k-1]为完全二叉树ls的叶子结点,存有k个关键字,沿从叶子  

* 到根的k条路径将ls调整成为败者树。 

*/ 

void CreateLoserTree(LoserTree ls){  

    int i; 

    b[k] = MINKEY; 

     

    /* 设置ls中“败者”的初值 */ 

    for(i = 0; i < k; ++i){ 

        ls[i] = k;  

    } 

     

    /* 依次从b[k-1],b[k-2],…,b[0]出发调整败者 */ 

    for(i = k - 1; i >= 0; --i){ 

        Adjust(ls, i); 

    } 

}

 

 

/* 沿从叶子结点b[s]到根结点ls[0]的路径调整败者树。*/ 

void Adjust(LoserTree ls, int s){  

    int i, t;   

    /* ls[t]是b[s]的双亲结点 */ 

    t = (s + k) / 2;  

    while(t > 0){ 

        /* s指示新的胜者 */ 

        if(b[s] > b[ls[t]]){ 

            i = s; 

            s = ls[t];  

            ls[t] = i; 

        } 

        t = t / 2; 

    } 

    ls[0] = s; 

}

第一次调整:

 

由程序可以,先找到叶子节点的父节点,t = (s + k) / 2 = 3 ;  s为3),

 while(t > 0){ 

        /* s指示新的胜者 */ 

        if(b[s] > b[ls[t]]){ 

            i = s; 

            s = ls[t];  

            ls[t] = i; 

        } 

        t = t / 2; 

    } 

b[ls[t=3]] = b[k] = MINKEY < b[s] = b[3] 则交换ls[t]=k和s=3,然后t除以2,t/2 = 1, b[ls[1]] = b[k] = MINKEY ,b[s=k]=MINKEY,直到跳出循环,然后 ls[0] = s; 由于ls[0] = s = k,所有不变;

 

由第二路归并树程序进入调整函数,找到父节点为3,然后就是b[2]和b[3]比较,b[3] = 9胜出,则留在ls[3] = 2,进入下一层的为ls[1] = 3;

由第一路归并树进入调整函数,找到父节点为2,然后是b[1]和b[k=4]比较由于b[4]为最小值,所有b[4]胜出,b[1]失败留在父节点ls[2] = 1,胜者进入上一层与ls[1]比较,很明显b[4]为最小值胜出到达ls[0],留在ls[1] = 3;

由第一路归并树进入调整树,先找到父节点2,然后与父节点比较b[0]胜出,b[1]依旧留在ls[2],继续上一层的比较直到为上图为止。

 

我们通过对创建败者树的分析可以知道,程序利用初始化败者树全为第k路,一个不存在的一路归并树,并且置第k路的值b[k]为最小值,这是为了让它在每次比较中都能胜出,让第一次比较的值留在失败者的位置,第二次比较的时候自然就跟下一路比较了,这样设计可以减少程序设计的特殊性,避免了特殊情况的出现。创建好败者树后,就可以利用败者树的性质来进行判断了。

 

实现代码:(为了防止归并段变为空的情况,我们将每路归并段最后都加入 了一个最大元素)

#include <stdio.h> 

#include <stdlib.h> 

#include <string.h> 

 

#define TRUE 1 

#define FALSE 0 

#define OK 1 

#define ERROR 0 

#define INFEASIBLE -1 

#define MINKEY -1 

#define MAXKEY 100 

 

/* Status是函数的类型,其值是函数结果状态代码,如OK等 */ 

typedef int Status;  

 

/* Boolean是布尔类型,其值是TRUE或FALSE */ 

typedef int Boolean; 

 

/* 一个用作示例的小顺序表的最大长度 */ 

#define MAXSIZE 20  

 

typedef int KeyType;

 

 

/* k路归并 */ 

#define k 3  

 

/* 设输出M个数据换行 */ 

#define M 10  

 

/* k+1个文件指针(fp[k]为大文件指针),全局变量 */ 

FILE *fp[k + 1];  

 

/* 败者树是完全二叉树且不含叶子,可采用顺序存储结构 */ 

typedef int LoserTree[k];  

 

typedef KeyType ExNode, External[k+1];  

 

/* 全局变量 */ 

External b;  

 

/* 从第i个文件(第i个归并段)读入该段当前第1个记录的关键字到外结点 */ 

int input(int i, KeyType *a){ 

    int j = fscanf(fp[i], "%d ", a); 

    if(j > 0){ 

        printf("%d\n", *a); 

        return 1; 

    }else{ 

        return 0; 

    } 

 

/* 将第i个文件(第i个归并段)中当前的记录写至输出归并段 */ 

void output(int i){ 

    fprintf(fp[k], "%d ", b[i]); 

 

/* 沿从叶子结点b[s]到根结点ls[0]的路径调整败者树。*/ 

void Adjust(LoserTree ls, int s){  

    int i, t; 

     

    /* ls[t]是b[s]的双亲结点 */ 

    t = (s + k) / 2;  

    while(t > 0){ 

        /* s指示新的胜者 */ 

        if(b[s] > b[ls[t]]){ 

            i = s; 

            s = ls[t];  

            ls[t] = i; 

        } 

        t = t / 2; 

    } 

    ls[0] = s; 

 

/**  

* 已知b[0]到b[k-1]为完全二叉树ls的叶子结点,存有k个关键字,沿从叶子  

* 到根的k条路径将ls调整成为败者树。 

*/ 

void CreateLoserTree(LoserTree ls){  

    int i; 

    b[k] = MINKEY; 

     

    /* 设置ls中“败者”的初值 */ 

    for(i = 0; i < k; ++i){ 

        ls[i] = k;  

    } 

     

    /* 依次从b[k-1],b[k-2],…,b[0]出发调整败者 */ 

    for(i = k - 1; i >= 0; --i){ 

        Adjust(ls, i); 

    } 

 

/**  

* 利用败者树ls将编号从0到k-1的k个输入归并段中的记录归并到输出归并段。  

* b[0]至b[k-1]为败者树上的k个叶子结点,分别存放k个输入归并段中当前记录的关键字。  

*/ 

void K_Merge(LoserTree ls, External b){  

    int i, q; 

     

    /* 分别从k个输入归并段读人该段当前第一个记录的关键字到外结点 */ 

    for(i = 0; i < k; ++i) { 

        input(i, &b[i]); 

    } 

     

    /* 建败者树ls,选得最小关键字为b[ls[0]].key */ 

    CreateLoserTree(ls);  

     

    while(b[ls[0]] != MAXKEY){ 

        /* q指示当前最小关键字所在归并段 */ 

        q = ls[0];  

         

        /* 将编号为q的归并段中当前(关键字为b[q].key)的记录写至输出归并段 */ 

        output(q);  

         

        /* 从编号为q的输入归并段中读人下一个记录的关键字 */ 

        if(input(q, &b[q]) > 0){ 

            /* 调整败者树,选择新的最小关键字 */ 

            Adjust(ls,q);  

        }  

    } 

     

    /* 将含最大关键字MAXKEY的记录写至输出归并段 */ 

    output(ls[0]);  

 

void show(KeyType t) { 

    printf("(%d)", t); 

 

int main(){ 

    KeyType r; 

    int i, j; 

    char fname[k][4], fout[5] = "out", s[3]; 

    LoserTree ls; 

     

    /* 依次打开f0,f1,f2,…,k个文件 */ 

    for(i = 0; i < k; i++){  

        /* 生成k个文件名f0,f1,f2,… */ 

        itoa(i, s, 10);  

        strcpy(fname[i], "f"); 

        strcat(fname[i], s); 

         

        /* 以读的方式打开文件f0,f1,… */ 

        fp[i] = fopen(fname[i], "r");  

        printf("有序子文件f%d的记录为:\n",i); 

         

        /* 依次将f0,f1,…的数据读入r */ 

        do{ 

            j = fscanf(fp[i], "%d ", &r); 

            /* 输出r的内容 */ 

            if(j == 1){ 

                show(r);  

            } 

        }while(j == 1); 

        printf("\n"); 

         

        /* 使fp[i]的指针重新返回f0,f1,…的起始位置,以便重新读入内存 */ 

        rewind(fp[i]);  

    } 

     

    /* 以写的方式打开大文件fout */ 

    fp[k] = fopen(fout, "w");  

     

    /* 利用败者树ls将k个输入归并段中的记录归并到输出归并段,即大文件fout */ 

    K_Merge(ls, b);  

     

    /* 关闭文件f0,f1,…和文件fout */ 

    for(i = 0; i <= k; i++){ 

        fclose(fp[i]);  

    } 

     

    /* 以读的方式重新打开大文件fout验证排序 */ 

    fp[k] = fopen(fout, "r");  

    printf("排序后的大文件的记录为:\n"); 

     

    i = 1; 

    do{ 

        /* 将fout的数据读入r */ 

        j = fscanf(fp[k], "%d ", &r); 

 

        /* 输出r的内容 */ 

        if(j == 1){ 

            show(r);  

        } 

         

        /* 换行 */ 

        if(i++ % M == 0){ 

            printf("\n");  

        } 

    }while(j == 1); 

    printf("\n"); 

     

    /* 关闭大文件fout */ 

    fclose(fp[k]);  

    return 0;

} 

测试数据:注意在每个文件后面都应该加一个哨兵,即一个最大值
f0: 10 15 16 100 
f1: 9 18 20 100
f2: 20 22 40 100
out: 9 10 15 16 18 20 20 22 40 100 

参考文献:

[1] http://baike.baidu.com/view/1368718.htm

[2] http://blog.csdn.net/nomad2/archive/2007/12/15/1940266.aspx

分享到:
评论

相关推荐

    二路归并和多路归并排序PPT数据结构课件

    二路归并和多路归并排序PPT数据结构课件

    算法之外部排序.pdf

    算法 之 外部排序(上海交大 课件) 1、外存信息的存取 2、外部排序的方法 3、多路平衡归并的实现 4、置换-选择排序 5、最佳归并树

    Python实现八大排序

    一般使用的八大排序算法是:插入排序、选择排序、冒泡排序、希尔排序、归并排序、快速排序、堆排序、基数排序,每个方法有其适合的使用场景,可以... (这八种排序算法中除了多路归并排序是外部排序,其他都是内部排序)

    【华科复试】【贪心算法】最优二路归并树&二路归并排序

    二元归并树:二路归并模式的归并过程可以用一个二元树的形式描述,称之为二元归并树。 贪心求解: 任意两个文件的归并所需的元素移动次数与这两个文件的长度之和成正比。度量规则:每次选择需要移动次数最少的两个...

    数据结构考研纲要

    86.外部排序 (1)文件较大,内存一次放不下 (2)两个阶段: ①生成初始归并段:读磁盘输入内存;采用有效的内排序方法分别进行排序,生成若干个有序子文件;即初始归并段 ②多趟归并排序 (3)归并排序 (4)m路归并;m+1...

    数据结构 c语言版

    11.3 多路平衡归并的实现 11.4 置换一选择排序 11.5 最佳归并树 第12章 文件 12.1 有关文件的基本概念 12.2 顺序文件 12.3 索引文件 12.4 ISAM文件和VSAM文件 12.4.1 ISAM文件 12.4.2 VSAM文件 12.5 直接...

    数据结构(C语言版)

    11.3 多路平衡归并的实现 11.4 置换一选择排序 11.5 最佳归并树 第12章 文件 12.1 有关文件的基本概念 12.2 顺序文件 12.3 索引文件 12.4 ISAM文件和VSAM文件 12.4.1 ISAM文件 12.4.2 VSAM文件 12.5 直接存取文件...

    《C算法》((美国)Robert Sedgewick)清晰版[DJVU] 第一卷

    11.3 外部排序 349 练习 353 11.4 排序归并的实现 353 练习 358 11.5 并行排序归并 359 练习 361 第三部分参考文献 362 第四部分 搜索 第12章 符号表和二叉搜索树 365 12.1 符号表抽象数据类型 366 练习 369 12.2 键...

    《C算法》((美国)Robert Sedgewick)清晰版[DJVU] 第二卷

    11.3 外部排序 349 练习 353 11.4 排序归并的实现 353 练习 358 11.5 并行排序归并 359 练习 361 第三部分参考文献 362 第四部分 搜索 第12章 符号表和二叉搜索树 365 12.1 符号表抽象数据类型 366 练习 369 12.2 键...

    数据结构(C语言版)\Java数据结构和算

    7.10 外部排序 7.11 参考文献和选读材料 第8章 Hash法 8.1 引言 8.2 静态Hash法 8.3 动态Hash法 8.4 Bloom滤波器 8.5 参考文献和选读材料 第9章 优先队列 9.1 单端优先队列和双端优先队列 9.2 左倾树 9.3...

    数据结构(C语言版)[严蔚敏]

    11.3 多路平衡归并的实现 11.4 置换一选择排序 11.5 最佳归并树 第12章 文件 12.1 有关文件的基本概念 12.2 顺序文件 12.3 索引文件 12.4 ISAM文件和VSAM文件 12.4.1 ISAM文件 12.4.2 VSAM文件 12.5 直接存取文件...

    《数据结构》(C语言版)严蔚敏

    11.3 多路平衡归并的实现 11.4 置换一选择排序 11.5 最佳归并树 第12章 文件 12.1 有关文件的基本概念 12.2 顺序文件 12.3 索引文件 12.4 ISAM文件和VSAM文件 12.4.1 ISAM文件 12.4.2 VSAM文件 12.5 直接存取文件...

    [数据结构(C语言版)].严蔚敏_吴伟民.高清扫描版.rar

    11.3 多路平衡归并的实现 11.4 置换一选择排序 11.5 最佳归并树 第12章 文件 12.1 有关文件的基本概念 12.2 顺序文件 12.3 索引文件 12.4 ISAM文件和VSAM文件 12.4.1 ISAM文件 12.4.2 VSAM文件 12.5 直接存取文件...

    数据结构的上机作业答案

    实验1: 1)熟悉Vc 6.0环境 ...1) 以书中图11.4的数据,程序实现多路平衡归并排序。 2)以书中图11.5的数据,程序实现置换-选择排序。 实验12:文件 1)以书中图12.4的数据,程序实现顺序文件。

    严蔚敏:数据结构题集(C语言版)

    11.3 多路平衡归并的实现 11.4 置换-选择排序 11.5 最佳归并树 第12章 文件 12.1 有关文件的基本概念 12.2 顺序文件 12.3 索引文件 12.4 ISAM文件和VSAM文件 12.4.1 ISAM文件 12.4.2 VSAM文件 12.5 直接存取文件...

    数据结构 上机作业答案

    实验1: 1)熟悉Vc 6.0环境 2)用两种算法实现1-1/...1) 以书中图11.4的数据,程序实现多路平衡归并排序。 2)以书中图11.5的数据,程序实现置换-选择排序。 实验12:文件 1)以书中图12.4的数据,程序实现顺序文件。

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar

    他是我国计算机普及和高校计算机基础教育开拓者之一,现任全国高等院校计算机基础教育研究会会长、教育部全国计算机应用技术证书考试委员会主任委员。 谭浩强教授创造了3个世界纪录:(1)20年来他(及和他人合作)...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar )

    他是我国计算机普及和高校计算机基础教育开拓者之一,现任全国高等院校计算机基础教育研究会会长、教育部全国计算机应用技术证书考试委员会主任委员。 谭浩强教授创造了3个世界纪录:(1)20年来他(及和他人合作)...

    数据结构与算法分析第二版 ---C语言描述(附加答案)

    排序7.1 预备知识7.2 插入排序7.2.1 算法7.2.2 插入排序的分析7.3 一些简单排序算法的下界7.4 希尔排序7.4.1 希尔排序的最坏情形分析7.5 堆排序7.5.1 堆排序的分析7.6 归并排序7.6.1 归并排序的分析7.7 快速排序...

Global site tag (gtag.js) - Google Analytics