目录

  • 置换-选择排序
  • 最佳归并树

外部排序分为几个步骤,首先根据内存将待排序文件分段,然后按照分段依次将每个分段的数据读入内存排序,最后将排序后的分段通过归并算法组合在一起。在排序的过程算法对外存的读写十分耗时,所以要尽量减少外存的读写。由于每次归并都要对所有数据进行一遍外存的读写,所以减少算法归并次数就能减少外存读写次数。而减少归并次数可以从两个方面入手:1.减少初始分段数;2.增加归并路数。
在介绍 多路平衡归并时已经介绍了增加归并路数的方法,下面介绍减少初始分段数的方法。

置换-选择排序

选择-置换排序是减少初始分段数外排序算法。
由于内部排序的方法都是在内存中对数据进行排序的,所以用这些方法得到的归并段长度完全依赖于内存工作区的大小。因而置换-选择排序没有采用先读取数据再排序的方法,而是边读取边排序。

假设初始待排文件为F,第 iii 个分段文件为 PiP_iPi​,内存工作区为M,工作区的可容纳m个记录,当前最小记录为MIN。则选择-置换排序的流程为:

  1. 从文件F读取m个记录到内存M,令 i=0i=0i=0
  2. 从M中选择最小的记录,记为MIN,并将MIN输出到 PiP_iPi​中
  3. 若F不空,从F中读取一个记录到M
  4. 从所有比MIN大的数据中选择最小的数据作为新的MIN,并将其输入到 PiP_iPi​中。
  5. 重复3-4步,直到选不出MIN,此时iii加1,
  6. 重复2-5步,直到M为空。

从内存区中选择最小数据使用败者树实现,为了实现第4步,在实现败者树的过程中需要注意一下几点:

  • 待排序元素有两个值,一个分段号,另一个是元素实际的值
  • 从文件中读取数据后,首先要将元素的值与MIN比较,如果元素小于MIN则元素的分段号等于i+1i+1i+1,反之等于 iii。
  • 调整败者树时,需要比较两个值,先比较元素所属分段号,分段号小者胜利,如果分段号相同,比较元素值,元素值小者胜利

C++代码

//排序包含10万个整数的文件,假设内存最多可容纳10000个元素
const int BuffSize = 10000;//假设最大缓存空间为10000
int CreateLosser(vector<pair<int, int>> &list, array<int, BuffSize - 1> &LosserTree);
int AdjustLosser(vector<pair<int, int>> &list, array<int, BuffSize - 1> &LosserTree, int index);
void Replace_Selection(string filepath) {ifstream in(filepath);ofstream out;vector<pair<int,int>> list;int filenum = 0;string temp;int min = -0x3f3f3f3f;int winner = BuffSize;while (list.size() < BuffSize) {if (in >> temp) {list.push_back({ stoi(temp),filenum });}else break;}list.push_back({ min,0 });//最后一个位置放“最小值”,用于初始化败者树array<int, BuffSize - 1> LosserTree;out.open("subsection_0_" + to_string(filenum) + ".txt");winner = CreateLosser(list, LosserTree);out << to_string(list[winner].first) << endl;while (true){if (in >> temp) {if (stoi(temp) < min) {list[winner] = { stoi(temp),filenum + 1 };}else {list[winner] = { stoi(temp),filenum };}}else {list[winner] = { 0x3f3f3f3f,filenum + 1 };}winner = AdjustLosser(list, LosserTree, winner);min = list[winner].first;if (min == 0x3f3f3f3f)break;if (list[winner].second > filenum) {out.close();filenum++;out.open("subsection_0_" + to_string(filenum) + ".txt");out << to_string(min) << endl;}else {out << to_string(min) << endl;}}in.close();out.close();
}int CreateLosser(vector<pair<int, int>> &list, array<int, BuffSize - 1> &LosserTree) {for (auto &item : LosserTree)item = BuffSize;int winner = BuffSize;for (int i = 0; i < BuffSize; i++) {winner = AdjustLosser(list, LosserTree, i);}return winner;
}int AdjustLosser(vector<pair<int, int>> &list, array<int, BuffSize - 1> &LosserTree,int index) {int winner = index;index = index + LosserTree.size();while (index>0){index = (index - 1) / 2;if (list[winner].second > list[LosserTree[index]].second) {int temp = LosserTree[index];LosserTree[index] = winner;winner = temp;}else if (list[winner].second == list[LosserTree[index]].second) {if (list[winner].first > list[LosserTree[index]].first) {int temp = LosserTree[index];LosserTree[index] = winner;winner = temp;}}}return  winner;
}

算法分析
通过选择-置换排序得到的分段长度不等,且初始归并段的平均长度为内存工作区的两倍。也就是说如果内存工作区可以容纳m个元素,则经过排序后的得到分段的平均长度为2m。与使用内排序的方法相比,选择-置换排序产生的分段数量减少了到原来的12\frac{1}221​。
若不计入读入写出数据所需的时间,对于n个记录,选择-置换排序仅需O(nlogm)O(nlogm)O(nlogm)的时间复杂度就可以将所有数据排序。

最佳归并树

在介绍最佳归并树之前,首先必须要知道什么是哈夫曼树。

从树中一个结点到另一个结点之间的分支构成两个节点之间的路径,路径上的分支数目称为路径长度。而树的路径长度是从树根到每一个节点的路径长度之和。
对于带权的节点的路径长度为从该节点到树根之间的路径程度和节点上权的乘积。树的带权路径长度为书中所有叶子结点的带权路径长度之和。
带权路径长度最小的二叉树被称作哈弗曼树。

置换-选择排序生成的分段长度是不相等的,对于这个不相等的分段直接执行多路平衡归并结果不一定最好。例如,置换-选择排序生成初始分段的长度分别为:[9,30,12,18,3,17,2,6,24],如果使用3-路归并,则其归并树如下:

整个归并需要对外存的读写次数为484,恰好为归并树带权路径长度的两倍。这给了我们提醒,是不是减少归并树的带权路径长度,就能减少外存读写次数呢?
答案是肯定的,我们可以针对不同长短的分段,构造一颗哈弗曼树(不一定是二叉树,可以是3叉、4叉等),以减少外存的读写次数。我们称这棵哈弗曼树为最佳归并树。针对上面那个问题,构造如下所示的最佳归并树,即可将外存读写次数从484次减少到446次。

补充虚段
假如有8个初始分段,而要构造一棵三叉的最佳归并树,该怎么办呢?——补充长度为0虚段。
例如,针对如下8个分段[9,12,18,3,17,2,6,24],构造一个三叉的最佳归并树,其结构如下,其中红色的节点为长度为0的虚段,用来辅助归并树的构建。

那么到底如何确定添加的虚段的数量呢?
一般的,假设分段数为 nnn,最佳归并树的路数为 kkk,若(m−1)MOD(k−1)=0(m-1)MOD(k-1)=0(m−1)MOD(k−1)=0,则不用加虚段,否则需要增加 k−(m−1)MOD(k−1)−1k - (m-1)MOD(k-1) - 1k−(m−1)MOD(k−1)−1 个虚段。(第一次归并为 (m−1)MOD(k−1)+1(m-1)MOD(k-1) + 1(m−1)MOD(k−1)+1路归并 )。

C++代码(仅给出部分)

//排序包含10万个整数的文件,接置换-选择排序,置换-选择排序和败者树的实现细节不再给出
void BestMerge(list<pair<int,string>> &files) {int addedSegment = merge_k - (files.size() - 1) % (merge_k - 1) - 1;for (int i = 0; i < addedSegment; i++)files.push_back({ 0,"" });while (files.size()>1){files.sort([](pair<int, string> a, pair<int, string>b) {return a.first < b.first; });Merge_K(files);}
}void Merge_K(list<pair<int, string>> &files) {const int buffersSize = BuffSize / (merge_k + 1);array<vector<int>, merge_k> inputBuffer;vector<int> outputBuffer;vector<int> maxval;maxval.push_back(-1);array<vector<int>::iterator, merge_k + 1> inputbfIterators;inputbfIterators[merge_k] = maxval.begin();int mergenum = 0;string temp;array<ifstream, merge_k>reader;for (int i = 0; i < merge_k; i++) {if (files.front().first>0) {reader[i].open(files.front().second);files.pop_front();for (int j = 0; j < buffersSize; j++) {if (reader[i] >> temp)inputBuffer[i].push_back(stoi(temp));else break;}}else {files.pop_front();inputBuffer[i].push_back(0x3f3f3f3f);}inputbfIterators[i] = inputBuffer[i].begin();}ofstream writer("subsection_filenum_" + to_string(files.size()+1) + ".txt");int outputnum = 0;array<int, merge_k - 1> lossertree;int winner = CreateLosserTree(lossertree, inputbfIterators);while (true) {for (int i = 0; i < merge_k; i++) {if (inputbfIterators[i] == inputBuffer[i].end()) {inputBuffer[i].clear();for (int k = 0; k < buffersSize; k++) {if (reader[i] >> temp)inputBuffer[i].push_back(stoi(temp));else {inputBuffer[i].push_back(0x3f3f3f3f);break;}}inputbfIterators[i] = inputBuffer[i].begin();}}if (outputBuffer.size() >= buffersSize) {for (auto item : outputBuffer) {writer << to_string(item) << endl;outputnum++;}outputBuffer.clear();}winner = Adjust(lossertree, inputbfIterators, winner);if (*(inputbfIterators[winner]) != 0x3f3f3f3f)outputBuffer.push_back(*(inputbfIterators[winner]++));else break;}if (outputBuffer.size() > 0) {for (auto item : outputBuffer) {writer << to_string(item) << endl;outputnum++;}}files.push_back({ outputnum,"subsection_filenum_" + to_string(files.size()+1) + ".txt" });cout << "生成文件" << "subsection_filenum_" + to_string(files.size()) + ".txt" << endl;fFILENAMEilenum = files.size();
}

算法整理:外排序篇-置换选择排序最佳归并树相关推荐

  1. 数据结构(八):排序 | 插入排序 | 希尔排序 | 冒泡排序 | 快速排序 | 简单选择排序 | 堆排序 | 归并排序 | 基数排序 | 外部排序 | 败者树 | 置换-选择排序 | 最佳归并树

    文章目录 第八章 排序 一.排序的基本概念 (一)什么是排序 (二)排序的应用 (三)排序算法的评价指标 (四)排序算法的分类 (五)总结 二.插入排序 (一)算法思想 (二)算法实现 (三)算法效率 ...

  2. 【外排序】外排序算法(磁盘排序、磁带排序) 外存设备结构分析 败者树多路归并 最佳归并树白话讲解

    外排序 外排序概述 外排序的基本方法是归并排序法 例子 总结 存储设备(可忽略) 磁带 磁带结构 磁盘 硬盘结构 块 硬盘上的数据定位 磁盘排序 磁盘排序过程 1.生成初始顺串 方法1(常规方法): ...

  3. 加标志量的选择排序算法c语言,置换选择排序算法详解(C语言实现)

    上一节介绍了增加 k-路归并排序中的 k 值来提高外部排序效率的方法,而除此之外,还有另外一条路可走,即减少初始归并段的个数,也就是本章第一节中提到的减小 m 的值. m 的求值方法为:m=⌈n/l⌉ ...

  4. 数据结构之外部排序:置换-选择排序

    外部排序:置换-选择排序 置换-选择排序算法思想: 步骤: 通过减少归并段r来减少IO次数 置换-选择排序算法思想: minimax就是FO中的最后一个数据元素的大小 1.将工作区填满 2.从工作区选 ...

  5. 【排序算法】冒泡排序、简单选择排序、直接插入排序比较和分析

    [排序算法]冒泡排序.简单选择排序.直接插入排序比较和分析 写在前面: 本文简单介绍了冒泡排序.简单选择排序.直接插入排序,并对这三种排序进行比较,入参都是80000个随机数,比较算法耗时.进一步,我 ...

  6. Java 与排序算法(2):选择排序

    一.选择排序 选择排序(Selection Sort)是一种简单的排序算法,其基本思想是在待排序序列中选择最小(或最大)的元素,将其与序列的第一个元素交换位置,然后在剩余的元素中继续选择最小(或最大) ...

  7. 8-14外部排序-置换选择排序

    效果: 构造更长的初始归并段 初始归并段的数量尽可能的少 以前的办法: 若文件共有n个记录,每个初始归并段只能包含l个记录(用于内部排序的内存工作区WA可容纳l个记录),则初始归并段数量r=n/l 现 ...

  8. 数据结构之外部排序:最佳归并树

    外部排序:最佳归并树 思维导图: 归并树的定义: 例: 最佳归并树(本质是一颗哈夫曼树): 所有的初始归并段一定能构造出一颗完美的哈夫曼树吗? 怎么选择补充虚短的个数? 思维导图: 归并树的定义: 例 ...

  9. c语言数据结构-算法篇之选择排序

    文章目录 前言 一.选择排序是什么? 二.选择排序 代码 前言 排序方法是一种重要的,基本的算法.排序的方法很多,本章就介绍选择排序 一.选择排序是什么? 第一次从R[0]~R[n-1]中选取最小值, ...

最新文章

  1. java中String的常用方法
  2. 用Memcache守护程序把数据缓存到内存二
  3. mysql 代替intersect_MySQL不支持INTERSECT和MINUS及其替代方法_MySQL
  4. 2.1.2 Dropout正则化以及其他正则化
  5. python求函数曲率_【Python】车道线拟合曲线的曲率半径计算公式及代码
  6. vba定义全局变量并赋值_利用VBA代码如何打开任意程序
  7. linux 串口控制read()的说明
  8. 如何搭建低延时、交互式的在线教育平台?(内附视频回放)
  9. 注释那些事儿:前端代码质量系列文章(一)
  10. 通过udp及vlc传递视频流
  11. STA 137 Topics covered this week
  12. Android中插件开发篇之----类加载器
  13. paip.提升用户体验---c++ 悬浮窗体以及右键菜单以及socket接口
  14. 智能AI机器人源码,电话机器人源码和系统部署运行环境freeswitch
  15. 思维导图的了解和使用
  16. 【阿里巴巴Java编程规范学习 四】Java质量安全规约
  17. 学编程怎样入门?这是最有效的学习方法
  18. 分享5个Excel实用日常小技巧,不会的别说你会Excel!
  19. 职场领导想逼你走时,他会用哪些奇招逼你走?
  20. java如何让坦克动起来_[笔记]基于java的坦克大战游戏实现思路

热门文章

  1. LUN详解 iscsi (转载)
  2. 能和小米比性价比的手环内部是啥样?
  3. Python代码加密方案
  4. 面向对象的四大特征(非常重要)
  5. 游戏设计之我见 —— 动作游戏中的攻防设计
  6. Vue学习-基础篇7
  7. Unity 基于Jenkins自动化打包流程
  8. 如何搭建服务器做财务系统,财务系统搭建到云服务器
  9. 资深老建模师带你快速掌握ZBrush中自动拓扑功能与动态网格功能,明日星星指日可待
  10. Flutter 实现微信朋友圈功能