In some cases, you could simply use an array to hold the nodes. A binary tree node at arr[i]
would have children from arr[(i*2)+1]
to arr[(i+1)*2]
. Its parent would be at arr[(i-1)/2]
, if i != 0. And to figure the real pointer address, of course, you could say &arr[i]
. It's actually a rather common implementation for trees that are full by specification, like the tree used for a heap.
In order for a node to know for itself how to find its children, though, you'd likely need either an index or a pointer to the container. (And even then, with only one of the two pieces, you're having to do a bit of hoop-jumping; you really need both pieces in order to do things easily. But having to calculate stuff rather than remembering it, is kinda the price you pay when you're trying not to remember much.) In order to keep stuff reasonably space-efficient, you'd have to dumb down the nodes; make them basically structs, or even just the values, and let the tree class do all the node-finding stuff. It'd just hand out pointers to nodes, and that pointer would be all the container needs to figure out the node's index (and, thus, where its children will be). You'd also have to pass both a tree pointer and a node pointer to any function that wants to traverse the tree.
Note, though, this won't save much space unless your trees are consistently near full (that is, unless most/all of your leaf nodes are at the end). For every leaf node that's not at the bottom of the tree (where the top is the root), you waste something like ((node size) * (tree size / i)) bytes.
If you can't count on the tree being full, or the nodes being in a certain restricted space, then there's not a whole lot to optimize here. The whole point of trees is nodes have pointers to their children; you can fake it with an array, but it must be easy to somehow find a node's children in order to make a tree worthwhile.