Как найти количество узлов на уровне k двоичного дерева без использования обхода в порядке первого порядка? - PullRequest
3 голосов
/ 17 сентября 2011

Учитывая это двоичное дерево (фактически, двоичное дерево может быть случайным и динамическим, это всего лишь пример ...):

См. Ссылку для изображения двоичного дерева: Пример двоичного дерева

Вот данные факты:

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

Проблема заключается в следующем: мне нужно найти способ рассчитать общее количество узлов на уровне 2 (на самом деле, на любом уровне, но сейчас давайте сосредоточимся на уровне два). Очевидно, что ответ равен 3, если мы заранее знаем структуру двоичного дерева, но предположим, что у нас нет этого изображения, только данные факты.

Еще один улов здесь: мы собираемся начать с узла, который находится на уровне 2 (наш целевой уровень), а не с корня. В этом примере я выбрал NODE F.

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

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

Кстати, я создаю сайт и использую PHP и MySQL. Но я хочу только концепцию или объяснение решения, больше похоже на алгоритм, а не на программный фрагмент или код ...

Надеюсь, кто-нибудь ответит мне ... Большое спасибо!

Ответы [ 7 ]

2 голосов
/ 17 сентября 2011

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

РЕДАКТИРОВАТЬ:

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

SELECT * FROM nodes where level=2
1 голос
/ 17 сентября 2011

Для дерева в СУБД вы можете использовать cte-идиому WITH RECURSIVE и вырезать на соответствующем уровне рекурсии (== уровне рекурсии данного узла, который, вероятно, потребует другой рекурсивный подвыбор)

РЕДАКТИРОВАТЬ: (добавленный код)

-- the test table
DROP table tree CASCADE;
CREATE table tree
    ( id CHAR(1) NOT NULL PRIMARY KEY
    , pa CHAR(1) REFERENCES tree(id)
    , le CHAR(1)     REFERENCES tree(id)
    , ri CHAR(1) REFERENCES tree(id)
    );

-- generate some data
INSERT INTO tree (id, pa, le, ri) VALUES
      ( 'a', NULL, 'b', 'c' )
    , ( 'b', 'a', 'd', 'e' )
    , ( 'c', 'a', 'f', NULL )
    , ( 'd', 'b', 'g', NULL )
    , ( 'e', 'b', NULL, 'h' )
    , ( 'f', 'c', NULL, 'i' )
    , ( 'g', 'd', NULL, NULL )
    , ( 'h', 'e', NULL, NULL )
    , ( 'i', 'f', NULL, NULL )
    ;
-- a room with a view
CREATE VIEW reteview AS (
    WITH RECURSIVE re AS (
        SELECT 0 AS lev,id, pa, le, ri FROM tree
        WHERE pa IS NULL
        UNION
        SELECT 1+re.lev AS lev
        , tr.id,  tr.pa,  tr.le,  tr.ri
            FROM tree tr, re
            WHERE re.id = tr.pa
        )
    SELECT * FROM re
    );

/* EXPLAIN ANALYZE */ -- SELECT * FROM reteview ;

/* EXPLAIN ANALYZE */ SELECT re0.*
    FROM reteview re0
    , reteview re1
    WHERE re1.id = 'f'
    AND re0.lev <= re1.lev
    ;

Результат:

 lev | id | pa | le | ri 
-----+----+----+----+----
   0 | a  |    | b  | c
   1 | b  | a  | d  | e
   1 | c  | a  | f  | 
   2 | d  | b  | g  | 
   2 | e  | b  |    | h
   2 | f  | c  |    | i
(6 rows)

План запроса (Postgres 9.01)

                                                                   QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
 Nested Loop  (cost=949.93..2773.55 rows=35167 width=36) (actual time=0.159..0.337 rows=6 loops=1)
   Join Filter: (re.lev <= re1.lev)
   ->  CTE Scan on re  (cost=474.97..566.71 rows=4587 width=36) (actual time=0.034..0.151 rows=9 loops=1)
         CTE re
           ->  Recursive Union  (cost=0.00..474.97 rows=4587 width=36) (actual time=0.021..0.129 rows=9 loops=1)
                 ->  Seq Scan on tree  (cost=0.00..23.10 rows=7 width=32) (actual time=0.012..0.014 rows=1 loops=1)
                       Filter: (pa IS NULL)
                 ->  Hash Join  (cost=2.28..36.01 rows=458 width=36) (actual time=0.018..0.022 rows=2 loops=4)
                       Hash Cond: (tr.pa = re.id)
                       ->  Seq Scan on tree tr  (cost=0.00..23.10 rows=1310 width=32) (actual time=0.001..0.003 rows=9 loops=4)
                       ->  Hash  (cost=1.40..1.40 rows=70 width=12) (actual time=0.003..0.003 rows=2 loops=4)
                             Buckets: 1024  Batches: 1  Memory Usage: 1kB
                             ->  WorkTable Scan on re  (cost=0.00..1.40 rows=70 width=12) (actual time=0.001..0.002 rows=2 loops=4)
   ->  Materialize  (cost=474.97..578.52 rows=23 width=4) (actual time=0.013..0.018 rows=1 loops=9)
         ->  Subquery Scan on re1  (cost=474.97..578.40 rows=23 width=4) (actual time=0.111..0.157 rows=1 loops=1)
               ->  CTE Scan on re  (cost=474.97..578.17 rows=23 width=36) (actual time=0.110..0.156 rows=1 loops=1)
                     Filter: (id = 'f'::bpchar)
                     CTE re
                       ->  Recursive Union  (cost=0.00..474.97 rows=4587 width=36) (actual time=0.008..0.135 rows=9 loops=1)
                             ->  Seq Scan on tree  (cost=0.00..23.10 rows=7 width=32) (actual time=0.002..0.008 rows=1 loops=1)
                                   Filter: (pa IS NULL)
                             ->  Hash Join  (cost=2.28..36.01 rows=458 width=36) (actual time=0.021..0.024 rows=2 loops=4)
                                   Hash Cond: (tr.pa = re.id)
                                   ->  Seq Scan on tree tr  (cost=0.00..23.10 rows=1310 width=32) (actual time=0.001..0.004 rows=9 loops=4)
                                   ->  Hash  (cost=1.40..1.40 rows=70 width=12) (actual time=0.004..0.004 rows=2 loops=4)
                                         Buckets: 1024  Batches: 1  Memory Usage: 1kB
                                         ->  WorkTable Scan on re  (cost=0.00..1.40 rows=70 width=12) (actual time=0.001..0.001 rows=2 loops=4)
 Total runtime: 0.764 ms
(28 rows)
1 голос
/ 17 сентября 2011

опция слишком загружает полную таблицу в php и создает дерево массивов. если у вас много строк (более 100 тыс.), вы можете запустить процесс до завершения загрузки таблицы, но вам нужно больше кода для управления им.

Альтернативно, вы можете сохранить результат на каждом узле с помощью триггера.

РЕДАКТИРОВАТЬ : немного подумав и прочитав разные ответы и комментарии. Я считаю, что лучший вариант - разделить решение:

  1. создать таблицу nodePerLevel, с двумя столбцами: level, nbNodes.
  2. создает триггер при вставке / удалении каждого узла, который добавляет / вычитает 1 до соответствующего уровня в этой новой таблице.
  3. когда нужен результат, выполните select sum(nbNodes) from nodesPerLevel where level >= ?
0 голосов
/ 19 февраля 2015

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

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

В JavaScript вы можете использовать массив или литерал объекта для этого. Я храню все в объектном литерале JavaScript, который похож на хеш-таблицу под капотом. Как это:

level = 0
object[level] = [node1] => { '0': [node, node2] }
object[level] = [node2] => { '0': [node1, node2] }

level = 1
object[level] = [node3] => { '0': [node, node2], '1': [node3] }

и т.д ...

Перед нажатием проверьте, существует ли ключ. Если он не существует, просто вставьте узел в ваш хеш, завернутый в массив.

Если ключ существует (имеется в виду коллизия уровня), вызовите разрешение коллизий, просто нажав массив в этой клавише.

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

{ '0': [ 20 ],
  '1': [ 8, 22 ],
  '2': [ 4, 12, 24 ],
  '3': [ 10, 14 ] } */

Или это, если вы храните весь узел:

{ '0': [ { value: 20, right: [Object], left: [Object] } ],
  '1':
   [ { value: 8, right: [Object], left: [Object] },
     { value: 22, right: [Object], left: null } ],
  '2':
   [ { value: 4, right: null, left: null },
     { value: 12, right: [Object], left: [Object] },
     { value: 24, right: null, left: null } ],
  '3':
   [ { value: 10, right: null, left: null },
     { value: 14, right: null, left: null } ] }

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

BinaryTree.prototype.kNodesAtDepth = function(level) {

    var levels = {};

    var traverse = function(current, depth) {
        if (!current) return null;
        if (!levels[depth]) levels[depth] = [current.value];
        else levels[depth].push(current.value);
        traverse(current.left, depth + 1);
        traverse(current.right, depth + 1);
    };

    traverse(this.root, 0);
    return levels[level].length;
};

//tests
var bst = new BinaryTree();
bst.add(20, 22, 8, 4, 12, 10, 14, 24);
var nodeCount = bst.kNodesAtDepth(2); //3
console.log(nodeCount); //3
0 голосов
/ 13 сентября 2013

Самый простой способ сделать это -

1. Найти все пути дерева

       50
     /    \
   30      60
 /   \    /  \
10   20  55   70

Пути:

50-30-10
50-30-20
50-60-55
50-60-70

2. Хранить в отдельных массивах.

3.Доступ к k-му элементу в каждом массиве.

Код sudo для поиска всех путей:

Inorder(root)
{
 //do the required null checks
         if(root==null)
            return

  1.     PushOnStack(root->info)  

  2.     Inorder(root->left)

  3.     peekAllStackElement (read all the elements in stack and store in array an         reverse)

  4.     PopFromStack(root->info)         

  5.     Inorder(root->right)

 }
0 голосов
/ 17 сентября 2011

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

select count(third.id) from node as first, node as second, node as third where first.id =`your top node id`  and second.parent = first.id and third.parent =  second.id

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

0 голосов
/ 17 сентября 2011

Единственная вещь, кроме полного чтения дерева от корня до уровня 2 и подсчета экземпляров, - это либо установить поле отношения между экземплярами одного уровня, либо, если ваши данные слишком изменчивы, чтобы этот подход работал лучше,реализовать кратковременную, но быструю (память?) кеширование систему , которая позволяет быстро идентифицировать узлы одного уровня.

...