6

I'm writing a binary tree class, and I'm stuck on a levelCount method, where I need to count the number of nodes on a level of the tree. The class and method look something like this:

public class ConsTree<T> extends BinaryTree<T>
{
   BinaryTree<T> left;
   BinaryTree<T> right;
   T data;

   public int levelCount(int level) 
   {
   }
}  

So the idea is that each tree has a tree to its left, a tree to its right, and data. There is an abstract class binarytree and subclasses ConsTree and EmptyTree.

I think I need to use a breadth first search and count the number of nodes once I get to that level, but I'm stuck on how to start. Any guidance here would be helpful. I can provide any other info necessary.

Kara
  • 6,115
  • 16
  • 50
  • 57
user1547050
  • 337
  • 2
  • 7
  • 15

1 Answers1

9

Here's the general approach.

You traverse the tree exactly as you would normally (depth-first, in-order) but you simply pass down the desired and actual level as well, with pseudo-code such as:

def getCountAtLevel (node, curr, desired):
    # If this node doesn't exist, must be zero.
    if node == NULL: return 0

    # If this node is at desired level, must be one.
    if curr == desired: return 1

    # Otherwise sum of nodes at that level in left and right sub-trees.
    return getCountAtLevel (node.left,  curr+1, desired) +
           getCountAtLevel (node.right, curr+1, desired)

#######################################################################
# Get number of nodes at level 7 (root is level 0).
nodesAtLevel7 = getCountAtLevel (rootNode, 0, 7)

It doesn't actually traverse the entire tree since, once it gets to the desired level, it can just ignore everything underneath that. Here's a complete C program that shows this in action:

#include <stdio.h>

typedef struct _sNode { struct _sNode *left, *right; } tNode;

// Node naming uses (t)op, (l)eft, and (r)ight.
tNode TLLL = {NULL,  NULL    }; // level 3
tNode TLLR = {NULL,  NULL    };
tNode TRLL = {NULL,  NULL    };
tNode TRLR = {NULL,  NULL    };
tNode TRRR = {NULL,  NULL    };
tNode TLL  = {&TLLL, &TLLR   }; // level 2
tNode TRL  = {&TRLL, &TRLR   };
tNode TRR  = {NULL,  &TRRR   };
tNode TL   = {&TLL,  NULL    }; // level 1
tNode TR   = {&TRL,  &TRR    };
tNode T    = {&TL,   &TR     }; // level 0 (root)

static int getCAL (tNode *node, int curr, int desired) {
    if (node == NULL) return 0;
    if (curr == desired) return 1;
    return getCAL (node->left,  curr+1, desired) +
           getCAL (node->right, curr+1, desired);
}

int main (void) {
    for (int i = 0; i < 5; i++) {
        int count = getCAL(&T, 0, i);
        printf ("Level %d has %d node%s\n", i, count, (count == 1) ? "" : "s");
    }
    return 0;
}

It builds a tree of the following form (where T means top, L is a left branch and R is a right branch):

            ______T______               (1 node)
           /             \
         TL               TR            (2 nodes)
        /                /  \
     TLL              TRL    TRR        (3 nodes)
    /   \            /   \      \
TLLL     TLLR    TRLL     TRLR   TRRR   (5 nodes)

                                        (0 nodes)

If you compile and run that code, you'll see it gives the correct node count at each level:

Level 0 has 1 node
Level 1 has 2 nodes
Level 2 has 3 nodes
Level 3 has 5 nodes
Level 4 has 0 nodes
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Is there any more effective way? By using this approach, we have to traverse from top to desired level always. – thanhbinh84 Jun 12 '16 at 03:13
  • @thanhbinh84, that's the most efficient way if all you have is the downward pointers. If you're allowed to modify the data structure, you can add sibling pointers which can make things considerably more efficient for that particular task at the cost of making updates a little more expensive - by that, I mean consider the third line of `X`s in the diagram. Each of those, as well as pointing to the their children, also points to the `X` to the right of it (at the same level) as well (the last `X` on the line points to NULL, of course). (continued) ... – paxdiablo Jun 12 '16 at 04:51
  • ... (continued) You still have to find the starting node for a given level (which may be anywhere) but, from that point, it's just following the sibling node. And you could, if desired, store pointers for the first node at each given level to make that even *more* efficient. – paxdiablo Jun 12 '16 at 04:54
  • Thanks @paxdiablo, you are right. I am thinking about storing nodes for each level or things like that so counting or searching will be more effective. It's strange that it is hard to find solution on the Internet as well as some algorithms books. – thanhbinh84 Jun 12 '16 at 08:20
  • There's no need for the two parameters: Curr and desired, only one is enough (decrease desired on each call, by 1, and when it hits 0, that's the level you wanted). Pushing many arguments can get heavy with recursive functions. – YoTengoUnLCD Oct 10 '16 at 21:08
  • Depends entirely on what you mean by heavy. In terms of both speed and stack usage, it's generally not wise to use recursion in situation where there will be a large number of recursive calls anyway, so an added parameter should have minimal effect. However, your approach is sound, you *could* do it with a single parameter. – paxdiablo Oct 10 '16 at 23:32