
- •Введение
- •1. ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
- •1.1. Переменные и базовые типы данных
- •1.2. Операции и выражения
- •1.3. Символические константы
- •1.5. Несколько слов о функции main()
- •2. ВВОД И ВЫВОД В СИ
- •2.2. Форматный ввод-вывод
- •3. ЦИКЛЫ И ОПЕРАТОРЫ СРАВНЕНИЯ
- •3.1. Условный оператор
- •3.2. Оператор выбора switch
- •3.3. Операторы цикла
- •3.4. Операторы break и continue
- •3.5. Примеры
- •3.6. Вычисление значений элементарных функций
- •3.7. Задачи
- •4. ОБРАБОТКА ПОСЛЕДОВАТЕЛЬНОСТЕЙ
- •4.1. Примеры
- •4.2. Задачи
- •5. ОДНОМЕРНЫЕ МАССИВЫ
- •5.1. Начальные сведения о массивах
- •5.2. Примеры работы с массивами
- •5.3. Задачи
- •6. МНОГОМЕРНЫЕ МАССИВЫ
- •6.1. Определение и инициализация двумерных массивов
- •6.2. Примеры с двумерными массивами
- •6.3. Задачи
- •7. УКАЗАТЕЛИ И МАССИВЫ
- •7.1. Указатели и адреса
- •7.2. Указатели и аргументы функций
- •7.3. Указатели и массивы
- •7.4. Операции с указателями
- •7.5. Указатели с типом void
- •7.6. Модификатор const
- •7.7. Массивы переменного размера
- •7.8. Массивы указателей
- •7.9. Двумерные массивы переменного размера
- •8. СИМВОЛЫ И СТРОКИ
- •8.1. Представление символьной информации в ЭВМ
- •8.2. Библиотека обработки символов
- •8.3. Строки в языке Си
- •8.4. Функции обработки строк
- •8.5. Функции преобразования строк
- •8.6. Примеры работы со строками
- •8.7. Разбиение строки на лексемы
- •8.8. Задачи
- •9. СТРУКТУРЫ
- •9.1. Основные сведения о структурах
- •9.2. Объединения
- •10. ДИРЕКТИВЫ ПРЕПРОЦЕССОРА
- •10.1. Директива #include
- •10.2. Директива #define
- •10.3. Директива #undef
- •10.4. Условная компиляция
- •11. ФУНКЦИИ
- •11.1. Основные сведения о функциях
- •11.2. Прототипы функций
- •11.3. Классы памяти
- •11.4. Указатели на функции
- •11.5. Рекурсия
- •11.6. Примеры с использованием рекурсии
- •11.7. Метод «разделяй и властвуй»
- •11.8. Задачи на применение рекурсии
- •12. РАБОТА С БИТАМИ ПАМЯТИ
- •12.1. Битовые операции
- •12.2. Примеры с использованием битовых операций
- •12.3. Задачи
- •13. РАБОТА С ФАЙЛАМИ
- •13.1. Файлы и потоки
- •13.2. Текстовые файлы
- •13.3. Двоичные файлы
- •13.4. Шифрование файлов
- •13.5. Задачи на текстовые файлы
- •13.6. Задачи на двоичные файлы
- •14. СТРУКТУРЫ ДАННЫХ
- •14.1. Односвязные списки
- •14.2. Примеры работы с односвязными списками
- •14.3. Задачи на односвязные списки
- •14.4. Стеки, очереди
- •14.5. Задачи на стеки и очереди
- •14.6. Двусвязные списки
- •14.7. Задачи на двусвязные списки
- •14.8. Бинарные деревья
- •14.9. Примеры с использованием бинарных деревьев
- •14.10. Задачи на бинарные деревья
- •Приложение 1. АЛГОРИТМЫ ПОИСКА
- •1. Линейный поиск
- •2. Поиск с барьером
- •3. Двоичный поиск
- •Приложение 2. АЛГОРИТМЫ СОРТИРОВКИ
- •Несколько слов о сложности алгоритмов
- •1. Метод прямого выбора
- •2. Метод прямого включения
- •3. Пузырьковая сортировка
- •4. Шейкерная сортировка
- •5. Быстрая сортировка
- •6. Сортировка подсчетом
- •Приложение 3. СОРТИРОВКА ИНДЕКСОВ И УКАЗАТЕЛЕЙ
- •1. Сортировка индексов на основе метода прямого выбора
- •2. Сортировка индексов на основе пузырьковой сортировки
- •3. Сортировка индексов на основе быстрой сортировки
- •4. Сортировка двумерных массивов
- •5. Сортировка строк
- •Приложение 4. СОРТИРОВКА ФАЙЛОВ И СПИСКОВ
- •1. Сортировка двоичных файлов
- •2. Сортировка линейных списков
- •Приложение 5. СОРТИРОВКА С УСЛОВИЕМ
- •1. Сортировка с условием на базе пузырьковой сортировки
- •2. Сортировка с условием на базе быстрой сортировки
- •3. Сортировка с условием двоичных файлов
- •4. Сортировка с условием линейного списка на базе пузырьковой сортировки
- •5. Сортировка с условием линейного списка на базе быстрой сортировки
- •ЛИТЕРАТУРА
{
for(j = i - 1; j >= 0 && min[j] > a[i]; j--) min[j + 1] = min[j];
min[j + 1] = a[i];
}
for( ; i < n; i++)
{
if (a[i] < min[m - 1])
{
for(j = m - 2; j >= 0 && min[j] > a[i]; j--) min[j + 1] = min[j];
min[j + 1] = a[i];
}
}
}
3. Пузырьковая сортировка
При пузырьковой сортировке совершается несколько проходов по массиву. При каждом таком проходе происходит сравнение соседних элементов. Если порядок соседних элементов неправильный, то они меняются местами. Если при очередном проходе по массиву выясняется, что нет такой пары рядом стоящих элементов, порядок которых неправильный, то есть массив уже отсортирован, то алгоритм сортировки завершается.
В алгоритме ниже целочисленная переменная flag будет отвечать за то, остались ли в массиве рядом стоящие элементы, порядок которых неправильный. По значению данной переменной будет видно, следует продолжать процесс сортировки либо массив уже отсортирован.
Поскольку при каждом прохождении по массиву максимальные элементы будут перемещаться в конец массива, то нет необходимости каждый раз просматривать все пары элементов. За верхнюю границу массива, до которой нужно проверять элементы, будет отвечать переменная r.
Сложность сортировки в среднем случае O(n2), в лучшем случае n-1.
305
void BubbleSort(double *a, const int n)
{
int i, r, flag; double buf; r = n;
do
{
flag = 0;
for(i = 1; i < r; i++) if (a[i] < a[i-1])
{
buf = a[i]; a[i] = a[i-1]; a[i-1] = buf; flag = 1;
}
r--; }while(flag);
}
4. Шейкерная сортировка
Данная сортировка является улучшением пузырькового метода. Алгоритм работы при шейкерной сортировке следующий. Движение по массиву происходит поочередно вперед и назад, и дополнительно выстраивается начало и конец массива в зависимости от места последнего изменения.
void ShaikerSort(double *a, int n)
{
int l, r, i, k; double buf; k = l = 0;
r = n - 2; while(l <= r)
{
for(i = l; i <= r; i++)
306
if (a[i] > a[i+1])
{
buf = a[i]; a[i] = a[i+1]; a[i+1] = buf; k = i;
}
r = k - 1;
for(i = r; i >= l; i--) if (a[i] > a[i+1])
{
buf = a[i]; a[i] = a[i+1]; a[i+1] = buf; k = i;
}
l = k + 1;
}
}
5. Быстрая сортировка
Данный алгоритм сортировки основан на рекурсивном методе. Быстрая сортировка фиксирует элемент массива x, называемый осевым, а затем переупорядочивает массив таким образом, что все элементы, меньшие осевого, оказываются перед ним, а большие элементы – за ним. Это происходит следующим образом. Просматриваем элементы массива слева направо, расположенные до осевого элемента x, пока не встретим элемент ai > x. Затем просматриваем элементы массива справа налево, расположенные после осевого элемента x, пока не встретим элемент aj < x. После этого меняем местами элементы ai и aj местами и продолжаем данный процесс. В каждой из частей массива элементы не упорядочиваются. Затем алгоритм QuickSort вызывается рекурсивно для каждой из двух частей.
Сложность сортировки в среднем случае O(n·log2 n).
void QuickSort (double *a, int left, int right)
{
int i, j; double x, buf;
307
i = left; j = right;
x = a[(left + right)/2]; do
{
while (a[i] < x) i++;
while (x < a[j]) j--;
if (i <= j)
{
buf = a[i]; a[i] = a[j]; a[j] = buf; i++;
j--;
}
} while( i <= j);
if (left < j) QuickSort (a, left, j); if (right > i) QuickSort (a, i, right);
}
Для массива a размера N данная сортировка вызывается следую-
щим образом: QuickSort(a, 0, N - 1).
Встроенная функция быстрой сортировки qsort(). В биб-
лиотеке <stdlib.h> содержится функция qsort(), которая сортирует произвольный массив объектов данных методам "быстрой сортировки". Данная функция имеет следующий прототип:
void qsort (void *a, size_t n, size_t size,
int (*compare)(const void *, const void *))
Первый аргумент представляет собой указатель на сортируемый массив a. Стандарт ANSI C допускает приведение указателя на любые данные к типу указатель на void. Поэтому первый аргумент функции qsort() может ссылаться на массив любого типа.
Второй аргумент отвечает за количество сортируемых элементов в массиве a. В качестве третьего параметра передается раз-
308
мер каждого элемента массива a в байтах. Например, если сортируемый массив имеет тип double, то в качестве третьего параметра передается sizeof(double). В то же время более универсальным способом передачи размера элементов массива служит выражение sizeof(*a).
Четвертый параметр функции qsort() определяет, в каком порядке сортировать массив a. Для этого передается указатель на функцию сравнения двух элементов. При этом данной функции (compare) передаются не значения элементов, а указатели на сравниваемые элементы. Функция compare() должна возвращать отрицательное целое число, если первый элемент должен предшествовать второму в массиве a, ноль, если элементы одинаковы, и положительное целое число, если первый элемент должен следовать за вторым в массиве a.
Приведем пример. Пусть требуется отсортировать массив double a[N] по возрастанию. Чтобы применить функцию qsort(), нужно только правильно прописать функцию сравнения элементов compare(). Например, это можно сделать таким образом:
int compare(const void *p1, const void *p2)
{
/* приводим указатели к типу double, чтобы иметь доступ к элементвм: */
const double *a1 = (const double *) p1; const double *a2 = (const double *) p2; if (*a1 < *a2)
return -1;
else if (*a1 == *a2) return 0;
else return 1;
}
При этом заметим, что данную функцию можно записать более компактно:
int compare(const void *p1, const void *p2)
{
309