存储器
- 计算机存储器分为主存储器(内存)和外存储器
- 内存性能高,价格高;外存性能低,价格也低,并且便携。
- 外存的基本存储单位:页(page),长度固定的存储块
文件的组织和管理
- 文件(file),外存上的数据结构,由大量相同的记录组成。
- 记录(record ),具有独立逻辑意义的数据块,可以由字符、二进制序列、字段或域组成。
- 操作系统的文件:连续字符序列,没有明显结构。
- 数据库文件:有结构,每个记录由一个或多个不可再分割的数据项组成。
- 定长文件:每一条记录是等长的。
- 不定长文件:记录的长度不相等。
- 单关键码文件
- 多关键码文件
- 实时和批量两种处理方式
- 文件的逻辑组织三种方式:顺序结构的定长记录、顺序结构的不定长记录、按关键码存取的记录
- 文件物理结构:顺序、索引、散列、倒排
- C++文件流(file stream),以外存文件为输入输出对象的数据流。
- istream, ostream, iostream, ifstream, ofstream, fstream
- fstream成员函数
- 文件三种操作:指针设定到指定位置、从当前位置读取字节、向当前位置写入字节
外排序
- 外排序(external sort),外存文件排序技术,对存放在外存中的数据进行排序。
- 思路:根据内存大小,将外存中的数据文件划分为若干段,每次把其中一段读入内存并排序
- 顺串或归并段(run),对每一段排序的结果子文件
- 外排序两个阶段:形成尽可能长的顺串、逐趟归并顺串。
- 时间:外存信息读写、在内存内排序 、内部归并
- 排序的关键:减少外存信息的读写次数,也就是归并的趟数
-
m
个初始顺串,每次归并
k 个,归并趟数[ logkm] - 减少归并次数:渐少初始顺串或增大每次归并个数
置换选择排序
算法处理过程:输入文件-输入缓冲区-内存-输出缓冲区-输出文件
当输入缓冲区空了的时候才从外部文件中一次性读取数据,当输出缓冲区满了的时候才一次性向外部文件中写数据。置换选择排序
- 读取M个记录到RAM中
- 建立最小值堆
- 在堆变成空之前,循环以下操作
- 每次输出一个堆顶到输出缓冲区
- 再比较输入缓冲区一个记录是否比刚刚输出的堆顶更大,如果是,将这个数据读入并放在堆顶,如果不是,把这个数据放在堆尾,堆的长度减1
- 重建堆
- 输出一个顺串
- 再读取新的 M 个数据到RAM中
template<class T>
void replacementSelection(T* A,int n,const char *in,const char* out)
{
T mval; //存放最小值
T r; //存放从输入缓存区读入的数据
FILE inputFile; //输入文件句柄
FILE outputFile; //输出文件句柄
Buffer<T> input; //输入缓冲区
Beffer<T> output; //输出缓冲区
initFiles(inputFile,outputFile,in,out); //初始化输入输出文件
MinHeap<T> H(A,n); //建立最小值堆
initMinHeapArry(inputFile,n,A); //从输入文件读取n个数据并初始化最小值堆
initInputBuffer(input,inputFile); //初始化输入缓冲区,读入一部分数据
for(int last = n - 1;last >= 0;)
{
mval = H.heapArray[0]; //获得堆顶最小值
sendToOutputBuffer(input,output,inputFile,outputFile,mval); //把mval送到输出缓冲区
input.read(r); //从输入缓冲区读取一个数据
if(!less(r,mval){
H.heapArray[0] = r; //如果r大于mval,r放到堆顶
}
else{
H.heapArray[0] = H.heapArray[last];
H.heapArray[last] = r;
H.setSize(last);
last --;
}
last --;
if(last != 0)
H.SiftDown(0); //重建堆
}
endUp(output,inputFile,outputFile); //处理输出缓冲区,关闭输入输出文件
}
用这个方法得到的顺串的长度是不确定的,最短是
二路外排序
过程
- 把数据文件划分成若干段
- 用内排序方法将各段排序,变成顺串
- 逐躺合并顺串,直到变成一个顺串
策略
- 创建尽可能大的初始顺串
- 把初始顺串长度视为权,利用Huffman方法优化归并
多路归并
- k路归并 每次将
k
个顺串合并排序成一个顺串。如果共有
m 个顺串,需要的归并趟数为 logkm - 选择树 一种完全二叉树,多路归并中用到的数据类型,分为赢者树和败者树。
用赢者树多路归并
赢者树示意图
赢者树类的实现
template<class T>
class WinnerTree(){
private:
int MaxSize; //最大选手数
int n; //当前选手数
int LowExt; //最底层外部结点数
int offset; //最底层之上的结点数
int *B; //赢者树存储数组
int *L; //叶结点数组
void Play(int p,int lc,int rc,int(*winner)(T A[],int b,int c)); //从内部结点B[p]处开始从右分支向上比赛
public:
WinnerTree(int Maxsize);
~WinnerTree(){delete [] B;}
void Initialize(T A[],int size,int(*winner)(T A[],int b, int c)); //初始化赢者树
int Winner(); //返回赢者索引
void RePlay(int i,int(*winner)(T A[],int b, int c)); //外部L[i]改变之后重建赢者树
};
- 用数组实现赢者树,并且区分内部结点和叶结点
- 选手用叶结点表示,每场比赛的结果(赢者的索引)用内部结点记录
- 一个选手的分值改变(下一个记录),只需要改变这个结点到根节点经过路径上的结点。
用败者树多路归并
- 比赛的过程和赢者树一样
- 关键的区别就是父结点记录的是败者的索引
- 根结点处添加一个结点记录胜者
- 优点是方便重构:重构时胜者只需要和上一级的父结点比赛(赢者树还要找兄弟结点比赛)
-
k
个顺串,生成长度
n 的文件,赢者树的时间复杂度 O(kn) ,败者树 O(nlogk)
主要参考Wang Tengjiao课件