|
|
ru.algorithms- RU.ALGORITHMS ---------------------------------------------------------------- From : Ilia Kantor 2:5020/1815.6 28 Mar 2002 22:13:56 To : All Subject : SFar 4/4 -------------------------------------------------------------------------------- === Cut === * Выбрать запись с наименьшим ключом, т.е. с ключом, значение которого >= значения ключа последней прочитанной записи. * Если все "старые" ключи меньше последнего ключа, то мы достигли конца отрезка. Выбираем запись с наименьшим ключом в качестве первого элемента следующего отрезка. * Записываем выбранную запись. * Заменяем выбранную и записанную запись на новую из входного файла. Hа следующей таблице выбор с замещением иллюстрируются для совсем маленького файла. Hачало файла - справа. Чтобы упростить пример, считается, что в буфер помещается всего лишь 2 записи. Конечно, в реальных задачах в буфер помещаются тысячи записей. Мы загружаем буфер на шаге В и записываем в выходной файл запись с наименьшим номером >= 6 на шаге D. Ею оказалась запись с ключом 7. Теперь мы заменяем ее на новую запись из входного файла - с ключом 4. Процесс продолжается до шага F, где мы оказывается, что последний записанный ключ равен 8 и все ключи меньше 8. В этот момент мы заканчиваем формирование текущего отрезка и начинаем формирование следующего. Шаг Вход Буфер Выход A 5-3-4-8-6-7 B 5-3-4-8 6-7 C 5-3-4 8-7 6 D 5-3 8-4 7-6 E 5 3-4 8-7-6 F 5-4 3 | 8-7-6 G 5 4-3 | 8-7-6 H 5-4-3 | 8-7-6 Обратите внимание мы храним записи в буфере до тех пор, пока не наступит время записать их в выходной файл. Если вход случаен, средняя длина отрезков равна примерно удвоенной длине буфера. Однако, если данные хоть как-то упорядочены, отрезки могут быть очень длинными. Вот почему этот метод, вообще говоря, более эффективен промежуточных, частичных сортировок. Прочитав из входного файла очередную запись, мы ищем наименьший ключ, который >= последнего считанного. При этом мы, конечно, можем просто сканировать записи в буфере. Однако, если таких записей тысячи, время поиска может оказаться недопустимо большим. Если на этом этапе использовать двоичные деревья, нам понадобится всего лишь log n сравнений. Реализация В реализации внешней сортировки на ANSI-C функция makeRuns вызывает readRec для чтения очередной записи. В функции readRec используется выбор с замещением (с двоичными деревьями) для получения нужной записи, а makeRuns распределяет записи согласно ряду Фибоначчи. Если количество отрезков оказывается вне последовательности Фибоначчи, в начало каждого файла добавляются пустые отрезки(холостые серии). Затем вызывается функция mergeSort, которая производит многофазное слияние отрезков. ----------------------------------------------------------- /* внешняя сортировка */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* темплейт для временных файлов (формат 8.3) */ #define FNAME "_sort%03d.dat" #define LNAME 13 /* операторы сравнения */ #define compLT(x,y) (x < y) #define compGT(x,y) (x > y) /* определяем сортируемые записи */ #define LRECL 100 typedef long int keyType; typedef struct recTypeTag { keyType key; /* ключ, по которому сортируем */ #if LRECL char data[LRECL-sizeof(keyType)]; /* остальные поля */ #endif } recType; typedef enum {false, true} bool; typedef struct tmpFileTag { FILE *fp; /* указатель на файл */ char name[LNAME]; /* имя файла */ recType rec; /* последняя прочитанная запись */ int dummy; /* номер холостых проходов */ bool eof; /* флаг конца пробега end-of-file */ bool eor; /* флаг конца прохода end-of-run */ bool valid; /* верно, если запись - годная */ int fib; /* идеальное число Фибоначчи */ } tmpFileType; static tmpFileType **file; /* массив информации о временных файлах */ static int nTmpFiles; /* количество временных файлов */ static char *ifName; /* имя входного файла */ static char *ofName; /* имя выходного файла */ static int level; /* уровень проходов */ static int nNodes; /* количество узлов для дерева выбора */ void deleteTmpFiles(void) { int i; /* удалить сливаемые файлы и освободить ресурсы */ if (file) { for (i = 0; i < nTmpFiles; i++) { if (file[i]) { if (file[i]->fp) fclose(file[i]->fp); if (*file[i]->name) remove(file[i]->name); free (file[i]); } } free (file); } } void termTmpFiles(int rc) { /* очистить файлы */ remove(ofName); if (rc == 0) { int fileT; /* file[T] содержит результаты */ fileT = nTmpFiles - 1; fclose(file[fileT]->fp); file[fileT]->fp = NULL; if (rename(file[fileT]->name, ofName)) { perror("io1"); deleteTmpFiles(); exit(1); } *file[fileT]->name = 0; } deleteTmpFiles(); } void cleanExit(int rc) { /* очистить временные файлы и выйти */ termTmpFiles(rc); exit(rc); } void *safeMalloc(size_t size) { void *p; /* Безопасно выделить память и инициализоваться */ if ((p = calloc(1, size)) == NULL) { printf("error: malloc failed, size = %d\n", size); cleanExit(1); } return p; } void initTmpFiles(void) { int i; tmpFileType *fileInfo; /* инициализовать файлы для слияния */ if (nTmpFiles < 3) nTmpFiles = 3; file = safeMalloc(nTmpFiles * sizeof(tmpFileType*)); fileInfo = safeMalloc(nTmpFiles * sizeof(tmpFileType)); for (i = 0; i < nTmpFiles; i++) { file[i] = fileInfo + i; sprintf(file[i]->name, FNAME, i); if ((file[i]->fp = fopen(file[i]->name, "w+b")) == NULL) { perror("io2"); cleanExit(1); } } } recType *readRec(void) { typedef struct iNodeTag { /* внутренний узел */ struct iNodeTag *parent;/* предок внутреннего узла */ struct eNodeTag *loser; /* внешний проигравший */ } iNodeType; typedef struct eNodeTag { /* внешний узел */ struct iNodeTag *parent;/* предок внешнего узла */ recType rec; /* вводимая запись */ int run; /* номер прохода */ bool valid; /* вводимая запись годна */ } eNodeType; typedef struct nodeTag { iNodeType i; /* внутренний узел */ eNodeType e; /* внешний узел */ } nodeType; static nodeType *node; /* массив узлов дерева выбора */ static eNodeType *win; /* новый победитель */ static FILE *ifp; /* входной файл */ static bool eof; /* верно, если конец входного файла */ static int maxRun; /* максимальное число проходов */ static int curRun; /* номер текущего прохода */ iNodeType *p; /* указатель на внутренние узлы */ static bool lastKeyValid; /* верно, если lastKey годен */ static keyType lastKey; /* последний ключ lastKey записан */ /* Прочитать следующую запись путем выбора с замещением */ /* Проверка на первый выход */ if (node == NULL) { int i; if (nNodes < 2) nNodes = 2; node = safeMalloc(nNodes * sizeof(nodeType)); for (i = 0; i < nNodes; i++) { node[i].i.loser = &node[i].e; node[i].i.parent = &node[i/2].i; node[i].e.parent = &node[(nNodes + i)/2].i; node[i].e.run = 0; node[i].e.valid = false; } win = &node[0].e; lastKeyValid = false; if ((ifp = fopen(ifName, "rb")) == NULL) { printf("error: file %s, unable to open\n", ifName); cleanExit(1); } } while (1) { /* заместить предыдущего победителя новой записью */ if (!eof) { if (fread(&win->rec, sizeof(recType), 1, ifp) == 1) { if ((!lastKeyValid || compLT(win->rec.key, lastKey)) && (++win->run > maxRun)) maxRun = win->run; win->valid = true; } else if (feof(ifp)) { fclose(ifp); eof = true; win->valid = false; win->run = maxRun + 1; } else { perror("io4"); cleanExit(1); } } else { win->valid = false; win->run = maxRun + 1; } /* привести в порядок предков победителя и проигравшего */ p = win->parent; do { bool swap; swap = false; if (p->loser->run < win->run) { swap = true; } else if (p->loser->run == win->run) { if (p->loser->valid && win->valid) { if (compLT(p->loser->rec.key, win->rec.key)) swap = true; } else { swap = true; } } if (swap) { /* p должно быть победителем */ eNodeType *t; t = p->loser; p->loser = win; win = t; } p = p->parent; } while (p != &node[0].i); /* конец прохода ? */ if (win->run != curRun) { /* win->run = curRun + 1 */ if (win->run > maxRun) { /* конец вывода */ free(node); return NULL; } curRun = win->run; } /* вывести верх дерева */ if (win->run) { lastKey = win->rec.key; lastKeyValid = true; return &win->rec; } } } void makeRuns(void) { recType *win; /* победитель */ int fileT; /* прошлый файл */ int fileP; /* следующий за прошлым файлом */ int j; /* выбираем file[j] */ /* Сделать инициализационные проходы через выбор с замещением. * Проходы напиcаны с использованием распределения Фибоначчи. */ /* инициализовать файловые структуры */ fileT = nTmpFiles - 1; fileP = fileT - 1; for (j = 0; j < fileT; j++) { file[j]->fib = 1; file[j]->dummy = 1; } file[fileT]->fib = 0; file[fileT]->dummy = 0; level = 1; j = 0; win = readRec(); while (win) { bool anyrun; anyrun = false; for (j = 0; win && j <= fileP; j++) { bool run; run = false; if (file[j]->valid) { if (!compLT(win->key, file[j]->rec.key)) { /* добавить к существующему проходу */ run = true; } else if (file[j]->dummy) { /* начать новый проход */ file[j]->dummy--; run = true; } } else { /* первый проход в файле */ file[j]->dummy--; run = true; } if (run) { anyrun = true; /* полный проход */ while(1) { if (fwrite(win, sizeof(recType), 1, file[j]->fp) != 1) { perror("io3"); cleanExit(1); } file[j]->rec.key = win->key; file[j]->valid = true; if ((win = readRec()) == NULL) break; if (compLT(win->key, file[j]->rec.key)) break; } } } /* Если нет места для проходов - вверх на уровень */ if (!anyrun) { int t; level++; t = file[0]->fib; for (j = 0; j <= fileP; j++) { file[j]->dummy = t + file[j+1]->fib - file[j]->fib; file[j]->fib = t + file[j+1]->fib; } } } } void rewindFile(int j) { /* Идти в начало file[j] и читать первую запись */ file[j]->eor = false; file[j]->eof = false; rewind(file[j]->fp); if (fread(&file[j]->rec, sizeof(recType), 1, file[j]->fp) != 1) { if (feof(file[j]->fp)) { file[j]->eor = true; file[j]->eof = true; } else { perror("io5"); cleanExit(1); } } } void mergeSort(void) { int fileT; int fileP; int j; tmpFileType *tfile; /* Многофазная сортировка слиянием */ fileT = nTmpFiles - 1; fileP = fileT - 1; /* снабдить файлы информацией */ for (j = 0; j < fileT; j++) { rewindFile(j); } /* Каждый проход по циклу сливает один проход */ while (level) { while(1) { bool allDummies; bool anyRuns; /* сканировать на предмет проходов */ allDummies = true; anyRuns = false; for (j = 0; j <= fileP; j++) { if (!file[j]->dummy) { allDummies = false; if (!file[j]->eof) anyRuns = true; } } if (anyRuns) { int k; keyType lastKey; /* слить 1 проход file[0]..file[P] --> в file[T] */ while(1) { /* Каждый проход по циклу записывает 1 запись в file[fileT] */ /* Hайти наименьший ключ */ k = -1; for (j = 0; j <= fileP; j++) { if (file[j]->eor) continue; if (file[j]->dummy) continue; if (k < 0 || (k != j && compGT(file[k]->rec.key, file[j]->rec.key))) k = j; } if (k < 0) break; /* записать record[k] в file[fileT] */ if (fwrite(&file[k]->rec, sizeof(recType), 1, file[fileT]->fp) != 1) { perror("io6"); cleanExit(1); } /* заменить record[k] */ lastKey = file[k]->rec.key; if (fread(&file[k]->rec, sizeof(recType), 1, file[k]->fp) == 1) { /* проверить на предмет конца пробега file[s] */ if (compLT(file[k]->rec.key, lastKey)) file[k]->eor = true; } else if (feof(file[k]->fp)) { file[k]->eof = true; file[k]->eor = true; } else { perror("io7"); cleanExit(1); } } /* Подкорректировкать холостые */ for (j = 0; j <= fileP; j++) { if (file[j]->dummy) file[j]->dummy--; if (!file[j]->eof) file[j]->eor = false; } } else if (allDummies) { for (j = 0; j <= fileP; j++) file[j]->dummy--; file[fileT]->dummy++; } /* конец прохода */ if (file[fileP]->eof && !file[fileP]->dummy) { /* completed a fibonocci-level */ level--; if (!level) { /* готово, file[fileT] содержит данные */ return; } /* fileP истощился, открываем новый такой же */ fclose(file[fileP]->fp); if ((file[fileP]->fp = fopen(file[fileP]->name, "w+b")) == NULL) { perror("io8"); cleanExit(1); } file[fileP]->eof = false; file[fileP]->eor = false; rewindFile(fileT); /* f[0],f[1]...,f[fileT] <-- f[fileT],f[0]...,f[T-1] */ tfile = file[fileT]; memmove(file + 1, file, fileT * sizeof(tmpFileType*)); file[0] = tfile; /* начать новые проходы */ for (j = 0; j <= fileP; j++) if (!file[j]->eof) file[j]->eor = false; } } } } void extSort(void) { initTmpFiles(); makeRuns(); mergeSort(); termTmpFiles(0); } int main(int argc, char *argv[]) { /* командная строка: * * ext ifName ofName nTmpFiles nNodes * * ext in.dat out.dat 5 2000 * читает in.dat, сортирует, используя 5 файлов и 2000 узлов, * выводит в out.dat */ if (argc != 5) { printf("%s ifName ofName nTmpFiles nNodes\n", argv[0]); cleanExit(1); } ifName = argv[1]; ofName = argv[2]; nTmpFiles = atoi(argv[3]); nNodes = atoi(argv[4]); printf("extSort: nFiles=%d, nNodes=%d, lrecl=%d\n", nTmpFiles, nNodes, sizeof(recType)); extSort(); return 0; } Обращаю Ваше внимание на формат входного файла - это HЕ последовательность чисел в обычном формате, а двоичный файл с элементами recType. Для проверки и демонстрации прилагаю две дополнительные программы: Первая создает бинарный файл 'input' размера AMOUNT со случайными ключами. Обратите внимание на соответствие типов keyType и recType - они должны быть одинаковы здесь и в сортировке. #include <stdio.h> #include <stdlib.h> #define AMOUNT 100 // Amount of data generated #define LRECL 100 typedef long int keyType; typedef struct recTypeTag { keyType key; /* ключ, по которому сортируем */ #if LRECL char data[LRECL-sizeof(keyType)]; /* остальные поля */ #endif } recType; void make_random (recType *data, long N) { long int i; for ( i=0; i < N; i++ ) data[i].key=rand()|(rand()<<16); } recType d[AMOUNT]; void main (void) { FILE *f; int i; make_random(d, AMOUNT); f=fopen("input","r+b"); for(i=0;i<AMOUNT;i++) fwrite(&d[i], sizeof(recType), 1, f); fclose(f); } Вторая читает бинарный файл 'output' размера AMOUNT и выдает ключи в порядке появления. Предположения о типах те же. #include <stdio.h> #include <stdlib.h> #define AMOUNT 100 // Amount of data to be read #define LRECL 100 typedef long int keyType; typedef struct recTypeTag { keyType key; /* ключ, по которому сортируем */ #if LRECL char data[LRECL-sizeof(keyType)]; /* остальные поля */ #endif } recType; recType d[AMOUNT]; void main (void) { FILE *f; int i; f=fopen("output","r+b"); for(i=0;i<AMOUNT;i++) { fread(&d[i], sizeof(recType), 1, f); printf("%ld\n", d[i].key); } fclose(f); } ==================================================================== > 14. Есть большое количество слов. Как их отсортировать ? ==================================================================== Способ 1. Во-первых, если хочется сортировать слова, то желательно иметь какое-то отношение сравнения, то есть функцию, которая по двум словам определяет, какое из них 'больше' и обладающую некоторыми свойствами. Пример такой функции: лексикографическое сравнение, то есть сравниваем буквы слова, начиная от первой. Если 1я буква одного слова идет в алфавите перед 1й буквой другого слова, то оно меньше: Даша < Маша. Если буквы одинаковые, то смотрим 2ю букву и далее, при необходимости. Абракадабра < Абрек Если буквы в каком-то из слов кончились, то оно меньше: Брахма < Брахман. При сравнении слов пропускаются значки типа кавычек, апострофов и проч. Дальше - если слова влезают в массив, то можно загрузить его в память и применить имеющуюся почти во всех языках программирования процедуру быстрой сортировки (в Си это qsort() ). Если слова хранятся в большом файле - можно воспользоваться многофазной сортировкой слияния, данной в другом вопросе. Исходник придется немного изменить: во-первых, поменять keyType - тип ключа, по которому сортируем. Это будет слово. А, во-вторых, поменять операторы сравнения #define compLT(x,y) (x < y) #define compGT(x,y) (x > y), например, на функцию сравнения, данную выше. Способ 2. Если слов много, и они невелики по длине, а максимальную длину слова мы знаем _до_ запуска процедуры - можно воспользоваться распределяющей сортировкой. Она может оказаться в разы быстрее. Проблема тут в апострофах и других лишних знаках, которые нужно как-то запоминать и убирать до сортировки и вставлять на место - после (это один из вариантов). ==================================================================== > App. Использованная литература. ==================================================================== При составлении FAQ'а наиболее полно использовалась информация из книги Дональда Кнута (D. Knuth) 'Искусство программирования на ЭВМ', книги Hиколаса Вирта (Niklaus Wirth) 'Алгоритмы + Структуры Данных = Программы' а также 'Краткое руководство по сортировке и поиску' Томаса Hимана (Thomas Niemann), которое можно найти в интернете по адресу http://epaperpress.com/sortsearch/russian/index.html Приношу благодарности всем критиковавшим, а также высказывавшим мудрые идеи и вопросы. === Cut === ъщю До следующих встреч, All ющъ --- GoldEd 3.00.Alpha4+ * Origin: http://algolist.da.ru - Мир Алгоритмов (2:5020/1815.6) Вернуться к списку тем, сортированных по: возрастание даты уменьшение даты тема автор
Архивное /ru.algorithms/39463ca3879d.html, оценка из 5, голосов 10
|