AVL tree function templates
[Miscellaneous functions]

This module contains a template to build an AVL-tree. More...

Functions

static AVL_NODE AVL_Search (AVL_NODE root, AVL_KEYT skey, AVL_USER user)
 Find a key on the tree.
static AVL_NODE AVL_Insert (AVL_NODE *root, AVL_PATH *path, AVL_NODE node, AVL_KEYT key, AVL_USER user)
 Inserts a new node with the given key to the tree.
static AVL_NODE AVL_Delete (AVL_NODE *root, AVL_PATH *path, AVL_KEYT key, AVL_USER user)
 Deletes the given node from the tree.
static void AVL_Walk (AVL_NODE root, AVL_PATH *path, AVL_USER user)
 Initialises a tree walk.
static AVL_NODE AVL_Step (AVL_PATH *path, AVL_USER user)
 One step on the tree walk.
static AVL_NODE AVL_First (AVL_NODE root, AVL_USER user)
 Returns the node with the smallest key.
static AVL_NODE AVL_Last (AVL_NODE root, AVL_USER user)
 Returns the node with the largest key.
static AVL_NODE AVL_Next (AVL_NODE root, AVL_PATH *path, AVL_KEYT refk, AVL_USER user)
 Finds the node with smallest key that is larger than the given key.

Detailed Description

This module contains a template to build an AVL-tree.

The AVL tree is a self-balancing binary search tree, named after Adelson-Velskiy and Landis, the Russian mathematicians who invented it in 1962. It allows you to keep data in a sorted manner. Finding, inserting and deleting a node all take O( log2( N ) ) time even in worst case, therefore the AVL tree is an efficient tool to implement small "databases" in embedded systems.

This implementation is actually a template. It does not assume much about your information and how you store it. In fact, it is not a usable C implementation on its own, you must write small wrapper functions that will be the actual API for the handling of the tree and you need macros or small inline functions to access your chosen data format. However, all the algorithmic working is in the library, so you do not have to know how AVL trees work to use it.

Nodes are ordered by a key. The keys must be unique, the same key can not be on the tree multiple times. The routines will refuse to insert the same key twice.
Apart from this key and your own data, your nodes need to store three more pieces of information that the AVL tree uses.
The first is an integer type that must be wide enough to store the height of the tree.
The other two information fields in your node are references to two other nodes (the subtrees under this node). Note the word 'reference'. Normally these would be pointers. However, the library does not assume that you have a tree that is built from node structures stored in memory. The references can be pointers, but it is not a strict requirement. The importance of this will be explained later.

To use this template you do the following:

This is what you have to define before you include misc/avl.h:

The prototype of these functions is the following:

static AVL_NODE AVL_GET_L( AVL_NODE node, AVL_USER user );
static AVL_NODE AVL_GET_R( AVL_NODE node, AVL_USER user );
static unsigned AVL_GET_H( AVL_NODE node, AVL_USER user );

static void AVL_SET_L( AVL_NODE node, AVL_NODE tree, AVL_USER user );
static void AVL_SET_R( AVL_NODE node, AVL_NODE tree, AVL_USER user );
static void AVL_SET_H( AVL_NODE node, unsigned height, AVL_USER user );

static int  AVL_CMP( AVL_NODE node, AVL_KEYT refk, AVL_USER user );

If the functions are simple, you should specify them as inline, or, if you prefer macros over inlined functions, you can use those instead of functions.

After definig these, you include misc/avl.h. That file, in turn, defines a type and a bunch of functions. The functions are the basic AVL tree managing functions. You can then use them to implement your own API functions with very little code. Most importantly, it is your responsibility to allocate and prepare new nodes, to handle the information in the node and to deallocate deleted nodes. Furthermore, if you run in a multitasking environment, then the protection from simultaneous access is on your shoulders too.

You also have to provide a workspace for the tree handling routines. That workspace is a structure that has the type of AVL_PATH. Among a couple of other fields it contains an array of AVL_NODE references. The size of the array is the value you defined by the AVL_PLEN macro. The following table shows you the minimum value of AVL_PLEN for a given maximum value of nodes.
The first column contains the maximum number of nodes on your tree, the second column contains the minimum value that you should use when you define AVL_PLEN and the third column is the minimum width, in bits, of the height field in your node (see the examples).

      N    P   W                   N    P   W                   N    P   W

      1    2   1               2,582   16   4           2,178,307   30   5
      3    3   2               4,179   17   5           3,524,576   31   5
      6    4   2               6,763   18   5           5,702,885   32   5
     11    5   3              10,944   19   5           9,227,463   33   6
     19    6   3              17,709   20   5          14,930,350   34   6
     32    7   3              28,655   21   5          24,157,815   35   6
     53    8   3              46,366   22   5          39,088,167   36   6
     87    9   4              75,023   23   5          63,245,984   37   6
    142   10   4             121,391   24   5         102,334,153   38   6
    231   11   4             196,416   25   5         165,580,139   39   6
    375   12   4             317,809   26   5         267,914,294   40   6
    608   13   4             514,227   27   5         433,494,435   41   6
    985   14   4             832,038   28   5         701,408,731   42   6
  1,595   15   4           1,346,267   29   5       1,134,903,168   43   6


Now that you defined everything and the library also defined everything, you can write your API functions. These will use the functions provided by the misc/avl.h file. They are formally specified by the function specification section, so that's not repeated here.

A little explanation is in order with regards to that mysticism about references instead of pointers, bit-width of integers and the obscure user parameter of every function. There are several issues that warrant all that magic. Instead of giving you the verbiage about them, you will find some example code below that shows you how to use them. Hopefully these will make it clear why the cruft is there. Note that although the examples seem quite long, most of it is explanatory comments. The actual code you have to write is just a few tens of lines.

Basic AVL tree example
/*
*   This example shows a very simple implementation of an AVL tree.
*   Simplicity is the key. We assume a proper operating system with
*   a heap, unlimited memory and we provide minimal functionality.
*   We use integer keys.
*/

/****************************************************************************/
/*       The following would go into our header file, called tree.h         */
/****************************************************************************/

#ifndef _TREE_H
#define _TREE_H


/*
*   This is our node structure
*/

typedef struct s_tree {

    // AVL tree related data

    struct s_tree   *left;
    struct s_tree   *right;
    int             height;

    // The integer key

    int             key;

    // Then all the application dependent information
    // that is associated with the key.

    ...

} MYDATA;

/*
*   Initialise the tree
*/

void    MyDataInit( void );

/*
*   Inserts a new key to the tree, returns the pointer to
*   the new structure that has been created and inserted
*   onto the three. If the key is already on the tree, then
*   it returns NULL.
*/

MYDATA  *MyDataInsert( int key );

/*
*   Finds a node on the tree with the given key.
*   Returns the node pointer or NULL if the key is not found.
*/

MYDATA  *MyDataFind( int key );

/*
*   Deletes the given key from the tree.
*/

void    MyDataDelete( int key );

#endif


/****************************************************************************/
/*       This is the implementation C file                                  */
/****************************************************************************/

#include <malloc.h>             // System malloc
#include "tree.h"               // The include file above

/*
*   What we need from the templates
*/

#define AVL_USE_DELETE          // We will use the node deletion feature


/*
*   Define what the templates need
*/

typedef MYDATA *AVL_NODE;       // Define the node reference type
typedef int     AVL_KEYT;       // Define the key reference type
#define AVL_USER    int         // We won't use it, but must define it anyway
#define AVL_PLEN    32          // We're OK up to 5 million nodes
#define AVL_NULL    NULL        // We use pointers, so AVL_NULL is NULL


/*
*   Define the getting functions. We decided to use macros.
*   We do not use the user parameter but of course it should
*   be defined as a macro argument.
*/

#define AVL_GET_L( node, user )     node->left
#define AVL_GET_R( node, user )     node->right
#define AVL_GET_H( node, user )     node->height


/*
*   Define the setting functions. Again, they are macros.
*/

#define AVL_SET_L( node, tree, user )   node->left   = tree
#define AVL_SET_R( node, tree, user )   node->right  = tree
#define AVL_SET_H( node, newh, user )   node->height = newh


/*
*   Define the comparison function. It is defined as a function.
*/

static inline int   AVL_CMP( AVL_NODE node, AVL_KEYT key, AVL_USER user )
{
    (void) user;    // We don't use it

    if ( node->key > key ) return +1;
    if ( node->key < key ) return -1;
    return 0;
}

/*
*   Now that we defined everything, we get the templates
*/

#include <misc/avl.h>

/*
*   This is where our implementation start. First, we need the root of
*   the tree and the workspace for the templates.
*/

static AVL_NODE root;
static AVL_PATH work;

/*
*   Tree initialisation
*/

void    MyDataInit( void )
{
    // All we have to do is to make the root empty

    root = NULL;
}

/*
*   Find a node by a key
*/

MYDATA  *MyDataFind( int key )
{
    // The functionality is already provided by the template.
    // All we do is to also pass the tree root and 0 for the
    // unused user parameter.

    return AVL_Search( root, key, 0 );
}

/*
*   Insert a new key. We return a pointer to the new node.
*   We return NULL if the key is already on the tree.
*/

MYDATA  *MyDataInsert( int key )
{
MYDATA  *node;
MYDATA  *temp;

    // Allocate a structure. In real code we'd check the return
    // value but now we simply assume that malloc() can't fail.

    node = malloc( sizeof( MYDATA ) );

    // Now we insert it to the tree.
    // We get back AVL_NULL if it went OK. Any other value
    // indicates that the key is already on the tree.

    temp = AVL_Insert( &root,           // Pointer to the root pointer
                       &work,           // Workspace
                       node,            // The new node to insert
                       key,             // The key for the node
                       0 );             // Unused user data

    if ( temp != AVL_NULL ) {

        // The key is already on the tree
        // Release the allocated memory and report error

        free( node );
        return NULL;
    }
    else {

        // The insertion was successful.
        // We have to fill the key field and return the new node.

        node->key = key;
        return node;
    }
}

/*
*   Delete a key from the tree.
*   We release the structure here, assuming that no other user data
*   fields need freeing (i.e. your node does not countain pointers
*   to malloc()-ed memory).
*/

void    MyDataDelete( int key )
{
MYDATA  *node;

    // The functionality is already provided, we just pass the
    // additional parameters.

    node = AVL_Delete( &root,       // Pointer to the root pointer
                       &work,       // Workspace
                       key,         // The key to delete
                       0 );         // Unused user parameter

    if ( node ) {

        // The key was found and the node was removed from
        // the tree. We just release the associated memory.

        free( node );
    }
    else {

        // The key was not found on the tree. We have nothing to do.

        ;
    }
}

Memory starvation example
/*
*   In this example we assume that we have very limited memory.
*   The example demonstrates a real-life problem which was solved the
*   way described below. In this example header files and and other
*   niceties are omitted, only the actual solution is shown.
*
*   The situation:
*   An application running on an NXP LPC23xx microcontroller.
*   The chip has 16K general purpose RAM. Furthermore, it has
*   an Ethernet controller and a USB controller, neither of which
*   is used by the application. The USB has a private 8K RAM block
*   which can be used as general memory if the USB is not in use.
*   The same is true for the Ethernet block, except that it has
*   16K RAM.
*
*   The application has to store information about some objects.
*   The max number of objects is TBD, but it must be at least 1000.
*   The unique key is 5 bytes. The application related data (on top
*   of the key) is 16 bytes.
*
*   The "database" is fairly often changed, thus something that is fast
*   to insert, delete and look up nodes is needed. The AVL tree meets
*   these requirements.
*
*   However, if the tree needs 2 pointers (4 bytes each) plus 1 byte
*   height plus 5 bytes key plus 16 bytes application data, then the
*   data structure is 30 bytes long. That will be rounded up to 32 bytes
*   by the compiler due to alignment requirements, so it would take 32K
*   to store the data. That would leave 8K for the rest of the application,
*   which is a bit tight. Naturally, the 16 bytes data plus the 5 bytes
*   key can't be saved. However, the 9 bytes AVL administrative info seems
*   to be a lot (almost 30% of the node size). Every byte saved in a node
*   is roughly an extra kilobyte for the rest of the code.
*
*   The solution is:
*
*   1.  We realise that since we use pre-allocated memory to store
*       the nodes, even if we use some malloc()/free() we will allocate
*       from an array of nodes.
*
*   2.  We also realise that since we use an array, we can split
*       nodes and store the parts in separate arrays, since the
*       array index links the node fragments at no extra cost.
*
*   Therefore, we could store the 16 byte application data in one array,
*   the key and the node administration in an other. The 16 byte data
*   consumes 16K RAM: a nice fit for the Ethernet RAM. Now if we could
*   pack the rest (the key and the AVL tree administrative data) into
*   the USB RAM somehow, that would leave the 16K general purpose RAM
*   completely free. Since the USB RAM is 8K, we have 8 bytes per node.
*   The key itself is 5 bytes. Thus, we have 3 bytes to store two node
*   references and the tree height. Currently we need 9 bytes for that.
*   So, how do we cram 9 bytes into 3?
*
*   The rest of the solution is:
*
*   3.  The AVL library uses node references and not pointers. We can use
*       array indices instead of pointers. Using unsigned short indices
*       decreases our memory consumption from 4+4+1=9 to 2+2+1=5 bytes.
*
*   4.  We only have to store 1000 nodes. A 1000 element array index is
*       only 10 bits. If we define the max number of nodes to 1023 (thus
*       exceeding the minimal requirement - payrise in order!), reserving
*       one value for the NULL reference, we could pack the two references
*       into 2*10 bits. 20 bits can be stored in 3 bytes, plus the byte for
*       the height of the subtree: we're down to 4 bytes AVL info per node.
*
*   5.  Consulting the table in the AVL documantation reveals that for a
*       tree with 1023 nodes the height information can be stored in 4 bits.
*       Thus, we have two 10-bit references and a 4-bit height. All together
*       24 bits, that is, 3 bytes. We managed to fit the key plus the AVL
*       information into 8 bytes, the problem has been solved.
*
*   Below is the implementation. Note that the data structure stored in
*   the Ethernet RAM and the associated access functions are not shown.
*   The example is only about the handling of the key plus AVL info,
*   stored in the USB RAM. The functions all return an array index,
*   that can be used to access the data in the Ethernet RAM.
*/

/*
*   Define an array of 1024 elements of 8 bytes each that is
*   in the USB RAM block. We define a pointer to 8 byte objects
*   and initialise it to the USB RAM's base address.
*/

static unsigned char (* const array)[ 8 ] = (void *) USB_RAM_ADDRESS;

/*
*   We will store the information in the 8 bytes as follows:
*   The first 5 (0-4) bytes are the key. Byte 5 will be the lower
*   8 bits of the left reference, byte 6 will store the lower 8 bits
*   of the right reference. The last byte has the height in its top
*   4 bits, then the next two bits store the upper two bits of the
*   left reference while the least significant two bits will hold the
*   upper two bits of the right reference. We define the node type
*   and the functions accordingly.
*/

typedef int         AVL_USER;       // We don't use it
typedef int         AVL_NODE;       // It is an index, actually
typedef char        *AVL_KEY;       // Pointer to the key
#define AVL_PLEN    15              // As per the size table

/*
*   We have to define the AVL_NULL value. When working with arrays,
*   one would instinctively choose the array size as the NULL value,
*   since the indices go from 0 to N-1. However, we have memory to
*   store 1024 elements and we only store 1023 of them. Thus, we can
*   use 0 as the NULL value and index the array from 1 to 1023. Since
*   comparison against 0 is usually more efficient than comparison
*   against an arbitrary number, this choice gives us a slight speed
*   (and code size) advantage.
*/

#define AVL_NULL    0


/*
*   We tell the AVL template that we need deletion
*/

#define AVL_USE_DELETE


/*
*   GET functions
*/

static AVL_NODE AVL_GET_L( AVL_NODE node, AVL_USER user )
{
    // If you use -W -Wall -Werror (as you should!), gcc gives you an
    // 'unused variable' error for unused parameters. Casting them to
    // void is using them without generating any code, so gcc is happy
    // and we're happy too. It also indicates to other people that you
    // intentionally ignored a function argument.

    (void) user;

    // The low 8 bits are in byte 5, the upper 2 bits are bits 3:2 in byte 7

    return array[ node ][ 5 ] | ( ( array[ node ][ 7 ] & 0x0c ) << 6 );
}

static AVL_NODE AVL_GET_R( AVL_NODE node, AVL_USER user )
{
    // The low 8 bits are in byte 6, the upper 2 bits are bits 1:0 in byte 7

    (void) user;
    return array[ node ][ 6 ] | ( ( array[ node ][ 7 ] & 0x03 ) << 8 );
}

static unsigned AVL_GET_H( AVL_NODE node, AVL_USER user )
{
    // The height is in bits 7:4 of byte 7

    (void) user;
    return array[ node ][ 7 ] >> 4;
}

/*
*   SET functions, the in same fashion as the getters.
*/

static void     AVL_SET_L( AVL_NODE node, AVL_NODE tree, AVL_USER user )
{
    array[ node ][ 5 ] = tree;
    array[ node ][ 7 ] &= 0xf3;
    array[ node ][ 7 ] |= ( tree >> 8 ) << 2;
}

static void     AVL_SET_R( AVL_NODE node, AVL_NODE tree, AVL_USER user )
{
    array[ node ][ 5 ] = tree;
    array[ node ][ 7 ] &= 0xfc;
    array[ node ][ 7 ] |= tree >> 8;
}

static void     AVL_SET_R( AVL_NODE node, unsigned height, AVL_USER user )
{
    array[ node ][ 7 ] &= 0x0f;
    array[ node ][ 7 ] |= height << 4;
}

/*
*   The comparison function
*/

static int      AVL_CMP( AVL_NODE node, AVL_KEYT refk, AVL_USER user )
{
    // We have to compare two 5-byte keys. The address of the key of a
    // node is actually the address of the node itself, since the first 5
    // bytes of a node form the key. Furthermore, the memcmp() function
    // in the C library does what we need, so we have not much to do.

    return memcmp( array[ node ], refk, 5 );
}

/*
*   Get the template
*/

#include <misc/avl.h>

/*
*   Define the free node list, the root and the workspace.
*/

static int      free;
static int      root;
static AVL_PATH work;

/*
*   Init
*   We know that our nodes are on word boundaries, thus it is safe to cast
*   a node address to a pointer to short. Therefore, we can build a singly
*   linked list of free nodes. Initially all nodes are free. So, each node
*   should contain an index 1 more than its own. The last free node, which
*   has the index of 1023, should contain AVL_NULL, i.e. 0 as its next.
*   We set the free node index to the first node in the chain, i.e. 1.
*/

void    MyDataInit( void )
{
int     i;

    for ( i = 1 ; i < 1023 ; i++ )

        *((unsigned short *) array[ node ]) = i + 1;

    *((unsigned short *) array[ 1023 ]) = AVL_NULL;

    free = 1;
    root = AVL_NULL;
}

/*
*   Search for a key.
*   The function returns the index or 0 if the key is not found.
*/

int     MyDataSearch( const char key[ 5 ] )
{
    // The AVL_Search() function does everything we need. Note that
    // we have to cast the key to AVL_KEYT. Although AVL_KEYT is
    // actually char * and the key is a character array, the cast
    // is needed because with -W -Wall gcc complains about the
    // const qualifier being omitted when we pass the key. The
    // explicit cast from const char * to char * keeps gcc happy.

    return AVL_Search( root, (AVL_KEYT) key, 0 );
}

/*
*   Insert a new node.
*   We return the index or 0 if the key is already on the tree
*   and -1 if we ran out of nodes.
*/

int     MyDataInsert( const char key[ 5 ] )
{
AVL_NODE    node;

    if ( free == AVL_NULL ) {

        // No free node, report error

        return -1;
    }

    // Allocate a node

    node = free;
    free = *((unsigned short *) array[ node ]);

    // Attempt insertion

    if ( AVL_Insert( &root, node, (AVL_KEYT) key, 0 ) != AVL_NULL ) {

        // The key is already on the tree. Release the node and
        // report error. Since the node was not changed by the
        // AVL_Insert routine, it is enough to restore free.

        free = node;
        return 0;
    }

    // The insertion was successful. Copy the key into the node and
    // return the index. The address of the key in the node is the
    // address of the node itself as the first 5 bytes of the node
    // contain the key.

    memcpy( array[ node ], key, 5 );
    return node;
}

/*
*   Delete a key.
*   If succeeded, then the index is returned. If the key was not on
*   the tree, 0 is returned.
*/

int     MyDataDelete( const char key[ 5 ] )
{
AVL_NODE    node;

    node = AVL_Delete( &root, &work, (AVL_KEYT) key, 0 );

    if ( node == AVL_NULL ) {

        // The key was not on the tree

        return 0;
    }

    // The key was deleted. Put the node back to the
    // free list and return the index.

    *((unsigned short *) array[ node ]) = free;
    free = node;
    return node;
}

Associative array example
/*
*   In this example we will use the user field.
*   We build an associative array. It will pair strings and
*   integers. You can look up a string by the integer and
*   vice versa. For the sake of simplicity we don't care about
*   memory now, we assume that we have malloc() and free().
*
*   We actually build two trees, one for the strings and one for
*   the integers. They are linked through the common node that
*   holds the data for both. We will use the user parameter to
*   distinguish between the two trees.
*
*   We don't detail our own header, it simply contains the prototypes
*   for the API functions. This is the C implementation file.
*/

#include "our_API_protos.h"
#include <string.h>
#include <malloc.h>

/*
*   Define the node type
*/

typedef struct s_node {

    struct s_node   *int_l;     // Integer tree left subtree
    struct s_node   *int_r;     // Integer tree right subtree
    unsigned        int_h;      // Integer tree height
    int             int_k;      // Integer tree key

    struct s_node   *str_l;     // String tree left subtree
    struct s_node   *str_r;     // String tree right subtree
    unsigned        str_h;      // String tree height
    const char      *str_k;     // String tree key

} *AVL_NODE;

/*
*   The key type is int. When we have to pass the string pointer as
*   key, it should be cast to an int. If the pointer type is wider
*   than the int type, you could do it the other way around, define
*   AVL_KEYT to be a pointer and cast the int to a pointer.
*/

typedef int     AVL_KEYT;

/*
*   The user type will be an int
*/

typedef int     AVL_USER;

/*
*   Since we use pointers, AVL_NULL is NULL
*/

#define AVL_NULL    NULL


/*
*   We don't care about memory, so we just define the workspace size to a
*   really high value.
*/

#define AVL_PLEN    64


/*
*   Now we define the get/set functions and the compare. The user parameter
*   will be used to distinguish between the integer tree and the string tree.
*   If the user parameter is 0, then we use the integer tree, if it is not
*   0, we use the string tree. We don't declare the functions inline, if
*   inlining is prudent, then gcc will inline them anyway.
*/

static AVL_NODE AVL_GET_L( AVL_NODE node, AVL_USER user )
{
    return user ? node->str_l : node->int_l;
}

static AVL_NODE AVL_GET_R( AVL_NODE node, AVL_USER user )
{
    return user ? node->str_r : node->int_r;
}

static unsigned AVL_GET_H( AVL_NODE node, AVL_USER user )
{
    return user ? node->str_h : node->int_h;
}

static void     AVL_SET_L( AVL_NODE node, AVL_NODE tree, AVL_USER user )
{
    if ( user )

        node->str_l = tree;
    else
        node->int_l = tree;
}

static void     AVL_SET_R( AVL_NODE node, AVL_NODE tree, AVL_USER user )
{
    if ( user )

        node->str_r = tree;
    else
        node->int_r = tree;
}

static void     AVL_SET_H( AVL_NODE node, unsigned height, AVL_USER user )
{
    if ( user )

        node->str_h = height;
    else
        node->int_h = height;
}

static int      AVL_CMP( AVL_NODE node, AVL_KEYT key, AVL_USER user )
{
    if ( user ) {

        return strcmp( node->str_k, (char *) key );
    }
    else {

        if ( node->int_k < key ) return -1;
        if ( node->int_k > key ) return +1;
        return 0;
    }
}

/*
*   Get the template and start the implementation
*/

#include <misc/avl.h>

static AVL_NODE root_int;   // Root for the integer tree
static AVL_NODE root_str;   // Root for the string tree
static AVL_PATH work;       // Workspace

/*
*   Init
*/

void    AssocInit( void )
{
    root_int = NULL;
    root_str = NULL;
}

/*
*   Look up a string by the int.
*   Returns NULL if the key is not found.
*/

const char *AssocFindByInt( int key )
{
AVL_NODE    node;

    // We pass the integer root and the 0 user parameter
    // will tell the AVL code to use the int_? fields.

    node = AVL_Search( root_int, key, 0 );

    if ( node )

        return node->str_k;
    else
        return NULL;
}

/*
*   Look up an int by a string.
*   If the string is found, then the int will be copied to the location
*   pointed by the 'result' pointer and 1 will be returned, if the string
*   is not found, then 0 is returned and the memory is not modified.
*/

int     AssocFindByString( const char *key, int *result )
{
AVL_NODE    node;

    // We pass the string root and the 1 user parameter will
    // tell the AVL code to use the str_? fields. We have to
    // cast the char pointer to the key type, which is an int.

    node = AVL_Search( root_str, (AVL_KEYT) key, 1 );

    if ( node ) {

        *result = node->int_k;
        return 1;
    }
    else
        return 0;
}

/*
*   Inserts a new node
*   Returns:
*    0 if succeeded or the same pair was on the list already
*    1 the int is on the tree with a different string
*    2 the string is on the tree with a different int
*    3 both int and string are on the tree with different pairs
*/

int     AssocRemember( const char *key_str, int key_int )
{
AVL_NODE    node;
AVL_NODE    test;

    // Allocate a new node

    node = malloc( sizeof( struct s_node ) );

    // Try to insert it into the integer tree first

    if ( test = AVL_Insert( &root_int, &work, node, key_int, 0 ) ) {

        // This int is already on the tree. We can release the node structure.

        free( node );

        // Let's see if the the node on the tree has the same string key
        // as the one we wanted to insert would have.

        if ( ! strcmp( test->str_k, key_str ) ) {

            // It's the same, so the same pair got registered again,
            // which is fine. Report success.

            return 0;
        }
        else {

            // OK, the int is on the tree with a different string.
            // Our return code depends on whether the string is on
            // the tree or not

            if ( AVL_Search( root_str, (AVL_KEYT) key_str, 1 ) )

                return 3;
            else
                return 1;
        }
    }

    // The int was not on the tree (but is now!).
    // Try to insert to the string tree.

    if ( AVL_Insert( &root_str, &work, node, key_str, 1 ) ) {

        // The string is already on the tree. Remove the new node from
        // the integer tree, release the memory and report error.

        AVL_Delete( &root_int, &work, key_int, 0 );
        free( node );
        return 2;
    }

    // Both insertions succeded, put the keys into the node and
    // report success.

    node->str_k = malloc( strlen( key_str ) + 1 );
    strcpy( node->str_k, key_str );
    node_int_k = key_int;
    return 0 ;
}

/*
*   Delete a node.
*   It receives both an int key and a string key. If the string key is NULL,
*   then it deletes the node identified by the integer key, if the string
*   key is not NULL, then it deletes the node by the string and the integer
*   key will be ignored. If the key is not found, then it does nothing.
*/

void    AssocForget( const char *key_str, int key_int )
{
AVL_NODE    node;

    if ( key_str )

        node = AVL_Delete( &root_str, &work, (AVL_KEYT) key_str, 1 );
    else
        node = AVL_Delete( &root_int, &work, key_int, 0 );

    if ( node ) {

        if ( key_str )

            AVL_Delete( &root_int, &work, node->int_k, 0 );
        else
            AVL_Delete( &root_str, &work, (AVL_KEYT) node->str_k, 1 );

        free( node->key_s );
        free( node );
    }
}


Function Documentation

static AVL_NODE AVL_Delete ( AVL_NODE *  root,
AVL_PATH *  path,
AVL_KEYT  key,
AVL_USER  user 
) [static]

Deletes the given node from the tree.

The function removes a node from the tree and returns its reference. If the node is not on the tree, then AVL_NULL is returned.

Parameters:
root Pointer to an AVL_NODE object that contains the reference to the root node of the tree (which should be AVL_NULL if the tree is empty). The object might be modified by this function.
path Pointer to an AVL_PATH structure. The structure will be used as workspace and can be discarded as soon as this routine returns.
key The key to be deleted from the tree.
user The implementation dependent parameter. This argument is not used by the AVL routines themselves, but they are passed to all user defined functions.
Returns:
Reference to the node that was removed from the three if the key was found, AVL_NULL if the key wasn't on the tree.
Note:
To use this function you must define the AVL_USE_DELETE macro before including the template.
static AVL_NODE AVL_First ( AVL_NODE  root,
AVL_USER  user 
) [static]

Returns the node with the smallest key.

Parameters:
root The root of the tree
user The implementation dependent parameter. This argument is not used by the AVL routines themselves, but they are passed to all user defined functions.
Returns:
The reference to the node with the smallest key or AVL_NULL if the tree was empty.
Note:
To use this function you must define the AVL_USE_FIRST macro before including the template.
static AVL_NODE AVL_Insert ( AVL_NODE *  root,
AVL_PATH *  path,
AVL_NODE  node,
AVL_KEYT  key,
AVL_USER  user 
) [static]

Inserts a new node with the given key to the tree.

Returns AVL_NULL if succeeded. If the tree already has a node that has the same key as the one to be inserted, then the tree is not modified and the already existing node is returned. Note that since the function does not know the relationship between AVL_NODE and AVL_KEYT objects, the node's key field will not be set to the key. It is your responsibility. Also note that if you set the node's key to a value other than the key you passed to this function, it is, from the AVL tree handling functions' point of view, equivalent to memory corruption, with the same consequences.

Parameters:
root Pointer to an AVL_NODE object that contains the reference to the root node of the tree (which should be AVL_NULL if the tree is empty). The object might be modified by this function.
path Pointer to an AVL_PATH structure. The structure will be used as workspace and can be discarded as soon as this routine returns.
node Reference to the new node to insert to the tree.
key The key for the new node.
user The implementation dependent parameter. This argument is not used by the AVL routines themselves, but they are passed to all user defined functions.
Returns:
AVL_NULL if the insertion succeeded. If the new node was not inserted because the key already exists on the tree then a reference to the node already on the tree with the same key is returned.
static AVL_NODE AVL_Last ( AVL_NODE  root,
AVL_USER  user 
) [static]

Returns the node with the largest key.

Parameters:
root The root of the tree
user The implementation dependent parameter. This argument is not used by the AVL routines themselves, but they are passed to all user defined functions.
Returns:
The reference to the node with the largest key or AVL_NULL if the tree was empty.
Note:
To use this function you must define the AVL_USE_LAST macro before including the template.
static AVL_NODE AVL_Next ( AVL_NODE  root,
AVL_PATH *  path,
AVL_KEYT  refk,
AVL_USER  user 
) [static]

Finds the node with smallest key that is larger than the given key.

This function returns a reference to a node that has the smallest key of all nodes with keys larger than the key passed to the function. If there is no node with a key larger than the given key, the function returns AVL_NULL.

Thus, this function can be used to walk the tree from smallest to largest by the following way:

    // This is a function you have yo define. It returns a key of a node.

    AVL_KEYT GetKeyOfTheNode( AVL_NODE );

    // This is the tree-walk routine

    void Walk( AVL_TREE root, AVL_USER user )
    {
    AVL_NODE    node;
    AVL_PATH    work;

        node = AVL_First( root, user );

        while ( node != AVL_NULL ) {

            DoSomethingWithTheNode( node );
            node = AVL_Next( root, &work, GetKeyOfTheNode( node ), user );
        }
    }

Note that this method works even if between successive invocations of the AVL_Next() function the tree gets modified, although if a new node gets inserted that has a key less than the key of the current node, the new node obviously will not be visited. However, the price you pay for this is that every time you call this function, a key search will be performed, at the cost of O(log(N)) steps. Therefore, a full walk will cost you O(N*log(N)) operations. If your tree does not change while you walk through it, you should consider using the AVL_Walk() and AVL_Step() functions, which will take you through the tree in O(N) operations.

Parameters:
root The root of the tree.
path Pointer to an AVL_PATH structure. The structure will be used as workspace and can be discarded as soon as this routine returns.
refk The reference key
user The implementation dependent parameter. This argument is not used by the AVL routines themselves, but they are passed to all user defined functions.
Returns:
Reference to the node that has the smallest key that is larger than the reference or AVL_NULL if there is no such node.
Note:
To use this function you must define the AVL_USE_NEXT macro before including the template.
static AVL_NODE AVL_Search ( AVL_NODE  root,
AVL_KEYT  skey,
AVL_USER  user 
) [static]

Find a key on the tree.

Returns a reference to the node data with the given key or NIL if the key is not found.

Parameters:
root The root node of the tree or AVL_NULL if the tree is empty.
skey The search key
user The implementation dependent parameter. This argument is not used by the AVL routines themselves, but they are passed to all user defined functions.
Returns:
A reference to the node with the given serach key or AVL_NULL if the key was not found. If multiple nodes have the same key then it is unpredictable which one will be returned.
static AVL_NODE AVL_Step ( AVL_PATH *  path,
AVL_USER  user 
) [static]

One step on the tree walk.

Receives an AVL_PATH object that was initially prepared by a call to the AVL_Walk() function. It then returns the node with the smallest key and modifies the AVL_PATH object such that the next invocation of the function will return the node with the second smallest key. That call, in turn, will modify the AVL_PATH object so that the next call returns the third smallest key and so on. Once the node with the largest key is returned, then any further calls to this function will return AVL_NULL.

It is very important that neither the AVL_PATH object nor the tree is modified between the calls. If you can't guarantee that, you should use the AVL_Next() function instead.

This function walks through the tree in O(N) amortised time, that is, one node is accessed on average for each call. However, the access pattern varies, and some calls may not access the tree at all, others might access as many as AVL_PLEN nodes.

Parameters:
path An AVL_PATH object that was initialised by a call to AVL_Walk().
user The implementation dependent parameter. This argument is not used by the AVL routines themselves, but they are passed to all user defined functions.
Returns:
The next node of a smallest to largest walk, or AVL_NULL if the largest node was already returned.
Note:
To use this function you must define the AVL_USE_WALK macro before including the template.
static void AVL_Walk ( AVL_NODE  root,
AVL_PATH *  path,
AVL_USER  user 
) [static]

Initialises a tree walk.

Parameters:
root The root of the tree
path Pointer to an AVL_PATH structure. The structure will be filled with data that, when passed to the AVL_Step() function, will make that function to return the node with the smallest key. Neither this structure nor the AVL tree itself should be modified between this call and calling AVL_Step() (and any subsequent calls to that function).
user The implementation dependent parameter. This argument is not used by the AVL routines themselves, but they are passed to all user defined functions.
Note:
To use this function you must define the AVL_USE_WALK macro before including the template.
Generated on Tue Jul 13 16:51:45 2010 by  doxygen 1.6.3