:: алгоритмы  и методы :: :: олимпиадные задачи :: :: связь :: :: о сайте ::
Путь: Сортировка » FAQ » Вопрос 7
  Вопрос 7




====================================================================
> 7. Сортировка двоичным деревом, 'древесная сортировка'.
====================================================================

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

  При добавлении в дерево нового элемента его последовательно сравнивают
с нижестоящими узлами, таким образом вставляя на место.
  Если элемент >= корня - он идет в правое поддерево, сравниваем его уже
с правым сыном, иначе - он идет в левое поддерево, сравниваем с левым,
и так далее, пока есть сыновья, с которыми можно сравнить.

 Вот процесс построения дерева из последовательности 44 55 12 42 94 18 06 67:
   44      44         44          44          44
            \        /  \        /  \        /  \
            55      12  55      12  55      12  55
                                  \           \   \ 
                                   42        42   94

(**) 44              44         (*) 44
    /  \            /  \           /  \
   12  55          12  55         12   55
     \   \        /  \   \       /  \   \ 
     42  94      06  42  94     06  42   94
     /               /              /    / 
   18             18             18    67

 Дерево может быть и более-менее ровным, как на (*), может и иметь всего
две основные ветви (**), а если входная последовательность уже отсортирована,
то дерево выродится в линейный список.

Если мы будем рекурсивно обходить дерево по правилу
"левый сын - родитель - правый сын", то, записывая все 
встречающиеся элементы в массив, мы получим упорядоченное 
в порядке возрастания множество. Hа этом и основана идея сортировки деревом.	

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

/*********** сортировка с помощью двоичного дерева *************/
#include <stdio.h>
#include <stdlib.h>

typedef struct tree
  {
    int a;              // данные
    struct tree *left;  // левый  сын
    struct tree *right; // правый сын
  } TREE;

TREE *add_to_tree(TREE *root, int new_value)
{
   if (root==NULL)  // если нет сыновей - создаем новый элемент
     {
        root = (TREE*)malloc(sizeof(TREE));
        root->a = new_value;
        root->left = root->right = 0;
        return root;
     }
   if (root->a < new_value)          // добавлем ветвь
     root->right = add_to_tree(root->right, new_value);
   else
     root->left  = add_to_tree(root->left,  new_value);
   return root;
}

void tree_to_array(TREE *root, int a[]) // процедура заполнения массива
  {
    static max2=0;                      // счетчик элементов нового массива
    if (root==NULL) return;             // условие окончания - нет сыновей
    tree_to_array(root->left, a);       // обход левого поддерева
    a[max2++] = root->a;
    tree_to_array(root->right, a);      // обход правого поддерева
    free(root);
  }

void sort_tree(int a[], int elem_total)        // собственно сортировка
{
   TREE *root;
   int i;

   root = NULL;
   for (i=0; i<elem_total; i++)        // проход массива и заполнение дерева
      root = add_to_tree(root, a[i]);
   tree_to_array(root, a);             // заполнение массива
}
  /* тестовая программа */
void main() {
   int i;
   /* Это будем сортировать */
   int a[14]={ 0,7,8,3,52,14,16,18,15,13,42,30,35,26 };

   sort_tree(a, 14);
   
   printf("sorted array:\n");
   for (i=0;i<14;i++) printf("%d ",a[i]);   
}
 								   
   Общее быстродействие метода O(nlogn). Поведение неестественно,
 устойчивости, вообще говоря, нет.

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

 Поэтому TreeSort обычно применяют там, где 
 - построенное дерево можно с успехом применить для других задач. 
 - данные уже построены в 'дерево'.                     } не тратится
 - данные можно считывать непосредственно в дерево.     }  лишняя
 Hапример, при потоковом вводе с консоли или из файла.  }  память
     
   Кроме того, ее элементы иногда используются в смежных задачах.
   
> Другое описание метода 'древесной сортровки' можно найти, например,
> у H. Вирта в книге 'Алгоритмы + Структуры Данных = Программы'.

   Основное различие - строится не представление данных в виде дерева,
а 'дерево выбора'. С помощью n/2 сравнений можно определить
наименьший элемент из каждой пары, при помощи следующих n/4 -
наименьший из каждой пары таких наименьших ключей и т.д
   При этом за n-1 сравнений мы можем построить дерево выбора, как 
показано для чисел 44 55 12 42 94 18 06 67:
               
                      06         Это HЕ двоичное дерево в смысле, 
                     /  \           определенном выше.
                    /    \       И это HЕ пирамида, используемая в HeapSort 
                   /      \                
                  /        \                
                 /          \            
                /            \
               12            06       Hа втором шаге мы спускаемся по 
              / \            / \       пути, указанному наименьшим ключом
             /   \          /   \      и исключаем его, последовательно 
            /     \        /     \      заменяя либо на 'дыру' (или ключ 
          44      12     18      06     'бесконечность'(БЕСК), либо на элемент,
         /  \    /  \    / \     / \     находящийся на противоположной
        44  55  12  42  94 18   06 67     ветви промежуточного узла:

     взяли ключ 06 сверху      опять выбрали наименьший с учетом, что любой
             БЕСК                              12           ключ < БЕСК.                
             /  \                             /  \                      
            /    \                           /    \     
           /      \                         /      \                 
          /        \                       /        \                
         /          \                     /          \            
        /            \                   /            \
       12            БЕСК               12            18        
      / \            / \               / \            / \        
     /   \          /   \             /   \          /   \       
    /     \        /     \           /     \        /     \       
  44      12     18     БЕСК       44      12     18      67     
 /  \    /  \    / \     / \      /  \    /  \    / \     / \   
44  55  12  42  94 18 БЕСК 67    44  55  12  42  94 18 БЕСК 67    

  Очевидно, требуется n операций на создание первоначального дерева,
 а затем n шагов, каждый по log n сравнений для выбора нового наименьшего.
                               2 		
  Такой вариант TreeSort довольно сильно отличается от изложенного выше.
Этот алгоритм еще называют 'выбор с замещением' и его можно неплохо 
применять в тех же случаях, что и выше. 

При этом он может быть даже выгоднее предыдущего метода,
хотя бы потому, что не нужно запоминать позицию, на которой мы остановились
при обходе дерева, т.е можно взять верхний элемент -> восстановить дерево ->
проделать некоторые операции -> взять следующий элемент и т.п.  
Обходить же дерево удобно сразу целиком, либо какую-то его большую часть.
Кроме того, самой структура дерева выбора также может быть полезна.
   
   Однако, нужно учесть, что памяти для дерева выбора нужно на
2n-1 элементов (элемент = ключ + указатели), в отличие от n элементов для 
простого дерева. 
   
  Пример использования выбора с замещением можно увидеть в
многофазной сортировке (см соответствующий вопрос). Элементы обоих 
'древесных' сортировок также используются в смежных