Search code examples
calgorithmdata-structuresansi-c

ANSI C - Finding node in arbitrary tree


I've created a simple test program as follows (error checks are omitted).

My Tree_Find() and Node_Find() functions seem to work correctly but I'd like to know if there are more efficient ways to achieve the same thing.

Node.h

typedef struct _Node Node;

Node* Node_Create(int nTag);
void Node_Destroy(Node **ppNode);
void Node_Append(Node **ppParent, Node *pChild);
Node* Node_Find(Node *pNode, int nTag);

Tree.h

#include "Node.h"

typedef struct _Tree Tree;

Tree* Tree_Create(void);
void Tree_Destroy(Tree **ppTree);
void Tree_Init(Tree *pTree);
Node* Tree_Find(Tree *pTree, int nTag);

Node.c

#include "Node.h"

struct _Node
{
    int nTag;
    struct _Node *pFirstChild;
    struct _Node *pNextSibling;
};

Node* Node_Create(int nTag)
{
    Node *pNode = malloc(sizeof(*pNode));
    pNode->nTag = nTag;
    pNode->pFirstChild = NULL;
    pNode->pNextSibling = NULL;

    return pNode;
}

void Node_Destroy(Node **ppNode)
{
    Node *pNode = NULL;

    if (!ppNode)
        return;

    if ((pNode = *ppNode) == NULL)
        return;

    Node_Destroy(&(pNode->pFirstChild));
    Node_Destroy(&(pNode->pNextSibling));

    free(pNode);
    *ppNode = NULL;
}

void Node_Append(Node **ppParent, Node *pChild)
{
    Node *pLastChild = NULL;

    if (!(*ppParent))
        return;

    if (!((*ppParent)->pFirstChild))
    {
        (*ppParent)->pFirstChild = pChild;

        return;
    }

    pLastChild = (*ppParent)->pFirstChild;
    while (pLastChild->pNextSibling)
        pLastChild = pLastChild->pNextSibling;

    pLastChild->pNextSibling = pChild;
}

Node* Node_Find(Node *pNode, int nTag)
{
    Node *pNodeFound = NULL;

    if (!pNode)
        return NULL;

    if (pNode->nTag == nTag)
        return pNode;

    if ((pNodeFound = Node_Find(pNode->pFirstChild, nTag)) == NULL)
        pNodeFound = Node_Find(pNode->pNextSibling, nTag);

    return pNodeFound;
}

Tree.c

#include "Tree.h"

struct _Tree
{
    Node *pRoot;
};

Tree* Tree_Create(void)
{
    Tree *pTree = malloc(sizeof(*pTree));

    pTree->pRoot = NULL;

    return pTree;
}

void Tree_Destroy(Tree **ppTree)
{
    Tree *pTree = NULL;

    if (!ppTree)
        return;

    if ((pTree = *ppTree) == NULL)
        return;

    Node_Destroy(&(pTree->pRoot));

    free(pTree);
    *ppTree = NULL;
}

void Tree_Init(Tree *pTree)
{
    Node *p1 = Node_Create(1);
    Node *p2 = Node_Create(2);
    Node *p3 = Node_Create(3);
    Node *p4 = Node_Create(4);
    Node *p5 = Node_Create(5);

    Node_Append(&p1, p2);
    Node_Append(&p1, p3);
    Node_Append(&p3, p4);
    Node_Append(&p3, p5);

    pTree->pRoot = p1;
}

Node* Tree_Find(Tree *pTree, int nTag)
{
    if (!pTree)
        return NULL;

    return Node_Find(pTree->pRoot, nTag);
}

main.c

#include "Tree.h"

int main(void)
{
    Node *pNodeToFind = NULL;

    Tree *pTree = Tree_Create();

    Tree_Init(pTree);

    pNodeToFind = Tree_Find(pTree, 1);
    pNodeToFind = Tree_Find(pTree, 2);
    pNodeToFind = Tree_Find(pTree, 3);
    pNodeToFind = Tree_Find(pTree, 4);
    pNodeToFind = Tree_Find(pTree, 5);
    pNodeToFind = Tree_Find(pTree, 6); // Not found!

    Tree_Destroy(&pTree);

    return 0;
}

Solution

  • If your goal is to build and seach arbitrary trees with no order on keys, then you've done just about all you can do except one thing: Your append method will have O(n^2) run time where n is the number of children a single node might have. If you maintain a queue instead of a list, you can append in unit time. There is a nice trick for implementing a queue with only one pointer in the header rather than separate head and tail pointers. Make the child list circular and have the parent point to the tail of the circular list. Then parent->children is the tail element and parent->children->sibling is the head. With this setup, you can push onto the tail and both push and pop from the head of the list of children. The code for these ops can be quite elegant, and your append will be constant time.

    Alternately you can store children in an auto-resized array. There is a slight storage penalty (you need to store a pointer and child count per node), but random access to children can be handy in some cases.