
- •Введение
- •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. Сортировка с условием линейного списка на базе быстрой сортировки
- •ЛИТЕРАТУРА
Приложение 1. АЛГОРИТМЫ ПОИСКА
Пусть имеется некоторый массив a (например, действительных чисел) размером n. Нужно определить, содержит ли массив a элемент с некоторым наперед заданным значением x. Если ответ положительный, то требуется возвратить индекс элемента со значением x, в противном случае – возвратить значение -1. В данном разделе приведены три алгоритма решения данной задачи: линейный поиск, поиск с барьером и двоичный поиск.
1. Линейный поиск
Алгоритм линейного поиска заключается в том, что массив последовательно просматривается по одному элементу до тех пор, пока либо не встретится искомый элемент x, либо не будут просмотрены все элементы массива (если элемента x в массиве нет).
int LinearSearch(const double *a, const int n, const double x)
{
int i; i = -1;
while (++i < n && a[i] != x)
;
return (i < n) ? i : -1;
}
2. Поиск с барьером
Алгоритм линейного поиска можно немного усовершенствовать, если в цикле while убрать проверку выхода за границу массива (i < n). Но для этого надо иметь гарантию, что элемент x обязательно найдется в массиве a, чтобы цикл while остановился. Для этой цели в конец массива достаточно поместить дополнительный
298

элемент со значением x. Такой элемент называется барьерным элементом.
int BarrierSearch(double *a, const int n, const double x)
{
int i;
a[n-1] = x; /* на последнее место ставим барьерный элемент */ i = -1;
while (a[++i] != x)
;
return (i < n-1) ? i : -1;
}
3. Двоичный поиск
Если заранее известно, что массив является упорядоченным, то алгоритм поиска можно сделать более эффективным. Пусть для определенности массив a упорядочен по возрастанию. В следующем алгоритме реализован двоичный поиск элемента.
Пусть l и r – соответственно левая и правая границы того подмассива в массиве a, который предположительно содержит искомый элемент x:
Изначально значения l и r охватывают весь массив a, то есть l=0 и r=n-1, где n – размерность массива a. На очередном шаге находим середину отрезка [l, r]: mid=(l+r)/2. После чего элемент x сравниваем со средним элементом a[mid] текущего подмассива (с границами l и r). При этом возможны такие ситуации: x==a[mid], x<a[mid], x>a[mid]. В первом сл учае элемент x найден в массиве a, и поиск можно завершить. В двух других случаях мы можем отбросить половину текущего подмассива путем переопределения либо значения l, либо значения r. Например, если x<a[mid], то понятно, что интересуемый нас элемент может находиться до элемента с индексом mid, поэтому переопределим значение правой границы r, положив r=mid-1, после чего переходим к следующему шагу.
299
int BinarySearch(const double *a, const int n, const double x)
{
int l, |
/* левая граница рассматриваемого массива |
*/ |
r, |
/* правая граница рассматриваемого массива */ |
|
mid; |
/* середина отрезка [l, r] |
*/ |
l = 0;
r = n – 1; while (l < r)
{
mid = (l + r) / 2; /* вычисляем середину отрезка [l, r] */ if (a[mid] == x)
l = r = mid; else
if (a[mid] < x) l = mid + 1;
else r = mid - 1;
}
return (a[l] == x) ? l : -1;
}
300