Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЛЕКЦИИ Программирование и основы алгоритмизации...doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
1.5 Mб
Скачать

Идеально сбалансированное дерево

Идеально сбалансированным дерево - это дерево, в котором число узлов (вершин) в левом и правом поддеревьях отличается не более чем на единицу.

Алгоритм равномерного распределения для известного числа вершин формулируют, используя рекурсию.

  1. Взять одну вершину в качестве корня.

  2. Построить тем же способом левое поддерево с вершинами.

  3. Построить тем же способом правое поддерево с вершинами.

Операции с бинарным упорядоченным деревом

  1. Удаление узла из дерева

а) Исключаемый узел – лист. В этом случае надо просто удалить ссылку на данный узел.

б) Из исключаемого узла выходит одна ветвь. В этом случае надо переназначить указатель (пунктир).

в) Из исключаемого узла выходят две ветви. В этом случае на место удаляемого узла надо поставить либо самый правый узел левой ветви, либо самый левый узел правой ветви для сохранения упорядоченности дерева.

Удаляем узел 6:

  1. Обход (просмотр) дерева

Существуют 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;

}

//--------------------------------------------------------------------------------------------