Complex Data Structures N-Ary Tree PDF
Complex Data Structures N-Ary Tree PDF
Introduction
One strength of the C programming language lies in the ability to abstract functionality away from the code that needs it. This is one fundamental advantage
of using any of the higher level languages. . . in assembly, for instance, the nice
structure code so frequently employed in C would be a mess of hard-coded offsets into memory that is difficult to keep track of by hand. The struct concept
in C allows us to not only aggregate the simple data structures discussed in the
last chapter, but to manage data which is much more complicated structurally.
In this chapter, a few complex data structres will be explored.
2.2
N-ary Trees
An N-ary Tree is a very flexible data structure. N-ary nodes are one fundamental
building block of a generalized graph strucure, but we will stick to the case of a
directed acyclic graph (tree) for this discussion. An N-ary tree itself is a node
with any number of children whose children are also N-ary trees. A tree can
be N-ary and always have a constant number of children and still be N-ary, as
long as the nodes could have any number of children. If instead it can only
have 26 children, for example, it would be a 26-ary tree. It is also important to
note that the number of children a node has does not depend on the number of
children that its children have or that its parent hasthey are separate and any
such constraint would be dictated by the application, not the data structure.
Figure 2.1 shows the basic visual representation of an N-ary Tree. Notice
that every node in the tree has three fundamental pieces of data:
The data being stored in the node
The number of children the node currently references
The dynamic collection of child references themselves
25
26
27
2.2.1
28
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void c r e a t e I n t D a t a ( i n t data )
{
int ptr = ( int ) c a l l o c (1 , sizeof ( int ) ) ;
p t r = data ;
return p t r ;
}
void c r e a t e D o u b l e D a t a ( double data )
{
double p t r = ( double ) c a l l o c ( 1 , s i z e o f ( double ) ) ;
p t r = data ;
return p t r ;
}
void c r e a t e S t r i n g D a t a ( const char s t r )
{
return s t r d u p ( s t r ) ;
}
typedef struct { f l o a t x , y ; } P o i n t ;
void c r e a t e P o i n t D a t a ( f l o a t x , f l o a t y )
{
Point ptr = ( Point ) c a l l o c (1 , sizeof ( Point ) ) ;
ptr >x = x ;
ptr >y = y ;
return p t r ;
}
They will both be used in this book, depending on whichever data type makes
more sense in context; this does not change the data in any way and is simply
to make code easier to read. Second, notice that the data for the tree node
must be passed into the createNode function as a void* already: this can get
clunky, and may set off a red flag that this would be a good place to write a
wrapper function for whatever datatype you are using, and you would be correct. A few examples of these are included in Code 2.3. Third, notice that the
freeTree function does very little work for itself. It calls upon other instances
of itselfthe recursive nature of the tree data structureto free themselves,
then it simply frees the child array and calls the programmer-passed dFree
function pointer to free the nodes data if one has been provided before freeing
the whole node. For most cases, simply passing &free will suffice for the dFree
parameter, as this will simply free the data stored at data. All of the example
data creation functions shown in Code 2.3 can be freed in this way. This will
not be true in the rare instance where you only want part of the data freed or if
the data points to a nested data structure whose children also need to be freed
before the main structure itself to prevent memory loss.
2.2.2
29
NaryTree t r e e = c r e a t e N o d e ( 2 , c r e a t e I n t D a t a ( 1 0 ) ) ;
t r e e >c h i l d [ 0 ] = c r e a t e N o d e ( 0 , c r e a t e I n t D a t a ( 5 ) ) ;
t r e e >c h i l d [ 1 ] = c r e a t e N o d e ( 1 , c r e a t e I n t D a t a ( 1 5 ) ) ;
But thats not particularly fun or very N-ary. In order for it to be easy to
manipulate the children, we need to be able to add and remove them at will.
Dynamic memory allocation is our friend here, especially realloc. The child
append function is simple enough, but the child insert function is a little more
interesting. Make sure you understand it before you continue on, both how it
works and how you might use it. The code for these functions is included in
Code 2.4.
With these functions defined, it becomes possible to do far more with the
N-ary tree code that makes sense on an intuitive level. For instance, to insert
the points on a circle into an nary tree, and then generate inner points along
the axes:
1
2
3
4
5
6
7
8
9
10
NaryTree c i r c l e = c r e a t e T r e e ( 0 , NULL) ;
f l o a t rad ; unsigned i , l e n ;
f o r ( rad = 0 ; rad < 2M PI ; rad += M PI / 1 6 )
appendChild ( r o o t , c r e a t e P o i n t D a t a ( c o s ( rad ) , s i n ( rad ) ) ) ;
f o r ( l e n = c i r c l e >n , i = 0 ; i < l e n ; ++i )
i f ( i % 8 == 0 )
appendChild ( r o o t , c r e a t e P o i n t D a t a (
( ( P o i n t ) r o o t >c h i l d [ i ] )>x / 2 ,
( ( P o i n t ) r o o t >c h i l d [ i ] )>y / 2
));
The advantage in this particular situation is that the coder doesnt need to
precalculate how many points are necessary, and the code can adjust automatically should the number of necessary points change. This sort of paradigm also
works doubly well when the user does not know how many data points will be
necessary. For instance, when writing a generalized string processing algorithm,
setting a fixed upper bound for number of child nodes would limit the flexibility of the algorithm. Using an N-ary node removes this limitation, as well as
minimizing the extraneous memory being used.
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Return t h e i n d e x o f t h e c h i l d we j u s t i n s e r t e d
return r o o t >n 1 ;
// And f i n a l l y , r e a l l o c a t e
r o o t >n;
r o o t >c h i l d = ( NaryNode ) r e a l l o c ( r o o t >c h i l d ,
( r o o t >n ) s i z e o f ( NaryNode ) ) ;