Внешние сортировки
Основным понятием при использовании внешней сортировки является понятие серии. Серия (упорядоченный отрезок) – это последовательность элементов, которая упорядочена по ключу. Количество элементов в серии называется длиной серии. Серия, состоящая из одного элемента, упорядочена всегда. Последняя серия может иметь длину меньшую, чем остальные серии файлов. Максимальное количество серий в файле N (все элементы не упорядочены). Минимальное количество серий одна (все элементы упорядочены). В основе большинства методов внешних сортировок лежит процедура слияния и процедура распределения. Слияние – это процесс объединения двух (или более) упорядоченных серий в одну упорядоченную последовательность при помощи циклического выбора элементов, доступных в данный момент. Распределение – это процесс разделения упорядоченных серий на два и несколько вспомогательных файла. Фаза – это действия по однократной обработке всей последовательности элементов. Двухфазная сортировка – это сортировка, в которой отдельно реализуется две фазы: распределение и слияние. Однофазная сортировка – это сортировка, в которой объединены фазы распределения и слияния в одну. Двухпутевым слиянием называется сортировка, в которой данные распределяются на два вспомогательных файла. Многопутевым слиянием называется сортировка, в которой данные распределяются на N (N > 2) вспомогательных файлов.
Прямым слиянием
Одна из сортировок на основе слияния называется простым слиянием.
Алгоритм сортировки простым слияния является простейшим алгоритмом внешней сортировки, основанный на процедуре слияния серией.
В данном алгоритме длина серий фиксируется на каждом шаге. В исходном файле все серии имеют длину 1, после первого шага она равна 2, после второго – 4, после третьего – 8, после k -го шага – 2k.
Алгоритм сортировки простым слиянием
Шаг 1. Исходный файл f разбивается на два вспомогательных файла f1 и f2.
Шаг 2. Вспомогательные файлы f1 и f2 сливаются в файл f, при этом одиночные элементы образуют упорядоченные пары.
Шаг 3. Полученный файл f вновь обрабатывается, как указано в шагах 1 и 2. При этом упорядоченные пары переходят в упорядоченные четверки.
Шаг 4. Повторяя шаги, сливаем четверки в восьмерки и т.д., каждый раз удваивая длину слитых последовательностей до тех пор, пока не будет упорядочен целиком весь файл ( рис. 43.1).
После выполнения i проходов получаем два файла, состоящих из серий длины 2i. Окончание процесса происходит при выполнении условия 2i>=n. Следовательно, процесс сортировки простым слиянием требует порядка O(log n) проходов по данным.
Признаками конца сортировки простым слиянием являются следующие условия:
длина серии не меньше количества элементов в файле (определяется после фазы слияния);
количество серий равно 1 (определяется на фазе слияния).
при однофазной сортировке второй по счету вспомогательный файл после распределения серий остался пустым.
Реализаия в файле sort.h
#pragma once
#include <fstream>
#include <iostream>
#include <string>
using std::fstream;
using std::string;
using std::ios;
using std::cout;
using std::endl;
template<class T>
class DOSort{
fstream file, file1, file2;
//Имя файла
string fileName;
//Кол-во
int amountObj;
//Длина серии
int srLen;
public:
//Конструкторы
DOSort(): amountObj(0), srLen(0), fileName("") {}
DOSort(string fName) {reset(fName);}
///////////////////////////////////
//Назначить новый файл(сброс)
void reset(string fName);
//Выполнить сортировку (точка входа)
void run();
private:
//Подсчёт значений
void countObj();
//Разделение на файлы
void splitFile();
//Объединение в файл
void mergeFiles();
///////////////////////////////////
//Базовая сортировка в mergeFiles()
void basicSort(T &obj1, T &obj2);
///////////////////////////////////
//Ошибка
void error() {exit(0);}
//Закрытие файлов
void closeFiles();
//Удаление временных файлов
void removeTmpFiles();
};
///////////////////////////////////
// Реализация
///////////////////////////////////
template<class T>
void DOSort<T>::reset(string fName)
{
fileName = fName;
amountObj = 0;
srLen = 1;
}
///////////////////////////////////
template<class T>
void DOSort<T>::run()
{
//Счёт объектов в файле
countObj();
//Выполнять пока длина серии меньше кол-во объектов
while(srLen < amountObj){
//Разделение на 2 файла
splitFile();
//Объединение в файл
mergeFiles();
//Увеличение длины серии
srLen*=2;
}
//Удаление временных файлов
removeTmpFiles();
}
///////////////////////////////////
template<class T>
void DOSort<T>::countObj()
{
//Для перебора элементов
T temp;
//Открытие файла
file.open(fileName, ios::in);
if (!file.is_open())
error();
//Подсчёт элементов
while(!file.eof()){
file >> temp;
amountObj++;
}
//Закрытие исх файла
file.close();
}
///////////////////////////////////
template<class T>
void DOSort<T>::splitFile()
{
//Открытие файлов
file.open(fileName, ios::in);
file1.open(fileName + ".part1", ios::out);
file2.open(fileName + ".part2", ios::out);
//Проверка на открытие файлов
if (!file || !file1 || !file2)
error();
//Объект для копирования
T obj;
//Пока не конец файла
while(!file.eof()){
//Файл 1
for(int i = 0; i < srLen && !file.eof(); i++){
file >> obj;
if (file)
file1 << obj << " ";
}
//Файл 2
for(int i = 0; i < srLen && !file.eof(); i++){
file >> obj;
if (file)
file2 << obj << " ";
}
}
//Закрытие файлов
closeFiles();
}
///////////////////////////////////
template<class T>
void DOSort<T>::mergeFiles()
{
//Открытие файлов
file.open(fileName, ios::out);
file1.open(fileName + ".part1", ios::in);
file2.open(fileName + ".part2", ios::in);
//Проверка на открытие файлов
if (!file || !file1 || !file2)
error();
//tmp объекты
T obj1, obj2;
//1-ое извлечение
file1 >> obj1;
file2 >> obj2;
//Пока не конец файла
while(!file1.eof() && !file2.eof())
basicSort(obj1, obj2);
//Копирование остатков file1 в file
while(!file1.eof()){
file << obj1 << " ";
file1 >> obj1;
}
//Копирование остатков file2 в file
while(!file2.eof()){
file << obj2 << " ";
file2 >> obj2;
}
//Закрытие файлов
closeFiles();
}
///////////////////////////////////
template<class T>
void DOSort<T>::basicSort(T &obj1, T &obj2)
{
int i = 0, j = 0;
//Распределение
while(i < srLen && j < srLen && !file1.eof() && !file2.eof()){
if (obj1 < obj2){
file << obj1 << " ";
file1 >> obj1;
i++;
}else{
file << obj2 << " ";
file2 >> obj2;
j++;
}
}
//Доп. копирование остатков fil1 в file
while (i < srLen && !file1.eof()){
file << obj1 << " ";
file1 >> obj1;
i++;
}
//Доп. копирование остатков file2 в file
while (j < srLen && !file2.eof()){
file << obj2 << " ";
file2 >> obj2;
j++;
}
}
///////////////////////////////////
template<class T>
void DOSort<T>::closeFiles()
{
file.close();
file1.close();
file2.close();
}
///////////////////////////////////
template<class T>
void DOSort<T>::removeTmpFiles()
{
remove(string(fileName + ".part1").c_str());
remove(string(fileName + ".part2").c_str());
}
