Бинарное дерево представляет собой структуру, в которой каждый узел (или вершина) имеет не более двух узлов-потомков и в точности одного родителя. Самый верхний узел дерева является единственным узлом без родителей; он называется корневым узлом. Бинарное дерево
с N узлами имеет не меньше [log2N + 1] уровней (при максимально плотной упаковке узлов).
Если уровни дерева занумеровать, считая что корень лежит на уровне 1, то на уровне с номером К лежит 2К-1 узел. У полного бинарного дерева с j уровнями (занумерованными от 1 до j) все листья лежат на уровне с номером j, и у каждого узла на уровнях с первого по j — 1
в точности два непосредственных потомка. В полном бинарном дереве с j уровнями 2j — 1 узел.
*-Нравится статья? Кликни по рекламе! :)
Бинарные деревья поиска обычно применяются для реализации множеств и ассоциативных массивов. И применяются для быстрого поиска информации. Например в статье Двоичный (бинарный) поиск элемента в массиве мы искали во встроенной в Python структуре данных, типа list, а могли реализовать бинарное дерево. Двоичные деревья, как и связные списки, являются рекурсивными структурами.
Различные реализации одного и того же бинарного дерева
Существуют следующие разновидности бинарных деревьев:
- полное(расширенное) бинарное дерево - каждый узел, за исключением листьев, имеет по 2 дочерних узла;
 - идеальное бинарное дерево - это полное бинарное дерево, в котором все листья находятся на одной высоте;
 - сбалансированное бинарное дерево - это бинарное дерево, в котором высота 2-х поддеревьев для каждого узла отличается не более чем на 1. Глубина такого дерева вычисляется как двоичный логарифм log(n), где n - общее число узлов;
 - вырожденное дерево - дерево, в котором каждый узел имеет всего один дочерний узел, фактически это связный список;
 - бинарное поисковое дерево (BST) - бинарное дерево, в котором для каждого узла выполняется условие: все узлы в левом поддереве меньше, и все узлы в правом поддереве больше данного узла.
 
Путь - это расстояние от корня дерева до какого либо узла. Если число листьев полного бинарного дерева обозначить как S, а число остальных узлов обозначить как N, то справедлива формула:
S = N +1  | 
Если путь от корневого узла до листа обозначить как внешний путь, а путь от корневого узла до НЕлиста обозначить как внутренний путь, тогда сумма всех внешних путей для дерева, изображенного на рисунке, примере полного дерева, ниже:
E=3+3+2+3+4+4+3+3=25,  | 
а сумма внутренних путей будет равна:
I=2+1+0+2+3+1+2=11.  | 
и тогда будет справедлива формула:
E=I+2n, где n - число узлов.  | 
Пример расширенного дерева
Можно сформулировать следующие задачи:
- сконструировать бинарное дерево таким образом, чтобы сумма путей была минимальной, так как это сокращает время вычислений для различных алгоритмов.
 - сконструировать полное расширенное бинарное дерево таким образом, чтобы сумма произведений путей от корневого узла до листьев на значение листового узла была минимальной.
 
Листинг 1. Пример алгоритма Хаффмана
2 3 5 7 11 13 17 19 23 29 31 37 41
    5 5 7 11 13 17 19 23 29 31 37 41
     10 7 11 13 17 19 23 29 31 37 41
       17    24 17 19 23 29 31 37 41
             24 34 19 23 29 31 37 41
             24 34    42 29 31 37 41
                      42 53 65 37 41
                      42 53 65    78
                         95 65    78
                         95      143
                                 238
Бинарное дерево, построенное по алгоритму Хаффмана
Более полную статью, по кодам Хаффмана читай в моей более ранней статье.
Реализация:
До этого, в своих статьях я показывал силу функционального программирования Python.
Теперь, пришло время, показать ООП в действии, создав новую структуру данных Tree, состоящую из других структур, типа Node.
Сразу скажу, что код взят с сайта IBM с минимальными изменениями и дополнениями. С моей точки зрения данный класс является недееспособным с точки зрения добавления элементов,по причине того, что мы сами должны строить дерево, помня структуру у себя в голове, а ведь мы можем и ошибиться. И позже я напишу так, как мне кажется верным, где добавление элемента является рекурсивным проходом по дереву и поиску подходящего места. А пока, разберем, что есть:
class node:
    def __init__(self, data = None, left = None, right = None):
        self.data   = data
        self.left   = left
        self.right  = right
    def __str__(self):
        return 'Node ['+str(self.data)+']'
#/* класс, описывающий само дерево */  
class Tree:
    def __init__(self):
        self.root = None #корень дерева
# /* функция для добавления узла в дерево */
    def newNode(self, data):
        return node(data,None,None)
Дальше, все функции расширяют класс Tree.Высота бинарного дерева
Для определения высоты дерева потребуется пройти от корня сначала по левому поддереву, потом по правому, сравнить две этих высоты и выбрать максимальное значение. И не забыть к получившемуся значению прибавить единицу (корневой элемент). Мы реализуем её в привычном уже нам рекурсивном виде.
# /* функция для вычисления высоты дерева */
    def height(self,node):
      if node==None:
          return 0
      else:
        lheight = self.height(node.left)
        rheight = self.height(node.right)
    
        if lheight > rheight:
            return(lheight+1)
        else:
          return(rheight+1)
«Зеркальное» отражение бинарного дерева
Когда два дерева являются зеркальным отражением друг друга, то говорится, что они симметричны. Для получения «зеркальной» копии дерева сначала выполняется проверка на наличие у корня дерева дочерних узлов, и если эти узлы есть, то они меняются местами. Потом эти же действия рекурсивно повторяются для левого и правого дочерних узлов. Если существует только один дочерний узел, тогда можно переходить на один уровень ниже по дереву и продолжать.
#    /* функция для зеркального отражения дерева */
    def mirrorTree(self, node):
      if  node.left and node.right:
        node.left,node.right=node.right,node.left
        self.mirrorTree(node.right)
        self.mirrorTree(node.left)
      else:
        if node.left==None and node.right:
            return self.mirrorTree(node.right)
        if node.right==None and node.left:
            return self.mirrorTree(node.left)
Проверка наличия узла в бинарном дереве
Нужно только учесть, что данная функция не работает для зеркального отображения дерева!
#    /* функция для проверки наличия узла */
    def lookup(self, node, target):
        if node == None:return 0
        else:
            if target == node.data: return 1
            else:
                if target < node.data: return self.lookup(node.left, target)
                else: return self.lookup(node.right, target)
Ширина бинарного дерева
Под шириной дерева понимается максимальное количество узлов, которые расположены на одной высоте. Чтобы определить ширину дерева, достаточно просто добавить счетчик в уже рассмотренный алгоритм для определения высоты дерева.
# /* функция для вычисления ширины дерева */
    def getMaxWidth(self,root):
      maxWdth = 0
      i = 1
      width = 0 ;
      h = self.height(root)
      while( i< h):
        width =   self.getWidth(root, i)
        if(width > maxWdth):
            maxWdth  = width;
        i +=1    
      return maxWdth;
    def getWidth(self, root, level):
      if root == None:
        return 0
      if level == 1: 
        return 1
      elif level > 1:
          return self.getWidth(root.left, level-1) +  self.getWidth(root.right, level-1)
Распечатка дерева:
# /* функция для распечатки элементов на определенном уровне дерева */
    def printGivenLevel(self, root, level):
      if root == None:
        return
      if level == 1:
        print ("%d " % root.data)
      elif level > 1:
          self.printGivenLevel(root.left, level-1);
          self.printGivenLevel(root.right, level-1);
# /* функция для распечатки дерева */
    def printLevelOrder(self, root):
      h = self.height(self.root)
      i=1
      while(i<=h):
        self.printGivenLevel(self.root, i)
        i +=1
Сравнение бинарных деревьев
Вынесите её за пределы класса только)
def sameTree(a, b):
    if a==None and b==None: return 1
    elif a and b:
        return(
                a.data == b.data and
                sameTree(a.left, b.left) and
                sameTree(a.right, b.right)
                )
    return 0
Количество узлов в бинарном дереве
Вычислить количество узлов в бинарном дереве также можно с помощью рекурсии.
Хотя с точки зрения производительности и принципов ООП, правильнее встроить счетчик в сам объект.
def size(node):
    if node==None:return 0;
    return(size(node.left) + 1 + size(node.right));
Немножко о производительности
Я протестировал наш класс на поиск. К сожалению, данная его реализация проиграла бинарному поиску по списку, описанному ранее. Я тестировал, запуская в 100000 цикле поиска элемента со значением 5 и результат нашего класса
400003 function calls (100003 primitive calls) in 3.481 seconds
против
200003 function calls in 1.791 seconds
Я считаю, что причина в рекурсивном исполнении данного метода + реализации на Python, а не Си)
Вывод:
Организация данных с помощью бинарных деревьев часто позволяет значительно сократить время поиска нужного элемента. Поиск элемента в линейных структурах данных обычно осуществляется путем последовательного перебора всех элементов, присутствующих в данной структуре. Поиск по дереву не требует перебора всех элементов, поэтому занимает значительно меньше времени. Максимальное число шагов при поиске по дереву равно высоте данного дерева, т.е. количеству уровней в иерархической структуре дерева.
Вот и все, в заключение хочу сказать, что если какие то вопросы не раскрыты, не понятны или не оптимальны, просьба не стесняться и писать в комментариях!)
Используемая литература:
- Дж. Макконнелл - Основы современных алгоритмов.
 - Структуры данных: бинарные деревья. Часть 1
 - Работа со структурами данных в языках Си и Python: Часть 6. Двоичные деревья
 - Может быть полезным Обходы бинарных деревьев
 
Собственно вот: при базовой реализации дерева нужно добавить функцию вставки, потому что addNode только создает узел, но не привязывает его к дереву.
ОтветитьУдалитьА как пользоваться этим деревом?? Можно примеры?
ОтветитьУдалитьУ Вас на сайте проблемка: пикчи не прогружаются, к огромному сожалению.
ОтветитьУдалить