
- •В.В. Чуркин технологии программирования
- •Содержание
- •Введение
- •Указатели. Операции над указателями Операции адресации и разыменования.
- •Арифметические операции.
- •Присваивание указателей.
- •Смещение и индексирование указателей.
- •Функции
- •Объявление функции (прототип)
- •Параметры функции
- •Встроенные функции
- •Функции с параметрами со значениями по умолчанию
- •Параметры функции main()
- •Рекурсивные функции
- •Перегрузка функций
- •Шаблоны функций
- •Указатели на функции
- •Объявление и инициализация массива указателей на функции:
- •Указатель на функцию как возвращаемое функцией значение
- •Выделение и освобождение динамической памяти
- •Символьные и строковые данные Символьные константы
- •Строковые константы (строки)
- •Символьные переменные
- •Строки – переменные
- •Специальные функции ввода-вывода строк
- •Стандартная библиотека функций языка с
- •Файлы Потоковый ввод-вывод в языке с Функции верхнего уровня файлового ввода-вывода
- •Открытие и закрытие потока
- •Текстовый режим
- •Бинарный режим
- •Закрытие файла
- •Функции в языке c для работы с файлами
- •Двоичный (бинарный) режим обмена с файлами
- •Строковый обмен с файлами
- •Форматный обмен с файлами
- •Позиционирование в потоке
- •Ввод-вывод нижнего уровня
- •Открытие / закрытие файла
- •Чтение и запись данных
- •Произвольный доступ к файлу
- •Позиционирование файлов
- •Сортировки числовых массивов Принцип наименьших привилегий
- •Обменная сортировка (SwapSort)
- •Сортировка выбором (SelectSort)
- •Пузырьковая сортировка (BubbleSort)
- •Сортировка вставками (InsertSort)
- •Быстрая сортировка (QuickSort)
- •Поиск в числовых массивах
- •Структуры
- •Форматы определения структурных типов
- •Форматы определения объектов структурных типов
- •Операции над объектами структурного типа
- •Доступ к элементам объектов структурного типа
- •Структуры, массивы и указатели
- •Объединения (смеси)
- •Оператор switch (переключатель)
- •Динамические структуры данных
- •Реализация стека с помощью массива
- •Очередь
- •Очередь приоритетов
- •Реализация очереди с помощью массива
- •Линейные списки
- •Функции для работы с двунаправленным линейным списком
- •Реализация списка с помощью массивов
- •Поиск хэшированием
- •Бинарные деревья
- •Бинарное упорядоченное дерево (дерево поиска)
- •Идеально сбалансированное дерево
- •Операции с бинарным упорядоченным деревом
- •Удаление узла из дерева
- •Обход (просмотр) дерева
- •Реализация дерева с помощью массивов
- •Вывод динамических структур в файл и чтение их из файла
- •Сбалансированные (avl) деревья
- •Алгоритм avl-вставки.
- •Повороты
- •Классы и объектно-ориентированное программирование
- •Объявление класса
- •Определение класса (реализация класса)
- •Использование класса (драйвер класса)
- •Доступ к элементам класса
- •Отделение интерфейса от реализации
- •Обслуживающие функции-утилиты
- •Конструкторы
- •Windows-программы в Builder
- •Структура головного файла проекта
- •Структура заголовочного файла модуля формы (“Unit1.H”)
- •Структура файла реализации модуля формы (“Unit1.Cpp”)
- •Области видимости (или области действия) переменных в блоках. Время жизни переменных
- •Доступ к свойствам и функциям-элементам (методам) объектов, переменным и функциям в приложении, содержащем одну форму
- •Константные объекты и константные функции-элементы
- •Перегрузка операций
- •Перегрузка унарных операций
- •Перегрузка бинарных операций
- •Перегрузка операции присваивания
- •Перегрузка операции приведения типа
- •Перегрузка операции индексирования []
- •Композиция классов
- •Дружественные функции класса
- •Дружественный класс
- •Использование указателя this
- •Статические элементы класса
- •Шаблон класса для статически и динамически создаваемых объектов
- •Конструктор 1
- •Деструктор
- •Вызовы конструкторов и деструкторов
- •Перегруженная операция присваивания
- •Конструктор 2 (конструктор копирования, конструктор копии)
- •Наследование. Иерархия классов.
- •Ключи доступа
- •Пример простого наследования (точка, круг)
- •Правила наследования функций-элементов. Вызовы конструкторов и деструкторов в иерархии
- •Виртуальные функции и полиморфизм
- •Правила определения и наследования виртуальных функций
- •Позднее (динамическое) связывание
- •Полиморфизм. Абстрактные и конкретные классы
- •Учебная литература (основная)
- •Учебная литература (для углубленного изучения)
- •Учебно-методические издания
Идеально сбалансированное дерево
Идеально сбалансированным дерево - это дерево, в котором число узлов (вершин) в левом и правом поддеревьях отличается не более чем на единицу.
Алгоритм равномерного распределения для известного числа вершин формулируют, используя рекурсию.
Взять одну вершину в качестве корня.
Построить тем же способом левое поддерево с
вершинами.
Построить тем же способом правое поддерево с
вершинами.
Операции с бинарным упорядоченным деревом
Удаление узла из дерева
а)
Исключаемый узел – лист. В этом случае
надо просто удалить ссылку на данный
узел.
б) Из исключаемого узла выходит одна ветвь. В этом случае надо переназначить указатель (пунктир).
в) Из исключаемого узла выходят две ветви. В этом случае на место удаляемого узла надо поставить либо самый правый узел левой ветви, либо самый левый узел правой ветви для сохранения упорядоченности дерева.
Удаляем узел 6:
Обход (просмотр) дерева
Существуют 4 способа обхода (просмотра) дерева, которые следуют из самой структуры дерева:
а) Просмотр слева-направо: A, R, B (левое поддерево-корень-правое поддерево).
б) Просмотр сверху-вниз: R, A, B (корень до поддеревьев).
в) Просмотр снизу-вверх: A, B, R (корень после поддеревьев).
г) Просмотр справа-налево: B, R, A (правое поддерево-корень-левое поддерево).
Приведем
результаты просмотров дерево, в которое
записана формула
:
а) A, R, B: - обычная инфиксная запись. Нужны скобки и повторное сканирование, чтобы вычислить.
б)
R,
A,
B:
- префиксная запись.
в)
A,
B,
R:
- постфиксная запись. Для вычисления не
требуется повторное сканирование
выражения.
Для бинарного упорядоченного дерева просмотр слева-направо (A, R, B) дает последовательность: 5, 6, 8, 9, 11, 12, 17, 18, 23; а просмотр справа-налево (B, R, A) даст: 23, 18, 17, 12, 11, 9, 8, 7, 6, 5.
Таким образом, бинарные упорядоченные деревья можно применять для сортировки.
Примечание. При обходе дерева узлы не удаляются.
В набор необходимых для работы с деревом входят следующие операции:
1) формирование первого узла дерева – корня;
2) включение узла в дерево;
3) поиск по дереву;
4) обход дерева;
5) удаление дерева.
Дерево является рекурсивной структурой данных, поскольку каждое поддерево также является деревом. Действия с такими структурами принято описывать с помощью рекурсивных алгоритмов.
Пример. Программа формирует дерево из массива целых чисел и выводит его на экран.
#include <iostream.h>
struct Node
{ int d;
Node* left;
Node* right;
};
//---------------------------------------------------------------------------------
Node* first(int d);
Node* search_insert(Node* root, int d);
void print_tree(Node* root, int l);
//----------------------------------------------------------------------------------
void main()
{ int b[]={10, 25, 20, 6, 21, 8, 1, 30};
Node* root=first(b[0]);
for(int i=1; i<8; i++) search_insert(root, b[i]);
print_tree(root, 0);
}
//------------------------------------------------------------------------------------
//Формирование первого узла дерева – корня
Node* first(int d)
{ Node* pv=new Node;
pv->d=d;
pv->left=0;
pv->right=0;
return pv;
}
//------------------------------------------------------------------------------------
//Поиск с включением
Node* search_insert(Node* root, int d)
{ Node* pv=root, *prev;
int found=0;
while(pv&&!found) {
prev=pv;
if(d==pv->d) found=1; //элемент уже есть в дереве!
else if(d<pv->d) pv=pv->left;
else pv=pv->right;
}
if(found) return pv; //включения не будет
//создание нового узла (вершины)
Node* pnew=new Node;
pnew->d=d;
pnew->left=0;
pnew->right=0;
if(d<prev->d) //присоединение к левому поддереву предка
prev->left=pnew;
else //присоединение к правому поддереву предка
prev->right=pnew;
return pnew;
}
//---------------------------------------------------------------------------------
Для каждого рекурсивного алгоритма можно создать его нерекурсивный эквивалент. Использована нерекурсивная функция поиска по дереву с включением. Она осуществляет поиск элемента (узла) с заданным ключом. Если элемент найден, она возвращает указатель на него, а если нет – включает элемент в соответствующее место дерева и возвращает указатель на включенный элемент. Для включения элемента необходимо помнить пройденный по дереву путь на один шаг назад и знать, выполняется ли включение нового элемента в левое или правое поддерево его предка.
Указатель для поиска по дереву обозначен pv, указатель на предка обозначен prev, указатель pnew используется для выделения памяти под включаемый в дерево узел. Рекурсии удалось избежать, сохранив всего одну переменную (prev) и повторив при включении операторы, определяющие, к какому поддереву присоединяется новый узел.
//----------------------------------------------------------------------------------
// Обход дерева слева-направо (A, R, B)
void print_tree(Node* p, int level)
{ if(p) {
print_tree(p->left, level+1); //вывод левого поддерева
for(int i=0; i<level; i++) cout<<”****”;
cout<<p->d<<endl; //вывод корня поддерева
print_tree(p->right, level+1); //вывод правого поддерева
}
}
//-----------------------------------------------------------------------------------
В дереве размещена последовательность чисел: 10, 25, 20, 6, 21, 8, 1, 30:
В функции обхода дерева вторым параметром передается целая переменная, определяющая, на каком уровне находится узел. Корень находится на уровне 0. Перед значением узла для имитации структуры дерева выводится количество звездочек ‘*’, пропорциональное уровню узла. Если закомментировать цикл печати звездочек, отсортированный по возрастанию массив будет выведен в столбик.
//----------------------------------------------------------------------------------------------
//Удаление дерева
void clear_tree(Node* p)
{ if(p->right) clear_tree(p->right);
if(p->left) clear_tree(p->left);
delete p;
}
//----------------------------------------------------------------------------------------------
Дополнительные функции работы с деревом
//----------------------------------------------------------------------------------
//Печать заданного слоя (df)
void print_puff(Node* p, int df, int dp)
{ if(p->right) print_puff(p->right, df, dp+1);
if(dp==df) {for(int i=0; i<dp; i++) cout<<”****”;
cout<<p->d<<endl;
if(p->left) print_puff(p->left, df, dp+1);
}
//--------------------------------------------------------------------------------------------
//Поиск узла с заданным ключом, вывод ветви и слоя с искомым ключом
void tree_key(Node* p, int k)
{ Node* pv=p;
int depth=0, depth_puff=0;
while(pv&&pv->d!=k)
{ depth++; depth_puff++;
if(pv->d>k) pv=pv->left;
else pv=pv->right; }
if(!pv) {cout<<”Узла с искомым ключом нет!”<<endl; return;}
cout<<”Искомый ключ - ”<<pv->d<<endl;
cout<<”Ветвь дерева с искомым ключом: ”<<endl;
print_tree(pv, depth);
cout<<”Слой дерева с искомым ключом: ”<<endl;
print_puff(p, depth_puff, 0);
}
//------------------------------------------------------------------------------------------
//Подсчет количества листьев (вызов – int c; kol_list(root, c);
void kol_list(Node* p, int& count)
{ Node* pv=p;
if(pv) { kol_list(pv->left, count);
kol_list(pv->right, count);
if(pv->left==0&&pv->right==0) count++; }
}
//-------------------------------------------------------------------------------------------
//Нахождение максимальной глубины левого и правого поддеревьев
int depth(Node* p)
{ int dl, dr, dv;
if(!p) dv=-1;
else { dl=depth(p->left);
dr=depth(p->right);
dv=1+(dl>dr?dl:dr);
return dv;
}
//--------------------------------------------------------------------------------------------