
- •Введение
- •Основные понятия и определения
- •Типы данных
- •1.1.1. Понятие типа данных
- •1.2.2. Внутреннее представление базовых типов в оперативной памяти
- •1.2.2. Внутреннее представление структурированных типов данных
- •1.2.3. Статическое и динамическое выделение памяти
- •Абстрактные типы данных (атд)
- •Понятие атд
- •1.2.2. Спецификация и реализация атд
- •Структуры данных
- •1.3.1. Понятие структуры данных
- •1.3.2. Структуры хранения — непрерывная и ссылочная
- •1.4.3. Классификация структур данных
- •Алгоритмы
- •1.4.1. Понятие алгоритма
- •1.4.2. Способы записи алгоритмов.
- •1.4.3. Введение в анализ алгоритмов Вычислительные модели
- •Задача анализа алгоритмов
- •Время работы алгоритма
- •Время выполнения в худшем и среднем случае
- •1.4.3. Введение в рекурсию
- •Первые примеры
- •1.5.1. Введение в «длинную» арифметику
- •1.5.2. Рекурсия
- •1.5.3. Поразрядные операции. Реализация атд «Множество»
- •2. Линейные структуры данных
- •2.1. Атд "Стек", "Очередь", "Дек"
- •2.2. Реализация стеков
- •2.2.1. Непрерывная реализация стека с помощью массива
- •2.2.2. Ссылочная реализация стека в динамической памяти
- •2.2.3. Примеры программ с использованием стеков
- •2.3. Реализация очередей
- •2.3.2. Непрерывная реализация очереди с помощью массива
- •2.3.2. Ссылочная реализация очереди в динамической памяти
- •2.3.3. Ссылочная реализация очереди с помощью циклического списка
- •2.3.4. Очереди с приоритетами
- •2.3.5. Пример программы с использованием очереди
- •2.4. Списки как абстрактные типы данных
- •2.4.1. Модель списка с выделенным текущим элементом
- •2.4.2. Однонаправленный список (список л1)
- •2.4.3. Двунаправленный список (список л2)
- •2.4.4. Циклический (кольцевой) список
- •2.5. Реализация списков с выделенным текущим элементом
- •2.5.1. Однонаправленные списки Ссылочная реализация в динамической памяти на основе указателей
- •2.5.2. Двусвязные списки
- •2.5.3. Кольцевые списки
- •2.5.4. Примеры программ, использующих списки Очередь с приоритетами на основе линейного списка
- •Задача Иосифа (удаление из кольцевого списка)
- •2.6. Рекурсивная обработка линейных списков
- •2.6.1. Модель списка при рекурсивном подходе
- •2.6.2. Реализация линейного списка при рекурсивном подходе
- •3. Иерархические структуры данных
- •3.1. Иерархические списки
- •3.1.1 Иерархические списки как атд
- •3.1.2. Реализация иерархических списков
- •3.2. Деревья и леса
- •3.2.1. Определения
- •3.2. Способы представления деревьев
- •3.2.3. Терминология деревьев
- •3.2.4. Упорядоченные деревья и леса. Связь с иерархическими списками
- •3.3. Бинарные деревья
- •3.3.1. Определение. Представления бинарных деревьев
- •3.3.2. Математические свойства бинарных деревьев
- •3.4. Соответствие между упорядоченным лесом и бинарным деревом
- •3.5. Бинарные деревья как атд
- •3.6. Ссылочная реализация бинарных деревьев
- •3.6.1. Ссылочная реализация бинарного дерева на основе указателей
- •3.6.2. Ссылочная реализация на основе массива
- •3.6.3. Пример — построение дерева турнира
- •3.7. Обходы бинарных деревьев и леса
- •3.7.1. Понятие обхода. Виды обходов
- •3.7.2. Рекурсивные функции обхода бинарных деревьев
- •3.7.3. Нерекурсивные функции обхода бинарных деревьев
- •3.7.4. Обходы леса
- •3.7.5. Прошитые деревья
- •3.8. Применения деревьев
- •3.8.1. Дерево-формула
- •3.8.2. Задача сжатия информации. Коды Хаффмана
- •4. Сортировка и родственные задачи
- •4.1. Общие сведения
- •4.1.1. Постановка задачи
- •4.1.2. Характеристики и классификация алгоритмов сортировки
- •4.2. Простые методы сортировки
- •4.2.1. Сортировка выбором
- •4.2.2. Сортировка алгоритмом пузырька
- •4.2.3.Сортировка простыми вставками.
- •4.3. Быстрые способы сортировки, основанные на сравнении
- •4.3.1. Сортировка упорядоченным бинарным деревом
- •Анализ алгоритма сортировки бинарным деревом поиска
- •4.3.2. Пирамидальная сортировка
- •Первая фаза сортировки пирамидой
- •Вторая фаза сортировки пирамидой
- •Анализ алгоритма сортировки пирамидой
- •Реализация очереди с приоритетами на базе пирамиды
- •4.3.2. Сортировка слиянием
- •Анализ алгоритма сортировки слиянием
- •4.3.3. Быстрая сортировка Хоара
- •Анализ алгоритма быстрой сортировки
- •4.3.4. Сортировка Шелла
- •4.3.5. Нижняя оценка для алгоритмов сортировки, основанных на сравнениях
- •4.4. Сортировка за линейное время
- •4.4.1. Сортировка подсчетом
- •4.4.2. Распределяющая сортировка от младшего разряда к старшему
- •4.4.3. Распределяющая сортировка от старшего разряда к младшему
- •5. Структуры и алгоритмы для поиска данных
- •5.1. Общие сведения
- •5.1.1. Постановка задачи поиска
- •5.1.2. Структуры для поддержки поиска
- •5.1.3. Соглашения по программному интерфейсу
- •5.2. Последовательный (линейный) поиск
- •5.3. Бинарный поиск в упорядоченном массиве
- •5.4. Бинарные деревья поиска
- •5.4.1. Анализ алгоритмов поиска, вставки и удаления Поиск
- •Вставка
- •Удаление
- •5.4.3. Реализация бинарного дерева поиска
- •5.5. Сбалансированные деревья
- •Определение и свойства авл-деревьев
- •Вращения
- •Алгоритмы вставки и удаления
- •Реализация рекурсивного алгоритма вставки в авл-дерево
- •5.5.2. Сильноветвящиеся деревья
- •Бинарные представления сильноветвящихся деревьев
- •5.5.3. Рандомизированные деревья поиска
- •5.6. Структуры данных, основанные на хеш-таблицах
- •5.6.2. Выбор хеш-функций и оценка их эффективности
- •Модульное хеширование (метод деления)
- •Мультипликативный метод
- •Метод середины квадрата
- •5.6.2. Метод цепочек
- •5.6.3. Хеширование с открытой адресацией
- •5.6.4. Пример решения задачи поиска с использованием хеш-таблицы
Первые примеры
В качестве первых примеров подобраны небольшие задачи-этюды, решение которых не требует никаких специальных знаний, но позволяет освоить или закрепить некоторые полезные приемы программирования, которые могут найти применение и при решении серьезных задач. Они сгруппированы по темам.
1.5.1. Введение в «длинную» арифметику
Иногда возникает необходимость выполнять вычисления над очень большими числами, которые не входят ни в один из стандартных числовых типов (вспомним, что тип — это множество допустимых значений и набор допустимых операций). В таком случае приходится придумывать какой-либо способ хранения таких больших чисел. Разумеется, весь набор арифметических операций в этом случае приходится реализовывать самостоятельно, так и возник термин «длинная» арифметика.
Наиболее простой способ хранения большого целого числа, который чаще всего и применяется, — использование целочисленного одномерного массива, каждый элемент которого хранит одну десятичную цифру числа. В этом случае реализация основных арифметических операций сводится к задаче обработки массивов и требует только хорошей техники программирования и аккуратности.
Для примера возьмем задачу поинтереснее, она предлагалась на одной из городских олимпиад для школьников.
Получить наименьшее натуральное число, обладающее следующими свойствами:
Оканчивается на 5.
При умножении его на 5 получается то же самое число, что и при переносе цифры 5 с последнего места на первое.
#include <iostream.h>
void main()
{
int a[100], k=1, d=0; a[0]=5;
do
{
a[k]=a[k-1]*5+d;
d=a[k]/10; a[k]=a[k]%10;
k++;
}
while ((d!=0)||(a[k-1]!=1));
cout<<"Here is a round number: ";
for (int i=k-1; i>=0; i--) cout<<a[i];
cout<<endl; cin.get();
}
1.5.2. Рекурсия
Пример 1. Последовательность чисел Фибоначчи определяется так:a(0)= 1, a(1) = 1, a(k) = a(k-1) + a(k-2) при k >= 2. Первые члены этой последовательности: 1 1 2 3 5 8 13 21 и т. д. Дано n, вычислить a(n).
Задача имеет очевидное нерекурсивное решение, имеющее сложность C*n. При больших n вычисления могут занять много времени, поэтому приведем быстрый рекурсивный алгоритм решения этой задачи. Пара соседних чисел Фибоначчи получается из предыдущей умножением на матрицу
|1 1|
|1 0|
(проверьте сами), так что задача сводится к возведению матрицы в степень n. Это можно сделать за C*log n действий тем же способом, что и для чисел (см. разд. 1.4.3).
#include <iostream.h>
typedef int Matr[2][2];
void mult(Matr t1, Matr t2, Matr p)
{ p[0][0] = t1[0][0]*t2[0][0] + t1[0][1]*t2[1][0];
p[0][1] = t1[0][0]*t2[1][0] + t1[0][1]*t2[1][1];
p[1][0] = t1[1][0]*t2[0][0] + t1[1][1]*t2[1][0];
p[1][1] = t1[1][0]*t2[1][0] + t1[1][1]*t2[1][1];
}
void power(Matr t, int n)
{ Matr tmp,tmp2;
if (n==0)
{ t[0][0] = 1; t[1][1] = 1; t[1][0] = 0; t[0][1] = 0;
return;
}
if (n%2==0)
{ power(t,n/2); mult(t,t,tmp); memcpy(t,tmp,4*sizeof(int));
}
else
{ memcpy(tmp,t,4*sizeof(int)); power(t,n-1); mult(tmp,t,tmp2);
memcpy(t,tmp2,4*sizeof(int));
}
}
int main(void)
{ Matr t = {1,1,1,0};
power(t,10); cout << t[0][0] << endl;
return 0;
}
Многие задачи, требующие перебора различных вариантов, имеют компактное рекурсивное решение. Приведем пример одной из таких задач.
Пример 2. Перечислить все представления положительного целого числа n в виде суммы последовательности невозрастающих целых положительных слагаемых. Например, для числа 3 таких представлений будет всего два:
2+1 и 1+1+1.
#include <iostream.h>
int a[1000];
void summa(int n, int max, int k)
{ if (n<max) max=n;
for (int i=max; i>0; i--)
{ a[k]=i;
if (i==n)
{ for (int i=0; i<k; i++) cout<<a[i]<<"+";
cout<<a[k]<<endl;
}
else summa(n-i,i,k+1);
}
}
void main()
{ int n;
cout<<"Введите число N: "; cin>>n;
summa(n,n,0);
}