Скачать презентацию Cортировка файлов Лекция 12 Файлы в Си Скачать презентацию Cортировка файлов Лекция 12 Файлы в Си

Л10_Сортировка файлов.pptx

  • Количество слайдов: 47

Cортировка файлов Лекция 12 Cортировка файлов Лекция 12

Файлы в Си (ANSI) Файлом называют способ хранения информации на физическом устройстве. Файл ― Файлы в Си (ANSI) Файлом называют способ хранения информации на физическом устройстве. Файл ― это понятие, которое применимо ко всему : от файла на диске до терминала. Файл представляется потоком байт. В языке Си отсутствуют операторы для работы с файлами. Все необходимые действия выполняются с помощью функций, включенных в стандартную библиотеку . Они позволяют работать с различными устройствами, такими, как диски, принтер, коммуникационные каналы и т. д. В Си существует два типа файлов: текстовые (text) и двоичные (binary).

Текстовые и бинарные файлы Текстовый файл — это файл, содержащий текст, разбитый на строки Текстовые и бинарные файлы Текстовый файл — это файл, содержащий текст, разбитый на строки при помощи некоторого разделяющего символа окончания строки или последовательности в Unix — одиночный символ перевода строки; в Microsoft Windows за символом перевода строки следует знак возврата каретки: 0 x 0 D 0 x 0 A 13 10 в десятичной системе счисления. Двоичный (бинарный) файл — файл, из которого байты считываются и выводятся в «сыром» виде без какого-либо связывания (подстановки). В текстовом файле символ "n" переводится в "rn" при записи в файл. При считывании производится обратная замена: "rn" "n". С бинарными файлами этого не происходит.

Описание файла Логическое имя представляет собой указатель на файл, который используется операционной системой для Описание файла Логическое имя представляет собой указатель на файл, который используется операционной системой для поддержки операций с этим файлом. Оно определяется так: FILE *fp; FILE ― имя типа, описанное в стандартном заголовочном файле , fp ― указатель на файл.

Библиотечные функции, используемые при работе с файлами. Функция открытия файла fopen( ) FILE * Библиотечные функции, используемые при работе с файлами. Функция открытия файла fopen( ) FILE * fopen (const char* name, const char* mode ); (спецификация файла, способ использования файла) В случае удачного открытия файла, функция fopen() возвращает дескриптор файла, иначе – константу NULL, которая определена в файле и эквивалентна 0. Рекомендуется использовать следующий способ открытия файла: if ((fp = fopen("c: \my_prog. txt", "rt")) == NULL) { fprintf(stderr, "Открыть файл не удалосьn"); exit(1); }

Способ использования файла rw - а - r+ - w+ - a+ - rb Способ использования файла rw - а - r+ - w+ - a+ - rb - wb аb - r+b w+b а+b - открыть существующий файл для чтения; создать новый файл для записи (если файл с указанным именем существует, то он будет переписан); дополнить файл (открыть существующий файл для записи информации, начиная с конца файла, или создать файл, если он не существует); открыть существующий файл для чтения и записи; создать новый файл для чтения и записи; дополнить или создать файл с возможностью чтения и записи; открыть двоичный файл для чтения; создать двоичный файл для записи; дополнить двоичный файл; открыть двоичный файл для чтения и записи; создать двоичный файл для чтения и записи; дополнить двоичный файл с предоставлением возможности чтения и записи

Способ использования файла rt wt at - r+t - w+t - a+t - открыть Способ использования файла rt wt at - r+t - w+t - a+t - открыть текстовой файл для чтения; создать текстовый файл для записи; дополнить текстовый файл; открыть текстовой файл для чтения и записи; создать текстовый файл для чтения и записи; дополнить текстовый файл с предоставлением возможности записи и чтения. Строки вида r+b можно записывать и в другой форме: rb+ Если режим t или b не задан, то он определяется значением глобальной переменной _fmode.

Функция fclose() После окончания работы с файлом он должен быть закрыт. Это делается с Функция fclose() После окончания работы с файлом он должен быть закрыт. Это делается с помощью библиотечной функции fclose( ). Прототип: int fclose(FILE *fp); При успешном завершении операции функция fclose( ) возвращает значение ноль. Любое другое значение свидетельствует об ошибке.

Буферизация Функции fopen( ) и fclose( ) работают с файлами с Буферизация Функции fopen( ) и fclose( ) работают с файлами с "буферизацией". Под буферизацией мы понимаем, что вводимые и выводимые данные запоминаются во временной области памяти, называемой буфером. Если буфер заполнился, содержимое его передается в файл (или затирается), и процесс буферизации начинается снова. Одна из основных задач fclose( ) заключается в том, чтобы освободить любые частично заполненные буферы при закрытии файла.

Функция fflush() Если заданный файл открыт для вывода, то содержимое буфера, записывается в него. Функция fflush() Если заданный файл открыт для вывода, то содержимое буфера, записывается в него. Если файл открыт для ввода, то функция fflush очищает содержимое буфера. После вызова функции файл остается открытым. Прототип: int fflush(FILE *fp); Функция fflush возвращает 0, если буфер успешно обновлен. Это же значение возвращается, когда файл открыт только для чтения. В случае возникновения ошибки возвращается значение EOF ( 1). Буферы автоматически обновляются, когда они полны, когда файл закрывается или произошло нормальное окончание работы программы без закрытия файла.

Функция fflush() Например, способ освобождения от нежелательных символов во входном файле: printf( Функция fflush() Например, способ освобождения от нежелательных символов во входном файле: printf("Введите возраст"); scanf("%d", &age); /*получение возраста*/ printf("Введите размер обуви: "); fflush(stdin); scanf("%d", &shoesize); Отмена буферизации канала stdout: setbuf(stdout, NULL); Канал вывода сообщений об ошибках stderr не буферизован, поэтому выдаваемые в него сообщения печатаются немедленно.

Функция fprintf( ) выполняет те же действия, что и функция printf( ), но работает Функция fprintf( ) выполняет те же действия, что и функция printf( ), но работает с файлом. Прототип: int fprintf(FILE *fp, const char *format, . . . ); Возвращаемое значение равно количеству реально выведенных символов. Если при выводе возникла ошибка, возвращается отрицательное число. Пример: fprintf(fp, "%х", а);

Функция fscanf( ) выполняет те же действия, что и функция scanf(), но работает с Функция fscanf( ) выполняет те же действия, что и функция scanf(), но работает с файлом. Прототип: int fscanf(FILE *fp, char *format, . . . ); Возвращает количество считанных параметров. При попытке считывания конца файла возвращается значение EOF. Пример: fscanf(fp, "a = %х", &a);

Пример #include <stdio. h> int main( ) { FILE *fi; int age; fi=fopen( Пример #include int main( ) { FILE *fi; int age; fi=fopen("input. txt", "r"); /* считывание */ fscanf(fi, "%d", &age); fclose(fi); fi=fopen("output. txt", "a"); /*дополнение*/ fprintf(fi, "Data is %d. n", age); fclose(fi); return 0; }

Функция feof( ) определяет конец файла при чтении двоичных данных. Прототип: int feof(FILE *fp); Функция feof( ) определяет конец файла при чтении двоичных данных. Прототип: int feof(FILE *fp); fp - указатель на файл, возвращенный функцией fopen( ). При достижении конца файла возвращается ненулевое значение, в противном случае возвращается 0. while (!feof(f)) fscanf (f, “%d”, &x); Если в конце последовательности стоит пробел, то конец файла еще не достигнут, но очередное число прочитать невозможно.

Функция fputc() Функция fputc( ) записывает символ в файл. Прототип: int fputc(int с, FILE Функция fputc() Функция fputc( ) записывает символ в файл. Прототип: int fputc(int с, FILE *fp); fp - указатель на файл, возвращенный функцией fopen(), с - символ. При успешном завершении fputc() возвращает записанный символ, в противном случае возвращается константа EOF. int ch; . . fputc(ch, fo);

Функция fgetc() Прототип: int fgetc(FILE *fp); fp - указатель на файл, возвращенный функцией fopen( Функция fgetc() Прототип: int fgetc(FILE *fp); fp - указатель на файл, возвращенный функцией fopen( ). Эта функция возвращает прочитанный из файла символ. Соответствующее значение имеет тип int, но старший байт равен нулю. Если достигнут конец файла, то fgetc( ) возвращает значение ЕОF. int ch; . . . ch=fgetc(fi); читает символ из файла, на который указывает fi

Функция fputs( ) записывает строку символов в файл. Она отличается от функции puts( ) Функция fputs( ) записывает строку символов в файл. Она отличается от функции puts( ) только тем, что в качестве второго параметра должен быть записан указатель на переменную файлового типа. Символ конца строки (‘') не записывается. Прототип: int fputs(const char *str, FILE *fo); Например: l = fputs("Ехаmple", fo); При успешном выполнении функция fputs() возвращает неотрицательное значение (последний записанный символ), а при неудачном — значение EOF. В отличие от puts функция fputs( ) не добавляет в конец строки символ перехода на новую строку.

Функция fgets( ) читает строку символов из файла. Она отличается от функции gets( ) Функция fgets( ) читает строку символов из файла. Она отличается от функции gets( ) тем, что имеет три параметра, третий - указатель на переменную файлового типа. Прототип: char *fgets(char *str, int n, FILE *fi); Строка считывается целиком, если ее длина не превышает указанного числа символов, в противном случае функция возвращает только заданное число символов. Пример: … fgets(string, n, fi); Функция возвращает указатель на строку string при успешном завершении и константу NULL в случае ошибки либо достижения конца файла.

Пример /* Программа считывает файл строка за строкой */ #include <stdio. h> #define MAX Пример /* Программа считывает файл строка за строкой */ #include #define MAX 80 int main( ) { FILE *f 1; char string[MAX]; f 1=fopen("input. txt", "r"); while (fgets(string, MAX, f 1) != NULL) puts(string); return 0; } Функция прекращает работу после считывания символа новой строки или после считывания символов общим числом MAX-1, в зависимости от того, что произойдет раньше. В любом случае символ '' добавляется в самый конец строки. Разница между gets( ) и fgets( ) заключается в том, что gets( ) заменяет символ новой строки на '', в то время как fgets( ) сохраняет символ новой строки.

Функция remove( ) удаляет файл. Прототип: int remove(const char *file_name); file_name - указатель на Функция remove( ) удаляет файл. Прототип: int remove(const char *file_name); file_name - указатель на строку со спецификацией файла. При успешном завершении возвращается ноль, в противном случае возвращается ненулевое значение.

Функция rewind( ) устанавливает указатель текущей позиции в начало файла. Прототип: void rewind(FILE *fp); Функция rewind( ) устанавливает указатель текущей позиции в начало файла. Прототип: void rewind(FILE *fp);

Функция fseek( ) позволяет выполнять чтение и запись с произвольным доступом. Прототип: int fseek(FILE Функция fseek( ) позволяет выполнять чтение и запись с произвольным доступом. Прототип: int fseek(FILE *fp, long count, int access); fp - указатель на файл, возвращенный функцией fopen( ), count - номер байта относительно заданной начальной позиции, начиная с которого будет выполняться операция, access - способ задания начальной позиции. Переменная access может принимать следующие значения: SEEK-SET (0) - начальная позиция задана в начале файла; SEEK-CUR (1) - начальная позиция считается текущей; SEEK-END (2) - начальная позиция задана в конце файла. При успешном завершении возвращается ноль, при ошибке - ненулевое значение Вызов fseek(fp, OL, 0) означает, что мы идем в файл, на который ссылается указатель fp, и находим байт, отстоящий на 0 байт от начала, т. е. первый байт.

Функция fread( ) предназначена для чтения блоков данных из потока. Прототип: unsigned fread(void *ptr, Функция fread( ) предназначена для чтения блоков данных из потока. Прототип: unsigned fread(void *ptr, unsigned size, unsigned n, FILE * fp); Она читает n элементов данных, длиной size байт каждый, в блок памяти, на который указывает указатель ptr, Общее число прочитанных байтов равно произведению n*size. При успешном завершении функция fread( ) возвращает число прочитанных элементов данных, при ошибке - 0.

Функция fwrite( ) предназначена для записи в файл блоков данных. Прототип: unsigned fwrite(void *ptr, Функция fwrite( ) предназначена для записи в файл блоков данных. Прототип: unsigned fwrite(void *ptr, unsigned size, unsigned n, FILE *fp); Она добавляет n элементов данных, длиной size байт каждый, в заданный выходной файл fp. При успешном завершении операции функция fwrite() возвращает число записанных элементов данных, при ошибке ― неверное число элементов данных.

Пять стандартных файлов stdin - для ввода данных из стандартного входного потока (по умолчанию Пять стандартных файлов stdin - для ввода данных из стандартного входного потока (по умолчанию - c клавиатуры); stdout - для вывода данных в стандартный выходной поток (по умолчанию - на экран дисплея); stderr - файл для вывода сообщений об ошибках (всегда связан с экраном дисплея); stdprn - для вывода данных на принтер; stdaus - для ввода и вывода данных в коммуникационный канал.

Слияние последовательностей Под слиянием будем понимать объединение двух или более упорядоченных последовательностей в одну Слияние последовательностей Под слиянием будем понимать объединение двух или более упорядоченных последовательностей в одну упорядоченную. Это можно сделать следующим образом: сравнить наименьшие элементы из упорядоченных последовательностей и наименьший из них перенести в готовую последовательность. Далее снова сравнить начала последовательностей и наименьший из этих элементов добавить в готовую последовательность и т. д. Как только одна из последовательностей закончится, она исключается из рассмотрения. Когда остается только одна последовательность, ее «хвост» можно просто переместить в готовую.

Объединим две последовательности в третью (позиция считывания отмечена чертой) 8 38 40 51 75 Объединим две последовательности в третью (позиция считывания отмечена чертой) 8 38 40 51 75 1 15 63 89 101 107 Сравним первые элементы отсортированных последовательностей, наименьший из них запишем в выходную последовательность: 8 38 40 51 75 1 15 63 89 101 107 1 Следующий шаг 8 38 40 51 75 1 15 63 89 101 107 1 8

Этот процесс продолжится до тех пор, пока все элементы первой и второй последовательности не Этот процесс продолжится до тех пор, пока все элементы первой и второй последовательности не будут переписаны в третью в заданном порядке. В результате получим отсортированную по возрастанию последовательность: 1 8 15 38 40 51 63 75 89 101 107

Метод слияния — один из самых первых методов, который естественным образом можно применить к Метод слияния — один из самых первых методов, который естественным образом можно применить к сортировке файлов, а именно два отсортированных файла слить в третий отсортированный. Данный метод слияния был предложен фон Нейманом в 1945 г. и предназначался именно для сортировки файлов.

Джон фон Нейман англ. John von Neumann Джон фон Нейман в 1940 -е Дата Джон фон Нейман англ. John von Neumann Джон фон Нейман в 1940 -е Дата рождения: 28 декабря 1903 Место рождения: Будапешт Дата смерти: 8 февраля 1957 (53 года) Место смерти: Вашингтон Научная сфера: математик, физик

 Сортировка массива простым двухпутевым слиянием Идея метода сортировки слиянием такова: разделим входную последовательность Сортировка массива простым двухпутевым слиянием Идея метода сортировки слиянием такова: разделим входную последовательность на две части, отсортируем каждую из них по отдельности, результаты сольем, как описано выше. Исходная задача сводится к двум аналогичным задачам с меньшим объемом данных, применим рекурсию: — на фазе рекурсивного спуска каждая из образующихся последовательностей делится на две части до тех пор, пока не образуются последовательности длины 0 или 1, которые сортировать не надо; — на фазе возврата из рекурсии пары уже отсортированных подпоследовательностей сливаются.

 Пример 13 86 71 52 99 21 37 45 66 4 75 80 Пример 13 86 71 52 99 21 37 45 66 4 75 80 31 4 разделение 3 разделение 2 разделение 1 разделение

Слияние элементарных последовательностей начинается из глубины рекурсии и дает следующие подпоследовательности: 86 71 99 Слияние элементарных последовательностей начинается из глубины рекурсии и дает следующие подпоследовательности: 86 71 99 21 45 71 86 54 21 99 37 46 4 75 80 31 слияние 4 ур: 13 слияние 3 ур: 13 71 86 21 54 99 45 66 4 75 37 45 66 4 31 31 75 80 80 слияние 2 ур: 13 21 54 71 86 99 4 31 37 45 66 75 80 слияние 1 ур: 4 13 21 31 37 45 54 66 71 75 80 86 99

Следующая пара функций реализует сортировку слиянием для массивов: void merge (key al[], int lenl, Следующая пара функций реализует сортировку слиянием для массивов: void merge (key al[], int lenl, key a 2[], int len 2, key ar[]) /* Слияние отсортированных массивов al длины lenl и а 2 длины len 2 в массив аr */ { int i=0, j=0, k=0; key x; while ((i

static key aw[N]; /* вспомогательный глобальный массив для слияния */ void sort_merging (key a[], static key aw[N]; /* вспомогательный глобальный массив для слияния */ void sort_merging (key a[], int L, int R) /* L, R - границы сортируемой части массива а */ { int i, M; М = (L+R)/2; if (L < M) sort_merging(a, L, M); if(M+l < R) sort_merging(a, M+l, R); /* слияние частей в aw */ merge(&a[L], M-L+l, &a[M+l], R-M, &aw[L]); /* копирование в исход. фрагмент */ for (i=L; i<=R; i++) a[i] = aw[i]; }`

Анализ Для слияния двух отсортированных частей необходим третий массиврезультат aw. Однако, поскольку упорядоченные данные Анализ Для слияния двух отсортированных частей необходим третий массиврезультат aw. Однако, поскольку упорядоченные данные должны накапливаться для последующего слияния в исходном массиве, приходится дополнительно переписывать результат на место исходной подпоследовательности. Нам было бы достаточно иметь в качестве aw локальный рабочий массив длины R – L + 1, но в Си невозможно описать массив переменной длины (без обращения к более медленным средствам динамической памяти). Введение же локального массива максимальной длины N, используемого лишь частично привело бы к затратам памяти до N log 2 N записей, так как на каждом из log 2 N уровней рекурсии в памяти хранился бы отдельный рабочий массив длины N.

Использование глобального массива приводит к оценке затрат памяти в данном методе ~ 2 N Использование глобального массива приводит к оценке затрат памяти в данном методе ~ 2 N записей и в данном случае организовано корректно: ни один элемент массива aw, записанный в функции слияния, не может быть изменен до переписи его в массив а в функции сортировки, а после переписи он становится не нужен.

Сортировка файла простым двухпутевым слиянием Пусть теперь вместо массива а дан файл f, который Сортировка файла простым двухпутевым слиянием Пусть теперь вместо массива а дан файл f, который нужно отсортировать. Заметим, что в функции слияния merge доступ к элементам частей массива и к массиву-результату исключительно последовательный: индексы-указатели текущего доступа сдвигаются только на единицу вперед, без возвратов и скачков. Поэтому операции вида a[i + +] для массива можно заменить на типовые операции чтения и записи элемента файла с продвижением к позиции следующего элемента.

При разделении массива нам не приходилось явно отводить память под образуемые части и переписывать При разделении массива нам не приходилось явно отводить память под образуемые части и переписывать в них элементы. Вместо этого мы устанавливали и перемещали два указателя. Однако файл читать можно только по одному указателю, поэтому разделяемые части придется явно переписывать в отдельные файлы. Таким образом, нужна процедура split, выполняющая физическое разделение.

Для разделения массива пополам мы пользовались знанием его длины. Для файла число его записей Для разделения массива пополам мы пользовались знанием его длины. Для файла число его записей не всегда известно и определение длины требует дополнительного холостого считывания. Это препятствие мы устраним так: поскольку разделяются еще неотсортированные файлы, разделение можно организовать подобно тому, как сдается колода карт на двух игроков: элементы разделяемого файла по мере считывания переписываются в два новых файла поочередно. Концом «раздачи» является достижение конца входного файла, при этом количество элементов в новых файлах отличается максимум на единицу, что и требуется.

1 разделение 2 разделение 3 разделение 4 разделение 13 86 71 52 99 21 1 разделение 2 разделение 3 разделение 4 разделение 13 86 71 52 99 21 37 45 66 4 75 80 31 13 71 99 37 66 75 31 86 52 21 45 4 80 13 99 66 31 71 37 75 86 21 4 52 45 80 13 66 99 31 71 75 37 86 4 21 52 80 45

Следующие процедуры реализуют все описанные модификации. Мы пользуемся стандартными файловыми функциями библиотеки Си, в Следующие процедуры реализуют все описанные модификации. Мы пользуемся стандартными файловыми функциями библиотеки Си, в том числе средствами создания промежуточных рабочих файлов, для которых не нужно беспокоиться о выборе уникальных имен. /* упрощенные вызовы файловых функций С */ #define fget(f, x) fread(&x, sizeof(x), 1, f) #define fput(f, x) fwrite(&x, sizeof(x), 1, f)

int split (FILE *f, FILE *fl, FILE * f 2) /* Разделение f: перепись int split (FILE *f, FILE *fl, FILE * f 2) /* Разделение f: перепись элементов нечетных позиций в f 1, четных - в f 2 */ { key x; int n=0; /* счетчик длины файла */ rewind (f); /* возврат к началу разделяемого файла */ fget (f, x); while (!feof(f)) { /* (feof срабатывает ПОСЛЕ попытки чтения!) */ fput (f 1, х); fget (f, x); if (!feof(f)) { fput (f 2, x); fget (f, x); } n++; } return n>l; /* 0 (длина 0 или 1) сигнализирует о прекращении разделения */ }

void merge (FILE *fl, FILE *f 2, FILE *fr) /* Слияние fl и f void merge (FILE *fl, FILE *f 2, FILE *fr) /* Слияние fl и f 2 в fr */ { key xl, x 2; rewind(f 1); /* перемотка к началу всех файлов */ rewind(f 2); rewind(fr); fget(fl, xl); fget(f 2, x 2); while (!feof(fl) || !feof(f 2)) { if (feof(fl)) { fput(fr, x 2); fget(f 2, x 2); } else if (feof(f 2)) { fput(fr, xl); fget(fl, xl); } else if (xl

void sort_merge (FILE *f) /* Главная процедура сортировки, входной файл должен быть открыт */ void sort_merge (FILE *f) /* Главная процедура сортировки, входной файл должен быть открыт */ { /* создание временных файлов для частей */ FILE *fl = tmpfile(), *f 2 = tmpfile(); /* разделение на фазе спуска в рекурсию */ if (split(f, fl, f 2)) { sort_merge(f 1); sort_merge(f 2); } /* слияние на фазе возврата из рекурсии */ merge(f 1, f 2, f); /* закрытие и удаление рабочих файлов */ fclose(f 1); fclose(f 2); }

int main () { int key x; FILE * f = fopen( int main () { int key x; FILE * f = fopen("inputfile", "r+b"); /* открытие файла */ sort_merge(f); /* сортировка открытого файла */ fclose(f); /* закрытие выходного файла */ return 0; }