- •Раздел 4. Разработка по Тема 4.1. Проектирование интерфейса с пользователем
- •4.1.1. Типы пользовательских интерфейсов.
- •4.1.2. Пользовательская и программная модели интерфейса.
- •4.1.3. Разработка диалогов.
- •4.1.4. Основные компоненты графических пользовательских интерфейсов.
- •Тема 4.2. Реализация графических пользовательских интерфейсов.
- •4.2.1. Диалоги, управляемые пользователем.
- •4.2.2. Диалоги, управляемые системой.
- •4.2.3. Использование метафор.
- •4.2.4. Технология Drag and Drop.
- •4.2.5. Интеллектуальные элементы.
- •4.3.1. Базовые типы данных.
- •Константы
- •Область действия имен
- •4.3.2. Указатели и адресная арифметика.
- •4.3.3. Составные типы данных. Структуры
- •Битовые поля
- •Определение типов
- •Перечислимые типы
- •4.3.4. Выражения и операции.
- •4.3.5. Управляющие конструкции. Условные операторы
- •Операторы циклов
- •4.4.1. Статические одномерные массивы.
- •4.4.2. Статические многомерные массивы.
- •4.4.3. Динамические массивы.
- •4.4.4. Массивы указателей.
- •4.5.1. Стеки.
- •4.5.2. Очереди.
- •4.5.3. Списки.
- •4.5.4. Бинарные деревья.
- •4.6.1. Объявление классов и экземпляров классов.
- •4.6.2. Инкапсуляция данных и методов.
- •4.6.3. Конструкторы классов.
- •Конструктор по умолчанию
- •Конструктор копирования
- •4.6.4. Деструкторы классов.
- •4.7.1. Разделы в описании класса.
- •4.7.2. Friend-конструкции.
- •4.7.3. Статические члены классов.
- •4.7.4. Использование описателя const в классах.
- •4.8.1. Вложенность классов.
- •4.8.2. Наследование данных и методов.
- •4.8.3. Типы наследования.
- •4.9.1. Полиморфизм раннего связывания.
- •4.9.2. Полиморфизм позднего связывания и виртуальные функции.
- •4.9.3. Абстрактные методы и классы.
- •4.10.1. Функции консольного ввода-вывода.
- •4.10.2. Функции файлового ввода-вывода.
- •4.10.3. Использование библиотеки классов потокового ввода-вывода.
- •4.11.1. Перегрузка операций.
- •4.11.2. Шаблоны функций.
- •4.11.3. Шаблоны классов.
- •4.11.4. Обработка исключений.
- •Тема 4.12. Com-технология.
- •4.12.1. Основные понятия.
- •4.12.2. Типы интерфейсов.
- •Свойства интерфейсов
- •Типы интерфейсов
- •4.12.3. Типы com-объектов.
- •4.12.4. Фабрика классов.
- •Тема 4.13. Построение com-сервера.
- •4.13.1. Язык idl.
- •Содержимое файла idl
- •4.13.2. Определение пользовательского интерфейса.
- •4.13.3. Реализация пользовательского интерфейса.
- •4.13.4. Создание тестового клиента.
- •Тема 4.14. Обзор платформы ms .Net.
- •4.14.1. Общая идея архитектуры .Net.
- •4.14.2. Достоинства и недостатки .Net.
- •4.14.3. Схема трансляции программ в .Net.
- •4.14.4. Язык msil.
- •4.14.5. Объектно-ориентированная модель .Net.
4.5.4. Бинарные деревья.
Бинарное дерево — это конечное множество элементов, которое либо пусто, либо содержит один элемент, называемый корнем дерева, а остальные элементы множества делятся на два непересекающихся подмножества, каждое из которых само является бинарным деревом. Эти подмножества называются левым и правым поддеревьями исходного дерева. Каждый элемент бинарного дерева называется узлом дерева. Общепринятый способ изображения бинарного дерева представлен на рис. 5.15, а на рис. 5.16 показаны структуры, которые таковыми не являются.
Если А — корень бинарного дерева, а В — корень его левого или правого поддерева, то говорят, что А является отцом В, а В — левый или правый сын. Два узла являются братьями, если они сыновья одного и того же отца. Узел, не имеющий сыновей (узлы D, G, Н и I на рис. 5.15), называется листом. Если каждый узел бинарного дерева, не являющийся листом, имеет непустые правое и левое поддеревья, то дерево называется строго бинарным деревом.
Уровень узла в бинарном дереве определен следующим образом: корень дерева имеет уровень 0, а уровень любого другого узла дерева на 1 больше уровня своего отца. Глубина бинарного дерева — это максимальный уровень листа дерева, что равно длине самого длинного пути от корня к листу дерева.
Графическое представление (рис. 5.15) наглядно и им удобно пользоваться при работе с бинарными деревьями. Однако следует помнить, что память линейна и с этой точки зрения можно считать, что бинарное дерево представляет собой разновидность связного списка. Поэтому основные операции над ними во многом те же самые, что и для списков: элементы дерева можно добавлять, удалять, а также осуществлять к ним доступ. Интерфейс для программ работы с бинарными деревьями приведен в листинге 5.10. Информационные поля могут быть произвольными. В данном случае, чтобы не загромождать функции дополнительной обработкой "сложных" полей, мы ограничились двумя целыми полями.
/* Интерфейс для работы с бинарными деревьями Модуль Tree.h */
#define TREE struct tree
TREE
{
int iField; // информационное значение
int iCount; // счетчик вхождений элемента
TREE *pLeft, *pRight; // указатели на левое и правое поддеревья
};
extern void insert(TREE **ppRoot, TREE *pItem);
extern int remove(TREE **ppRoot, TREE *pItem) ;
extern void destroy(TREE *pRoot);
extern void display(TREE *pRoot);
Структура для работы с бинарными деревьями приведена на рис. 5.17. Обратите внимание на ее сходство со структурой для работы с двунаправленным списком (рис. 5.13).
При работе с деревьями одной из основных операций является прохождение дерева — обход всего дерева, при котором каждый узел посещается один раз. Существуют три способа, отличающиеся порядком посещения корня и прохождения его левого и правого поддеревьев (рис. 5.18):
Прямой порядок (нисходящий)
попасть в корень;
пройти в прямом порядке левое поддерево;
пройти в прямом порядке правое.
Симметричный порядок (последовательный)
пройти в симметричном порядке левое поддерево;
попасть в корень;
пройти в симметричном порядке правое поддерево.
Обратный порядок (восходящий)
пройти в обратном порядке левое поддерево;
пройти в обратном порядке правое поддерево;
попасть в корень.
Функции для работы с бинарными деревьями те же, что и для списка. Реализация этих функций приведена в листинге 5.11.
/* Реализация функции работы с деревом
Модуль Tree.с */
#include <string.h>
#include <malloc.h>
#include <stdio.h>
#include "tree.h"
static TREE* create(TREE *pItem)
{
TREE *pNewItem = (TREE *)malloc(sizeof(TREE));
*pNewItem = *pItem;
return pNewItem;
}
void insert(TREE **ppRoot, TREE *pltem)
{
// В качестве ключевого поля в списке используется фамилия.
TREE *pParent = 0, *pCurItem = *ppRoot;
TREE *pNewItem;
int nFound = 0;
// Проходим дерево, пока не достигнут лист или не найден узел
while(pCurItem && !nFound)
{
// Пока не достигнут лист дерева или не найден узел
if(pItem->iField = = pCurItem->iField)
{
// Узел найден — устанавливаем флаг завершения поиска
nFound = 1;
//и увеличиваем значение счетчика числа вхождений
pCurItem->iCount++;
}
else
{
// Запоминаем текущий узел
pParent = pCurItem;
if(pItem->iField < pCurItem->iField)
// Если новый элемент "меньше", чем хранящийся в узле,
// идем по левой ветви
pCurItem = pCurItem->pLeft;
else
// иначе — по правой
pCurItem = pCurItem->pRight;
}
}
// Если элемента нет в дереве
if(!nFound)
{
// Такого элемента в дереве нет — создаем новый узел pNewItem = create(pItem); pNewItem->pLeft = pNewItem->pRight = 0; if(pParent = =0)
{
// Первый узел дерева — создаем его и делаем корнем
*ppRoot = create(pItem);
(*ppRoot)->pLeft = (*ppRoot)->pRight = 0;
}
else {
// Если новый элемент меньше, чем хранящийся в текущем узле,
if(pItem->iField < pParent->iField)
// вставляем его в левую ветвь
pParent->pLeft = pNewItem;
else
// иначе — в правую pParent->pRight = pNewItem; } . } } int remove(TREE **ppRoot, TREE *pltem)
{
TREE *pPreItem = 0, *pPresent = *ppRoot;
TREE *pReplace, *pParent, *pTmp;
int nFound = 0;
// Ищем узел с заданным ключевым полем
while(pPresent && !nFound)
{
if(pItem->iField = = pPresent->iField)
nFound = 1;
else
{
pPreItem = pPresent;
if(pItem->iField < pPresent->iField)
pPresent = pPresent->pLeft;
else
pPresent = pPresent->pRight;
}
if(nFound)
{
if(pPresent->pLeft = = 0)
// Если нет левого сына, то удаляемый элемент
// заменяем на правого сына
pReplace = pPresent->pRight;
else
// Если нет правого сына, то удаляемый элемент
// заменяем на левого сына
if(pPresent->pRight = = 0)
pReplace = pPresent->pLeft;
else {
pParent = pPresent;
pReplace = pPresent->pRight;
pTmp = pPresent->pLeft;
// Ищем самый левый лист
while(pTmp != 0)
{
pParent = pReplace;
pReplace = pTmp;
pTmp = pReplace->pLeft;
}
// Если это корень
if(pParent != pPresent)
{
// Заменяем на найденный лист
pParent->pLeft = pReplace->pRight;
pReplace->pRight = pPresent->pRight;
}
pReplace->pRight = pPresent->pRight;
}
if(pPreItem = 0)
// Если удаляется корень,
//то выбранный ранее сын становится корнем дерева
*ppRoot = pReplace;
else
//В противном случае заменяем значение
// соответствующего указателя
if(pPresent == pPre!tem->pLeft)
pPre!tem->pLeft = pReplace;
else
pPre!tem->pRight = pReplace;
// He забываем освободить память, занимаемую удаленным узлом
free(pPresent);
}
return 1;
}
void destroy(TREE *pRoot)
{
if(pRoot)
{
// Рекурсивно проходим левое поддерево
destroy(pRoot->pLeft);
// Рекурсивно проходим правое поддерево
destroy(pRoot->pRight);
// Удаляем узел
free(pRoot);
}
pRoot = 0;
}
void display(TREE *pRoot)
{
if(pRoot)
{
// Рекурсивно проходим левое поддерево
display(pRoot->pLeft);
// Выводим информацию об узле
printf("\n%s, %s", pRoot->aLastName, pRoot->aFirstName);
printf(" , \t%s", pRoot->aTelephoneNumber);
// Рекурсивно проходим правое поддерево
display(pRoot->pRight);
}
}
Функция destroy для прохождения дерева использует восходящий способ обхода дерева, "спускаясь" сначала вниз по левому поддереву, а функция display — симметричный (последовательный) обход бинарного дерева. В обоих случаях используется рекурсивный вызов соответствующей функции.
Рассмотрим теперь более подробно функцию insert, которая добавляет в дерево новый узел. Прежде всего организуем поиск добавляемого узла — может быть, он уже присутствует в дереве.
Поиск заканчивается либо при достижении листа дерева, либо если узел с таким полем уже есть. После завершения поиска необходимо вставить в дерево новый узел (рис. 5.19), если такого еще там нет (напоминаем, что в переменной pParent хранится указатель на отца вставляемого узла).
Очевидно, что "внешний вид" дерева зависит от порядка включения в него элементов. Но в любом случае это будет упорядоченное бинарное дерево, в котором номер левого сына всегда меньше, а правого — больше, чем номер отца.
Подведем итог: для того чтобы вставить в дерево новый узел, необходимо найти для него отца (иногда называемого родителем) и сделать его соответственно левым или правым сыном, в зависимости от значения ключевого поля нового узла.
Рассмотрим удаление узла из бинарного дерева. В этом случае все несколько сложнее. Естественно, прежде всего необходимо найти узел, который требуется удалить. Делается это точно так же, как и при вставке (функция insert). Результатом поиска может быть один из трех вариантов:
1. Узла с заданным ключом в дереве нет.
2. Узел с заданным ключом имеет не более одного потомка.
3. Узел с заданным ключом имеет двух потомков.
Действия в первом случае очевидны — завершаем работу функции. Второй случай (исключаемый элемент — лист или узел с одним потомком) также достаточно прост.
Трудность возникает, если нужно удалить узел с двумя потомками. В этом случае удаляемый узел нужно заменить либо на самый правый элемент его левого поддерева, либо на самый левый элемент его самого правого поддерева, причем они должны иметь максимум одного потомка.
Рассмотренный алгоритм удаления узла дерева иллюстрируется на рис. 5.20, где приведено исходное дерево (а), из которого последовательно удаляются вершины с ключами 13, 15, 5 и 10.
Тема 4.6. Классы в Visual C++.