开发者

How to find the number of nodes at level k of a binary tree without using Breadth-first order traversal?

Given this binary tree (actually, the binary tree can be random and dynamic, this is just an example...):

See link for the binary tree image: binary tree example

This are the given facts:

  1. All nodes are connected to their father so that we can traverse from bottom to top (and of course top to bottom too).
  2. All nodes hold information on how many descendants do they have in their left and right part.

The problem is this: I need to find a way to calculate the total number of nodes in level 2 (actually, in any level but for now, let's concentrate on level two). Obviously, the answer is 3 if we know the structure of the binary tree beforehand, but assume that we do not have this image, only the given facts.

Another catch here is we are going to start from a node that is in level 2 (our target level) and not the root. In this example, I've chosen NODE F.

I know that using the Breadth-first order traversal is the straight forward solution but I find it too time consuming since every time that I read a node, I will query it from a database.

I am looking for a more practical approach. But if it is "impossible" to solve this problem due to the insufficient given data, please let me know on what other data should be given in order for this to be solvable. I will assess it if it is feasible.

I am creating a website by the way and using PHP and MySQL. But I only want the concept or the explanation of the s开发者_运维技巧olution, more like an algorithm rather than a programming snippet or code...

I hope someone can answer me...Thank you very much!


The "Breadth-first search" is the way to do it. But, if you don't want to use it i'd recommend to include pointers to brothers in the nodes. If you have to perform this kind of querys usually that would be a great saving.

EDIT:

If you can denormalize your nodes and store in the table the sibilings and level for all your nodes, then you can query without problems.

SELECT * FROM nodes where level=2


an option is too load the full table in php, and create a array tree. if you have a lot of rows (100k+), you can start the process before the table has finished loading, but you need more code to control it.

Alternativly, you can store the result at each node with a trigger.

EDIT: after thinking a bit, and reading the different answers and comments. I consider that the best option is to split the solution:

  1. create a table nodesPerLevel, with 2 colums: level, nbNodes.
  2. create a trigger on insert/delete of each node, that adds/substract 1 to the corresponding level in this new table.
  3. when the result is needed, do select sum(nbNodes) from nodesPerLevel where level >= ?


For a tree in a DBMS you can use the WITH RECURSIVE cte-idiom and clip at the proper recursion level (== the recursion level of the given node, which will probably need another recursive subselect)

EDIT: (added code)

-- 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
    ;

Result:

 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)

Query plan (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)


The only thing other than reading the tree from the root entirely up to level 2 and counting instances is to either set a relation field among instances of the same level or, if your data is too volatile for that approach to perform better, implement a short-timed but fast (memory?) caching system that allows you to rapidly identify nodes of the same level.


As your nodes are stored in database, best solution could be SQL query like this (syntax may be wrong):

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

Query like this will deliver you nodes without extra trip to DB for every single node. But it may be costly for your database (dependeds on database and amount of nodes). You could also store level of node in node itself - this way query would be simpler and require less resources.


Easiest way to do this is

1.Find all paths of a tree

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

Paths are:

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

2.Store in Separate arrays .

3.Access the kth element in each array.

Some sudo code to find all paths :

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)

 }


Once you get your data into a binary search tree, you can recurse it like below. Use recursion to keep track of the depth at each node in the tree.

On each recursive call, push the node at the current level to a hash table, using the level as your key and the node as your value.

In JavaScript, you can use an array or an object literal to do this. I'm storing everything in a JavaScript object literal which is similar to a hash table under the hood. Like this:

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

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

etc...

Before pushing, check to see if the key exists. If it doesn't exist, just insert the node into your hash wrapped in an array.

If a key exists (meaning there is a level collision), invoke collision resolution by simply pushing to the array at that key.

Now you have all the nodes at each level stored inside unique arrays inside an object. It should look like this:

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

Or this if you're storing the entire node:

{ '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 } ] }

You can do what you want with them after this. Sum the values at each level, convert to a linked list or, in your case, just check the length of the array at the desired level. This will give you the number of nodes.

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜