Most commonly, however, (in C, at least) such references to itelf are pointers to elements of the same type. You have already seen the most common example of recursive types: linked lists.
struct elem { int content; struct elem *next; /* <----- reference to struct elem */ };
More generally, recursive types are used to represent arbitrary graphs.
There are two major alternative ways of designing binary trees:
struct tnode { int content; struct tnode *left, *right; };
Let's go back to the simple case (which I would recommend in all cases!).
The version which carries values at leaf nodes only is perhaps a bit more cumbersome to define cleanly in C. Here is an example, involving unions:struct tnode { int is_leaf; union { struct { struct tnode *left, *right; } s; int content; } u; };
A recursive procedure that allows you to visit all the nodes in preorder is now very easy to write:
void tpreorder (struct tnode *tree, void visitor (int)) { if (tree) { visitor (tree->content); tpreorder (tree->left); tpreorder (tree->right); } }Can you guess how you would do inorder or postorder traversals?
A variant of the traversal could be used to sum the values stored in a tree:
int tsum (struct tnode *) { return t ? t->content + tsum (t->left) + tsum (t->right) : 0; }
The content of any node in a binary search tree it greater than the content of any other node in its left subtree and less than the content of any other node in its right subtree.Here is how you search for a node with a given content:
struct tnode *find (struct tnode *t, int content) { if (t && t->content != content) return find ((t->content < content) ? t->right : t->left, content); return t; }Notice how the structure of the algorithm follows the structure of the the datatype definition (just like in the case of the traversal procedures above).
On closer inspection you will find that the recursive call to find occurs right next to the word return. This indicates that your procedure is tail-recursive and can easily be rewritten using a loop.Advise: When dealing with recursive datatypes always write your code in the natural, that is: recursive way first! Only if you discover that this approach constitutes a performance bottleneck, then you should start worrying about translating it into an iterative solution where possible.struct tnode *lfind (struct tnode *t, int content) { while (t && t->content != content) t = (t->content < content) ? t->right : t->left; return t; }
void insert (struct tnode **t, int content) { if (*t) if ((*t)-> content == content) return; else if ((*t)->content < content) insert (&(*t)->right, content); else insert (&(*t)->left, content); else { struct tnode *n = malloc (sizeof (struct tnode)); if (n == NULL) { fputs ("Out of memory\n", stderr); exit (1); } else { n->left = n->right = NULL; n->content = content; *t = n; } } }Exercise: Write this function iteratively.
With deletion we won't be so lucky, because not every node we ever may want to delete is necessarily a leaf node. So, how do we deal with this? (Hint: If the node n to be deleted is not a leaf itself, then try selecting an appropriate leaf node from the rest of the tree, which you then can use to replace n!)
static struct elem *flat (struct tnode *t, struct elem *l) { if (t) { struct elem *n = malloc (sizeof (struct elem)); if (n == NULL) { fputs ("Out of memory\n", stderr); exit (1); } n->next = flat (t->right, l); n->content = t->content; return flat (t->right, n); } else return l; } struct elem *flatten (struct tnode *tree) { return flat (tree, NULL); }