Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Skripnik.doc
Скачиваний:
1
Добавлен:
28.10.2018
Размер:
210 Кб
Скачать
☆

3 Общедоступные члены класса

C общедоступными членами класса все обстоит намного проще. Доступ к общедоступным членам класса разрешен функциям-членам самого класса, производных классов и обычным пользователям класса.

2.6 Указатель this

Каждый вызов метода устанавливает указатель на объект, по отношению к которому осуществляется вызов. Ссылаться на этот указатель можно посредством ключевого слова this (указатель на самого себя), предоставляющего функциям-членам класса возможность доступа к данным объекта. "Невидимый" указатель this передается функциям-членам в качестве первого аргумента автоматически и не требует явного указания в списке аргументов функции. К отдельным элементам объекта, задействанным в вызове функции, можно обращаться, используя выражение this о < имя члена данных > . Указатель this не доступен для нестатических методов, и для дружественных функций.

  Конструкторы и деструкторы

Конструктор - функция-член, имя которой совпадает с именем класса. Конструктор используется для построения объекта данного класса и автоматически вызывается один раз в момент объявления или создания экземпляра класса до выполнения любой другой функции класса. Конструктор не имеет возвращаемого значения. Деструктор - функция-член, имя которой совпадает с именем класса, а начинается с символа ~ . Деструктор служит для удаления или очистки объекта, и автоматически вызывается тогда, когда объект выходит из области видимости. В отличие от конструкторов деструктор класса не имеет входных параметров и поэтому может быть только один деструктор в классе.

Обычно конструктор и деструктор описываются в разделе public.

{ void tn(int, int, int, int); /* функция */ int n; scanf(" %d",&n); tn(n,1,2,3); }

void tn(int n, int i, int j, int w) /* рекурсивная */

{ if (n>1) /* функция */ { tn (n-1,i,w,j); tn (1,i,j,w); tn (n-1,w,j,i); }

else printf(" \n %d -> %d",i,j);

return ; }

Каждый раз при вызове процедуры tn под параметры n, i, j, w выделяется память и запоминается место возврата. При возврате из процедуры tn память, выделенная под параметры n, i, j, w, освобождается и становится доступной память, выделенная под параметры n, i, j, w предыдущим вызовом, а управление передается в место возврата.

11. Общие операции над деревьями. Процедуры добавления и удаления элемента. Количество листьев и узлов в дереве.

Существует 3 способа обхода бинарного дерева: в прямом порядке, в симметричном порядке, в обратном порядке.

В прямом порядке:

  1. Попасть в корень 2 Пройти в прямом порядке левое поддерево 3 Пройти в прямом порядке правое поддерево

В симметричном порядке:

  1. Пройти в симметричном порядке левое поддерево 2 Попасть в корень 3 Пройти в симметричном порядке правое поддерево

В обратном порядке:

  1. Пройти в обратном порядке левое поддерево

2 Пройти в обратном порядке правое поддерево 3 Попасть в корень

Алгоритм поиска можно записать, пользуясь рекурсивным определением двоичного дерева в рекурсивном виде. Если искомый элемент Item меньше Tree^.Item, то поиск продолжается в левом поддереве, если равен – то поиск считается успешным, если больше – поиск продолжается в правом поддереве; поиск считается неудачным, если мы дошли до пустого поддерева, а элемент найден не был. Программу лучше писать рекурсивную для избежания избыточности. Каждый рекурсивный вызов размещает в стеке локальные переменные Item и Tree, а также адрес точки возврата из подпрограммы. В нерекурсивном варианте можно обойтись одной переменной Item и одной переменной Tree.

Восстановление сбалансированности.

Пусть имеется дерево, сбалансированное всюду, кроме корня, а в корне показатель сбалансированности по модулю равен 2-м. Такое дерево можно сбалансировать с помощью одного из четырех вращений. При этом высота дерева может уменьшиться на 1. Для восстановления баланса после удаления/добавления вершины надо пройти путь от места удаления/добавления до корня дерева, проводя при необходимости перебалансировку и изменение показателя сбалансированности вершин вдоль пути к корню. Можно доказать, что в других вершинах перебалансировка и изменение показателя сбалансированности не понадобятся.

Добавление элемента в сбалансированное дерево.

Схематично алгоритм вставки нового элемента в сбалансированное дерево будет состоять из следующих трех основных шагов:

  1. Поиск по дереву. 2 Вставка элемента в место, где закончился поиск, если элемент отсутствует.

3 Восстановление сбалансированности.

1-ый шаг необходим для того, чтобы убедиться в отсутствии элемента в дереве, а также найти такое место вставки, чтобы после вставки дерево осталось упорядоченным. Первые два шага ничем не отличаются от алгоритма, приведенного в начале разделе.

3-ий шаг представляет собой обратный проход по пути поиска: от места добавления к корню дерева. По мере продвижения по этому пути корректируются показатели сбалансированности проходимых вершин и производится балансировка там, где это необходимо. Добавление элемента в дерево никогда не требует более одного поворота.

Удаление элемента из сбалансированного дерева.

Алгоритм удаления элемента из сбалансированного дерева будет выглядеть так:

1Поиск по дереву. 2 Удаление элемента из дерева. 3 Восстановление сбалансированности дерева.

1-ый шаг необходим, чтобы найти в дереве вершину, которая должна быть удалена. Первых два шага аналогичны удалению элемента, приведенному в начале раздела.

3-ий шаг представляет собой обратный проход от места, из которого взят элемент для замены

12. Указатели в С++, их использование. Динамическое распределение памяти

Переменные, которые хранят адреса других переменных, называются указателями.

Указатель - это переменная целого типа, которая интерпретируется как адрес байта памяти, содержащий некоторый элемент данных.

Суть концепции указателей состоит в том, что вы работаете с адресом ячейки памяти, получая лишь косвенный доступ к ее содержимому, благодаря чему появляется возможность динамически создавать и уничтожать переменные. Хотя с помощью указателей можно создавать чрезвычайно эффективные алгоритмы, сложность программы, в которой используются указатели, значительно возрастает.

В языках C/C++ все переменные должны быть предварительно объявлены. Объявление указателя pimemorycell_address выглядит следующим образом:

int *pimemorycell_address;

Символ * говорит о том, что создается указатель. Этот указатель будет адресовать переменную типа int. Следует подчеркнуть, что в C/C++ указатели могут хранить адреса только переменных конкретного типа. Если делается попытка присвоить указателю одного типа адрес переменной другого типа, возникнет ошибка либо во время компиляции, либо во время выполнения программы.

Обращение к переменной, чей адрес хранится в другой переменной, осуществляется путем помещения перед указателем оператора *: *pimemorycell_address. Такая запись означает, что будет произведен косвенный доступ к ячейке памяти через имя указателя, содержащего адрес ячейки. Например, если выполнить две показанные ниже строки, то переменная imemorycell_contentsпримет значение 20:

pimemorycell_address = &imemorycell_contents;

*pimemorycell_address = 20;       

Чтобы объявить в программе указатель, который будет хранить адрес другого указателя, нужно просто удвоить число звездочек в объявлении:

int  **ppi;

Каждый символ * читается как указатель на. Таким образом, количество указателей в цепочке, задающее уровень косвенной адресации, соответствует числу звездочек перед именем идентификатора. Уровень косвенной адресации определяет, сколько раз следует выполнить операцию раскрытия указателя, чтобы получить значение конечной переменной.

Во время компиляции программ на языках C/C++ память компьютера разделяется на четыре области: программного кода, глобальных данных, стек и динамическую область ("куча"). Последняя отводится для хранения временных данных и управляется функциями распределения памяти, такими как malloc() и free(). Функция mallос () резервирует непрерывный блок ячеек для хранения указанного объекта и возвращает указатель на первую ячейку этого блока. Функция free() освобождает ранее зарезервированный блок и возвращает эти ячейки в динамическую область для последующего резервирования. В качестве аргумента в функцию malloc() передается целочисленное значение, указывающее количество байтов, которое необходимо зарезервировать. Если резервирование прошло успешно, функция malloc() возвращает переменную типа void*, которую можно привести к любому необходимому типу указателя. Концепция использования указателей типа void описана в стандарте ANSI С. Этот спецификатор предназначен для создания обобщенных указателей неопределенного типа, которые впоследствии можно преобразовывать к требуемому типу. Обобщенный указатель сам по себе не может быть использован для обращения к каким-нибудь данным, так как он не связан ни с одним из базовых типов данных. Но любой указатель может быть приведен к типу void и обратно без потери информации.

В следующем фрагменте программы резервируется память для 300 чисел типа float:

float *pf;

int inura_floats = 300;

pf = (float *) malloc(inum_floats * sizeof(float));

В данном примере функция malloc() резервирует блок памяти, достаточный для хранения 300 значений типа float. Выделенный блок надежно отделен от других блоков и не перекрывается ими. Предугадать, где именно он будет размещен, невозможно. Все блоки особым образом помечаются, чтобы система могла легко их обнаруживать и определять их размер. Если блок ячеек больше не нужен, его можно освободить следующей строкой:

free ((void*)   pf);

удаляемого, или от места, из которого удален элемент, если в замене не было необходимости.

Операция удаления может потребовать перебалансировки всех вершин вдоль обратного пути к корню дерева, то есть порядка log(N) вершин. Доказательство того, что порядок числа вершин имеет такой аналитический вид, смотрите в следующем разделе.

Анализ операций над сбалансированным деревом.

Понятно, что в случае полного двоичного дерева (наилучший случай) мы получим сложность T(log(n)) (на каждом шаге размер дерева поиска будет сокращаться вдвое). Рассмотрим минимальное сбалансированное дерево (худший случай). Таким будет дерево, у которого для каждой вершины высота левого и правого поддеревьев различаются на 1. Для такого дерева можно записать следующую рекуррентную формулу для числа вершин (h – высота дерева):

Покажем, что h<log2(Nh). Для этого необходимо и достаточно показать, что 2h>Nh. Докажем последнее методом математической индукции.а) h=0: 20>N0=0; б) h=1: 21>N1=1; в) h>1: Пусть 2h-2>Nh-2 и 2h-1>Nh-1. Тогда 2h-2+2h-1>Nh-2+ Nh-1.

И далее получаем 2h>1+2h-2+2h-1>1+Nh-2+ Nh-1=Nh, что и требовалось доказать.

Таким образом, мы доказали, что алгоритмы поиска/добавления/удаления элементов в сбалансированном дереве имеют сложность T(log(n)).

Г.М.Адельсон-Вельский и Е.М.Ландис доказали теорему, согласно которой высота сбалансированного дерева никогда не превысит высоту идеально сбалансированного дерева более, чем на 45%.

// симметричное рекурсивное прохождение узлов дерева

template <class T>

void Inorder (TreeNode<T> *t, void visit(T& item))

{ // рекурсивное прохождение завершает-ся на пустом поддереве

if (t != NULL)

{ Inorder(t->Left(), visit); // спуститься по левому поддереву

visit(t->data); // посетить узел

Inorder(t->Right(), visit); // спуститься по правому поддереву }}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]