0% found this document useful (0 votes)
313 views6 pages

Complex Data Structures N-Ary Tree PDF

The document discusses complex data structures in C programming, focusing on N-ary trees. It defines an N-ary tree as a node that can have any number of children, with each child also being an N-ary tree. Functions are provided to allocate and free the memory for N-ary tree nodes, as well as manipulate the child nodes by adding and inserting children. Examples are given to demonstrate how to create an N-ary tree containing data points on a circle and generating additional points along the axes.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
313 views6 pages

Complex Data Structures N-Ary Tree PDF

The document discusses complex data structures in C programming, focusing on N-ary trees. It defines an N-ary tree as a node that can have any number of children, with each child also being an N-ary tree. Functions are provided to allocate and free the memory for N-ary tree nodes, as well as manipulate the child nodes by adding and inserting children. Examples are given to demonstrate how to create an N-ary tree containing data points on a circle and generating additional points along the axes.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 6

Chapter 2

Complex Data Structures


2.1

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

CHAPTER 2. COMPLEX DATA STRUCTURES

Figure 2.1: N-ary Tree Data Structure


1
2
3
4
5
6

typedef struct sNaryNode {


void data ;
// P o i n t t o t h e node s d a t a
unsigned i n t
n;
// The number o f c h i l d r e n
struct sNaryNode c h i l d ; // The c h i l d l i s t
} NaryNode ;
typedef NaryNode NaryTree ;

Code 2.1: N-ary Tree Structure Defintion in C


In C, there are many ways in which these can be represented. The data
could be a statically defined data type in the structure. It could be a typedef
that allows the programmer to specify the type before #includeing the library.
In this document, the most generalized paradigm will be used: a void pointer
(void*) will be used to point to any data in a generalized way. The structure
definition for this is included in Code 2.1.
Semantically, a tree can be uniquely identified by its root. Unlike a linked
list, whose head and tail can be tracked in a LinkedList structure to make linked
list operations simpler, nearly all operations on a tree operate recursively, and
as such it is useful to simply represent the tree with the root node. In this way,
referencing a child node is identical to referring to that subtree. This useful
property will be put to use soon.
Code 2.1 defines four things. First, it defines a structure to collapse all of
the data that we need for a node (data, n, child) into a single memory map
called sNaryNode, which is assigned to the type name NaryNode. This type
name is also aliased to NaryTree for reasons which will be explained in the next
section. Inside the memory structure of struct sNaryNode, the first field is
data: this is a pointer to a memory location with an unspecified data type.
As you will remember from Chapter 1, this pointer is simply a number that
the computer understands to reference a specific place in its memory, and the
void designation indicates that the type of data stored in that location will be
specified by the programmer later when the data itself is actually desired. This
gives us the flexibility to create a dynamic data structure which does not need

2.2. N-ARY TREES


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

27

NaryTree c r e a t e N o d e ( i n t c h i l d r e n , void data )


{
// A l l o c a t e s p a c e f o r a new NaryNode i n memory
NaryNode node = ( NaryNode ) c a l l o c ( 1 , s i z e o f ( NaryNode ) ) ;
// S e t t h e c o n t e n t s o f t h e NaryNode a p p r o p r i a t e l y
node>data = data ;
node>n
= children ;
node>c h i l d = ( NaryNode ) c a l l o c ( c h i l d r e n , s i z e o f ( NaryNode ) ) ;
// Return t h e node we i n i t i a l i z e d
return node ;
}
typedef void ( DataFreeFunc ) ( const void ) ;
void f r e e T r e e ( NaryTree t r e e , DataFreeFunc dFree )
{
unsigned i ;
// Don t t r y t h i s w i t h a NULL p o i n t e r
i f ( t r e e == NULL) return ;
// Free t h e c h i l d r e n r e c u r s i v e l y
f o r ( i = 0 ; i < t r e e >n ; ++i )
f r e e T r e e ( t r e e >c h i l d [ i ] , dFree ) ;
// Free t h e c h i l d a r r a y
f r e e ( t r e e >c h i l d ) ;
// Free t h e d a t a i f a f u n c t i o n i s p r o v i d e d
i f ( dFree ) dFree ( t r e e >data ) ;
// And f i n a l l y , f r e e t h e s t r u c t u r e
free ( tree ) ;
}

Code 2.2: N-ary Tree Allocation and Freeing


to be reworked in order to use a new type of data. The structure contains a
variable, n, that indicates how many children it is referencing. This is necessary
for the tree to be truly N-ary, as it facilitates the requirement that each node
can have any number of children.

2.2.1

Allocation and Freeing the Nary Nodes

When creating a dynamic data structure, the most fundamental operations


which must be defined are its creation and removal. In C programming, this
comes in the form of allocation and freeing of memory dynamically. Trees are
defined recursively, so it is sufficient to create two functions to accomplish both
of these tasks.
The code in Code 2.2 is fairly self-explanatory, but a few things bear mentioning. First, be sure to note the interchangeability of NaryNode and NaryTree.

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

CHAPTER 2. COMPLEX DATA STRUCTURES

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

Code 2.3: Example Data Allocation Functions

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. N-ARY TREES

2.2.2

29

Manipulating the Child Array

With the helper functions in Code 2.3, it is possible to do something as simple


as the following:
1
2
3

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

CHAPTER 2. COMPLEX DATA STRUCTURES

i n t appendChild ( NaryNode r o o t , void data )


{
// Increment t h e d e g r e e o f t h e node
r o o t >n++;
// R e a l l o c a t e t h e c h i l d a r r a y ( n c h i l d r e n i n t h e c h i l d a r r a y )
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 ) ) ;
// Add t h e newNode i n t o t h e c h i l d a r r a y and i n c r e m e n t d e g r e e
r o o t >c h i l d [ r o o t >n 1 ] = c r e a t e N o d e ( 0 , data ) ;

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

i n t i n s e r t C h i l d ( NaryNode r o o t , i n t idx , void data )


{
unsigned i ;
// F i r s t , we make s p a c 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 ) ) ;
// Then we r o t a t e e v e r y t h i n g b a c k by one and i n s e r t d a t a
f o r ( i = r o o t >n1; i > i d x ; i )
r o o t >c h i l d [ i ] = r o o t >c h i l d [ i 1 ] ;
r o o t >c h i l d [ i ] = c r e a t e N o d e ( 0 , data ) ;
// 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 i ;
}
void d e l e t e C h i l d ( NaryTree r o o t , i n t idx , DataFreeFunc dFree )
{
unsigned i ;
// D e l e t e t h e c h i l d
f r e e T r e e ( r o o t >c h i l d [ i d x ] , dFree ) ;
// Remove t h e d e f u n c t c h i l d
f o r ( i = i d x ; i < r o o t >n 1 ; ++i )
r o o t >c h i l d [ i ] = r o o t >c h i l d [ i 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 ) ) ;

Code 2.4: Child Insertion and Deletion

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy