Научная статья на тему 'Рекурсивные алгоритмы при работе с бинарными деревьями'

Рекурсивные алгоритмы при работе с бинарными деревьями Текст научной статьи по специальности «Компьютерные и информационные науки»

CC BY
261
59
i Надоели баннеры? Вы всегда можете отключить рекламу.
Ключевые слова
БИНАРНОЕ ДЕРЕВО / РЕКУРСИЯ / BINARY TREE / RECURSION

Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Степович-цветкова Г. С.

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

i Надоели баннеры? Вы всегда можете отключить рекламу.
iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

RECURSIVE ALGORITHMS TO WORK WITH BINARY TREES

Recursive nature of binary trees is analyzed, some recursive algorithms for manipulating binary trees with the evaluation of their complexity are discussed.

Текст научной работы на тему «Рекурсивные алгоритмы при работе с бинарными деревьями»

Ключевые слова: линейные списки, указатели.

Stepovitch-Tsvetkova G.S.

PhD in economic, Ivanovo State University

PECULIARITIES OF WORKING WITH LINEAR LISTS IN IMPLEMENTATION BY POINTERS

Abstract

The peculiarities and some advantages of the pointers implementation of linear lists in comparison with other methods.

Keywords: linear lists, pointers.

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

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

Рассмотрим двунаправленные линейные списки, реализованные с помощью указателей, которые применяются в алгоритмах управления пространством оперативной памяти, в том числе его повторного использования и разделения между несколькими объектами или процессами. К числу таких алгоритмов относятся, например, методы создания связных списков свободного пространства памяти и методы «сборки мусора», при использовании которых происходит подсчет доступной памяти, если появляется нехватка памяти.

Описание одного элемента двунаправленного списка, объединяющего поле данных и указатели на предыдущий и последующий элементы, на языке С++ выглядит следующим образом:

struct Node{ int info;

Node *next; // указатель на следующий элемент Node *prev; // указатель на предыдущий элемент

};

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

begin end

0

Рис. 1 - Двунаправленный линейный список

Над списками могут быть произведены различные операции: добавление элемента; чтение заданного элемента; удаление элемента; упорядочивание списка по ключу и другие.

Особенностью работы со списками в реализации с помощью указателей является необходимость манипулирования различными указателями. Так, например, при удалении элемента списка необходимо произвести пять действий, каждый из которых так или иначе связан с работой над указателями. Первое действие - создание указателя на удаляемый элемент, второе - проверка местоположения удаляемого элемента в списке. В случае если удаляемый элемент находится в начале списка, то необходимо сначала установить указатель на начало списка на следующий за удаляемым элемент, а затем у нового начального элемента списка в поле указателя на предыдущий элемент установить значение ноль. Удаление элемента, расположенного в конце списка происходит аналогично. Чтобы удалить элемент из середины списка, необходимо у соседних элементов переставить указатели друг на друга. После того, как все необходимые указатели переставлены, производим освобождение памяти, занятой удаляемым элементом.

Алгоритм вставки элемента в список также основан на манипуляции с указателями на предыдущий и последующий элементы у тех членов списка, между которыми добавляется новый элемент. При этом важной особенностью такой реализации списков является то, что при добавлении и удалении элементов из списка никакого физического сдвига элементов в памяти производить не нужно, в отличие от реализации списков с помощью массивов. Поэтому важным преимуществом использования линейных списков в алгоритмах является возможность добавления и удаления элементов в списке за время 0(1).

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

Литература

1. Ахо А., Хопкрофт Дж., Ульман Дж. Структуры данных и алгоритмы : пер. с англ. - М. : Издательский дом «Вильямс», 2000. - 384 с.

2. Шилдт Г. Искусство программирования на C++. - СПб. : БХВ-Петербург, 2005. - 496 с.

Степович-Цветкова Г.С.

Кандидат экономических наук, Ивановский государственный университет РЕКУРСИВНЫЕ АЛГОРИТМЫ ПРИ РАБОТЕ С БИНАРНЫМИ ДЕРЕВЬЯМИ

Аннотация

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

Ключевые слова: бинарное дерево, рекурсия.

Stepovitch-Tsvetkova G.S.

PhD in economic, Ivanovo State University

99

RECURSIVE ALGORITHMS TO WORK WITH BINARY TREES

Abstract

Recursive nature of binary trees is analyzed, some recursive algorithms for manipulating binary trees with the evaluation of their complexity are discussed.

Keywords: binary tree, recursion.

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

Бинарные деревья имеют широкое применение в алгоритмах поиска, в различных алгоритмах вычислительной геометрии, поскольку как структура данных бинарное дерево обладает рядом преимуществ. Во-первых, алгоритмически достаточно эффективны алгоритмы поиска элементов по номеру при эффективном исключении из рассмотрения «лишних» поддеревьев. Это возможно, поскольку в дереве наблюдается экспоненциальный рост количества вершин с ростом его глубины, и в таком случае может быть достигнут логарифмический порядок сложности алгоритма поиска, что явно выигрывает по сравнению с линейными структурами данных, в которых подобные алгоритмы основаны на полном переборе. Во-вторых, с технологической точки зрения перестановка вершин в деревьях, а также другие манипуляции с вершинами, могут быть произведены лишь переустановкой связей между узлами, не перемещая их физически.

Существуют различные способы представления деревьев, а именно: в виде массива с индексами предков, в массиве с вычисляемыми адресами потомков, в виде ветвящегося списка и с использованием массива указателей на потомков. Наиболее близко по духу к логике построения структуры данных дерева его представление с помощью ветвящегося списка, у которого каждый элемент имеет указатели на левое и правое поддерево (см. Рис. 1). root

Рис. 1 - Бинарное дерево в виде ветвящегося списка

В таком представлении все дерево задается указателем на единственный его элемент - корневой узел, а каждый из элементов является объединением полей данных и двух указателей - ссылок на потомков, и определяется следующим типом, описанным на языке программирования C++: struct Node{

int info; // поле данных

Node *left; // указатель на левое поддерево

Node *right;// указатель на правое поддерево

};

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

То есть очевидно применение рекурсии при полном обходе дерева для проведения однотипных операций над его узлами, например, для вывода элементов дерева на экран. Отметим, что существует три варианта обхода дерева: в прямом порядке, в симметричном и в обратном порядке. В первом случае каждый узел посещается до того, как посещены его потомки. Симметричный обход предусматривает посещение сначала левого поддерева, затем узла, затем правого поддерева. В случае обратного обхода узлы посещаются «снизу вверх». Например, функция вывода на экран дерева в порядке симметричного обхода выглядит следующим образом: void print_tree(Node *p){ if(P){

print_tree(p->left); //вывод левого поддерева cout<<" "<<p->info<<" "; // вывод корня поддерева print_tree(p->right); //вывод правого поддерева }

}

Формальным параметром, передаваемым функции, является указатель на просматриваемый узел дерева. В самом первом вызове функции в качестве этого узла выступает корневой элемент дерева. Условием выхода из рекурсии является отсутствие узла дерева (p==NULL). Если указатель p не нулевой, то есть очередной узел дерева существует, тогда функция рекурсивно вызывается для левого потомка, затем выводится на экран значение поля данных рассматриваемого узла, после чего функция рекурсивно вызывается для правого поддерева.

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

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

Литература

1. Ахо А., Хопкрофт Дж., Ульман Дж. Структуры данных и алгоритмы : пер. с англ. - М. : Издательский дом «Вильямс», 2000. - 384 с.

2. Романов Е.Л. Беседы о программировании. URL: http://ermak.cs.nstu.ru/cprog/html/081.htm (дата обращения: 23.05.2013).

100

i Надоели баннеры? Вы всегда можете отключить рекламу.