- •Введение
- •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. Сортировка с условием линейного списка на базе быстрой сортировки
- •ЛИТЕРАТУРА
return a[*(const int *)p1] - a[*(const int *)p2];
}
int main()
{
int i, ind[N];
…/* заполнение массива a */ for(i = 0; i < N; i++)
ind[i] = i;
qsort(ind, N, sizeof(*ind), compare); return 0;
}
4. Сортировка двумерных массивов
Пусть a[M][N] – некоторая матрица. На множестве строк данной матрицы (a[0], a[1],…,a[M-1]) определим числовую функцию F. Например, функция F может возвращать значение первого элемента строки (F(a[i]) = a[i][0]), сумму элементов строки (F(a[i]) = a[i][0] + a[i][1] + … + a[i][N-1]) и т.д. Задача состоит в том, чтобы упорядочить строки матрицы таким образом, чтобы последовательность F(b[0]), F(b[1]),…, F(b[M-1]) была неубывающей, где символом b обозначена новая матрица, полученная из матрицы a путем перестановки строк.
Для решения данной задачи имеется несколько подходов. Вопервых, можно действительно переставлять строки матрицы a, но для матриц с большим значением числа N (число столбцов) это неэффективно. Во-вторых, можно завести массив индексов int ind[M] ={0, 1,…, M-1} и переставлять в нем элементы таким образом, что-
бы последовательность F(a[ind[0]]), F(a[ind[1]]),…, F(a[ind[M-1]])
удовлетворяла условию задачи. При этом матрица a останется без изменений, а обращение к “новой” матрице b будет происходить следующим образом:
a[ind[i]][j], 0 ≤ i < M, 0 ≤ j < N.
Такой подход для одномерных массивов мы уже рассматривали выше. Поэтому (для разнообразия) остановимся на третьем подхо-
319
де (сопряженным со вторым), который заключается в рассмотрении дополнительного массива указателей int *pa[M], где pa[i] содержит адрес i-ой строки матрицы a, i = 0,…,M-1.
Изначально массив pa инициализируется следующим обра-
зом:
for(i = 0; i < M; i++) pa[i] = a[i];
Затем с помощью алгоритма быстрой сортировки происходит преобразование массива указателей pa так, чтобы последовательность F(pa[0]), F(pa[1]),…, F(pa[M-1]) удовлетворяла начальному условию. В алгоритме ниже в качестве функции F выступает функция, которая возвращает первый элемент строки. Поэтому вместо записи F(pa[i]) будем использовать запись * pa[i].
#include<stdio.h>
#define M 5 /* Число строк матрицы */ #define N 10 /* Число столбцов матрицы */
/* вывод матрицы на экран через массив индексов pa */ void Print(int *pa[], int m, int n)
{
int i, j;
for (i = 0; i < m; i++)
{
for (j = 0; j < n; j++) printf("%5d", pa[i][j]);
printf("\n");
}
}
/* сортировка массива pa по первым элементам массива a: */ void QuickSort (int *pa[], int left, int right)
{
int i, j, *buf; int x;
i = left;
320
j = right;
x = *pa[(left + right)/2]; do
{
while (*pa[i] < x) i++;
while (x < *pa[j]) j--;
if (i <= j)
{
buf = pa[i]; pa[i] = pa[j]; pa[j] = buf; i++;
j--;
}
} while( i <= j);
if (left < j) QuickSort (pa, left, j); if (right > i) QuickSort (pa, i, right);
}
int main()
{
int i, a[M][N], /* исходная матрица */
*pa[M]; /* массив указателей */ …/* инициализация матрицы a */
/* инициализация массива индексов pa: */ for(i = 0; i < M; i++)
pa[i] = a[i];
/* вывод исходной матрицы a: */ Print(pa, M, N);
/* сортировка элементов массива указателей pa */ QuickSort(pa, 0, M-1);
/* вывод упорядоченной матрицы по первым элементам строк: */
Print(pa, M, N); return 0;
}
321
5. Сортировка строк
Рассмотрим алгоритмы сортировки строк. При этом задача состоит в том, чтобы выделить слова из введенной строки и расположить их в лексикографическом порядке на основе ASCII кодов символов. В качестве основы возьмем очень быстрые алгоритмы выделения всех слов из строки, приведенные в параграфе 8.7, а в качестве сортировки используем алгоритм быстрой сортировки (QuickSort), где сравнение слов будет происходить с помощью функции strcmp из библиотеки <string.h>.
Случай 1. Если слова в строке разделены символами ‘\0’ (см. случай 1 параграфа 8.7), то можно определить массив указателей, содержащий адреса выделенных слов. В этом случае сортируются ссылки на данные слова.
#include <stdio.h> #include <string.h>
#define DELIMITERS " .,:;?!\n\t" /* символы-разделители */ #define SIZE 100 /* максимальный размер массива указаателей */
#define N 1024 /* размер строки */
/* сортировка строк на основе быстрой сортировки */ void QuickSortWords (char *a[], int left, int right)
{
int i, j;
char *x, *buf; i = left;
j = right;
x = a[(left + right)/2]; do
{
while (strcmp(x, a[i]) > 0) i++;
while (strcmp(x, a[j]) < 0) j--;
if (i <= j)
{
322
buf = a[i]; a[i] = a[j]; a[j] = buf; i++; j--;
}
} while( i <= j);
if (left < j) QuickSortWords (a, left, j); if (right > i) QuickSortWords (a, i, right);
}
int main( )
{
char sentence[N]; /* строка-предложение */ char *pstr[SIZE]; /* маасив указателей */ int i, j, flag[256] = {0}, n;
fgets(sentence, N, stdin);
for (i = 0; DELIMITERS[i]; i++) flag[DELIMITERS[i]] = 1;
for (i = 0; sentence[i] && flag[sentence[i]]; i++) sentence[i] = '\0';
n = 0;
/* выделяем слова в строке sentence и адреса слов записываем в массив pstr:
*/
while (sentence[i])
{
pstr[n++] = &sentence[i];
while (sentence[i] && !flag[sentence[i]]) i++;
j = i;
while (sentence[i] && flag[sentence[i]]) i++;
sentence[j] = '\0';
}
/* сортируем слова в строке через массив ссылок pstr: */
QuickSortWords(pstr, 0, n - 1);
/* выводим слова в лексикографическом порядке */ for(i = 0; i < n; i++)
puts(pstr[i]);
323
return 0;
}
Случай 2. Если слова выделяются из строки-предложения путем копирования слов в динамические строки (см. случай 2 параграфа 8.7), то адреса этих динамических строк будем сохранять в массиве ссылок pstr, а потом сортировать с помощью функции QuickSortWords (из предыдущего случая) не сами строки, а их адреса.
#include <stdio.h> #include <stdlib.h> #include <string.h>
#define DELIMITERS " .,:;?!\n\t" /* символы-разделители */ #define SIZE 100 /* максимальный размер массива указаателей */
#define N 1024 /* размер строки */
int main( ) |
|
{ |
|
char sentence[N]; |
/* исходная строка */ |
char *pstr[SIZE];
int n, i, j, flag[256] = {0};
for (i = 0; DELIMITERS[i]; i++) flag[DELIMITERS[i]] = 1;
fgets(sentence, N, stdin);
for (i = 0; sentence[i] && flag[sentence[i]]; i++)
;
n = 0;
while (sentence[i])
{
j = i;
while (sentence[i] && !flag[sentence[i]]) i++;
/* выделяем память для очередного слова: */ pstr[n] = (char *)malloc((i - j + 1) * sizeof(char)); /* копируем очередное слово: */ strncpy(pstr[n], &sentence[j], i - j);
324
pstr[n][i - j] = '\0'; n++;
while (sentence[i] && flag[sentence[i]]) i++;
}
/* функция QuickSortWords прописана в случае 1 */
QuickSortWords(pstr, 0, n - 1);
/* выводим слова в лексикографическом порядке */ for(i = 0; i < n; i++)
puts(pstr[i]); return 0;
}
325