Tree traversal

Contents

Traversing the tree and synchronized iteration

Once you have a tree you are happy with, you may want to iterate through it to parse all nodes. The problem is that since a tree is not a linear array, there are more than one way to do so.

The first way is to iterate depth-first. That is, when you meet a node, you are sure that you will parse all of its children and sub-children before parsing its siblings. So you go from the root through the first branch, going as deep as you can until you reach the leaf of that branch. Then you go up just a level, and repeat.

With the tree class, you generate the sequence of indices that will walk you through the tree in a single call:

lineage = tree.example;
t = lineage.subtree(19);
iterator = t.depthfirstiterator;
disp(iterator)
     1     2     3     4     5     6     7     8     9    10    11

You see here that we get a line vector of indices. This allows for a simplified syntax for the iteration. Suppose we want to generate a tree that would keep track of the iteration order. A way to do that is the following:

df_order = tree(t, 'clear'); % Generate an empty synchronized tree
iterator = t.depthfirstiterator; % Doesn't matter whether you call this on |t| or |df_order|
index = 1;
for i = iterator
    df_order = df_order.set(i, index);
    index = index + 1;
end
disp(df_order.tostring)
         1          
  +------+--+       
  |         |       
  2         5       
 ++-+   +---+--+    
 |  |   |      |    
 3  4   6      9    
       ++-+  +-+-+  
       |  |  |   |  
       7  8 10  11  

A second way to traverse the tree is breadth-first. With this iterator, you parse all the nodes of a given depth before going any deeper, regardless of any children a node can have:

bf_order = tree(t, 'clear');
iterator = t.breadthfirstiterator;
index = 1;
for i = iterator
    bf_order = bf_order.set(i, index);
    index = index + 1;
end
disp(bf_order.tostring)
         1          
  +------+--+       
  |         |       
  2         3       
 ++-+   +---+--+    
 |  |   |      |    
 4  5   6      7    
       ++-+  +-+-+  
       |  |  |   |  
       8  9 10  11  

The last way available is a dummy one: it simple output the order in which the nodes have been added to the tree at construction time.

na_order = tree(t, 'clear');
iterator = t.nodeorderiterator;
index = 1;
for i = iterator
    na_order = na_order.set(i, index);
    index = index + 1;
end
disp(na_order.tostring)
         1          
  +------+--+       
  |         |       
  2         5       
 ++-+   +---+--+    
 |  |   |      |    
 3  4   6      9    
       ++-+  +-+-+  
       |  |  |   |  
       7  8 10  11  

Note that in all cases we generated the whole iterating sequence, then used it to actually iterate. If between the call and the iteration you add or remove nodes to the tree, you will completely mess up the calculated iteration order or generate an error. Look above for the section about synchronized tree for the forbidden methods.

Another important perk is that the iteration sequence is the same for synchronized trees, a simple consequence of using copy trees. That is useful and important for trees depicting multiple properties of a common object.

[ lineage, duration ] = tree.example; % 1st one is made of strings only, 2nd one of integers
iterator = lineage.breadthfirstiterator;
for i = iterator(1:7) % print only he first 7 cells
    fprintf('The cell %s has an interphase duration of %d minutes.\n', ...
        lineage.get(i), ...
        duration.get(i))
end
The cell Zygote has an interphase duration of 13 minutes.
The cell AB has an interphase duration of 10 minutes.
The cell P1 has an interphase duration of 17 minutes.
The cell AB.a has an interphase duration of 16 minutes.
The cell AB.p has an interphase duration of 1 minutes.
The cell P2 has an interphase duration of 16 minutes.
The cell EMS has an interphase duration of 14 minutes.

Of course, two plain arrays could have yielded the same result. But for this problem, the hierarchy is important, and this is the benefit of using trees. For instance:

slin = lineage.subtree(19); % Work on a subset
sdur = duration.subtree(19);
resTree = tree(slin, 'clear');
iterator = resTree.depthfirstiterator;
for i = iterator
    resTree = resTree.set(i, sprintf('%s: (%d min)', ...
        slin.get(i), ...
        sdur.get(i)));
end
disp(resTree.tostring)
                                         EMS: (14 min)                                         
               +------------------------------+---------------+                                
               |                                              |                                
          MS: (1 min)                                    E: (2 min)                            
       +------+-------+                       +---------------+---------------+                
       |              |                       |                               |                
 MS.a: (1 min) MS.p: (10 min)           E.a: (16 min)                   E.p: (3 min)           
                                      +-------+-------+               +-------+-------+        
                                      |               |               |               |        
                               E.al: (16 min)  E.ar: (14 min)  E.pl: (13 min)  E.pr: (10 min)  

Finding the shortest path between two nodes

Path finding is classic problems in graphs where you have two nodes and want to find the path to reach one from the other. The path is simply the ordered sequence of the nodes to traverse.

For our tree class, do not expect anything fancy like Dijkstra algorithm: the tree is a very simple specialization of a graph. The findpath method is the only one available right now, and return the shortest path in terms of number of edges:

% Find the path between node 'ABplp' and node 'Ca'
  lineage = tree.example;
  n1 = find(lineage.strcmp('AB.plp'));
  n2 = find(lineage.strcmp('C.a'));
  path = lineage.findpath(n1, n2) %#ok<NOPTS>
  pt = tree(lineage, 'clear');
  index = 1;
  for i = path
    pt = pt.set(i, index);
    index = index + 1;
  end
  disp(pt.tostring)
path =

    14    11     4     2     1    17    18    21    22

                               5                               
           +------------------+------------+                   
           |                               |                   
           4                               6                   
     +-----+-----+                +-------+----------+         
     |           |                |                  |         
     ø           3                7                  ø         
  +--+--+     +--+--+       +----+----+        +-----+--+      
  |     |     |     |       |         |        |        |      
  ø     ø     2     ø       ø         8        ø        ø      
 ++-+  ++-+  ++-+  ++-+   ++---+   +--+--+    ++-+   +--+--+   
 |  |  |  |  |  |  |  |   |    |   |     |    |  |   |     |   
 ø  ø  ø  ø  ø  1  ø  ø   ø    ø   9     ø    ø  ø   ø     ø   
                         ++-+     ++-+  ++-+        ++-+  ++-+ 
                         |  |     |  |  |  |        |  |  |  | 
                         ø  ø     ø  ø  ø  ø        ø  ø  ø  ø 

Orienting yourself in the tree

When traversing a tree, a few methods will help you to know where you are:

Back to main page.