3

I am writing an implementation of Conway's Game of Life and decided to use a variation of the Hashlife algorithm, using quadtrees and a hashtable to store cells and handle collisions, somewhat similar to what is described here and here. The details are basically that the entire space is composed of quadtrees, which descend to leaves with a living or dead state. Quads themselves are immutable, and are hashed to share references throughout the entire tree to deal with very sparse areas or common repeated patterns.

One problem I am running into is even generating a field to hold cells that are very far apart from each other. It seems that even recursively defining an empty quad tree of height 24 takes a very significant amount of time.

What is the best way of tackling this problem?

Two solutions I see may be to leave empty quads uninitialized until they are needed, but the immutability of quads may make this a tad tricky - if I keep each quad immutable I cannot just instantiate a child node, I would have to update the entire structure from the ground up.

Another solution I thought of would be to have multiple quadTrees - so if I have a point that's several trillion cells away from other cells, they will be governed by separate trees. Obviously the problem here is dealing with merging of trees that become adjacent or overlapping - it seems like to go down this route would require quad-trees of quad-trees, and I suspect this will not end well for me.

What are other solutions that I am missing? Is there something I am ignoring about the above two solutions that would make them less hairy?

Thank you!

Justin Hamilton
  • 570
  • 1
  • 5
  • 13

1 Answers1

1

I've dealt with this and the seminal implementation Golly deals with this by using NULL (or some suitable stand-in value) as representing 'empty' at any level of the tree. This works because we know empty advances to empty.

So if you imagine a 8x8 span holding a glider in the North West quadrant you'd have a structure that was

NorthWest=Glider (held in a 4x4 sub-tree) NorthEast=SouthWest=SouthEast=nullptr

Using this approach you can make massive patterns so long as they're sparse.

Somewhere in your program you'll access the hash-table and you may need to add code of the form:

Node* getNode(Node* NW,Node* NE,Node* SW,Node* SE) {
   if(NW==nullptr&&NW==nullptr&&NW==nullptr&&NW==nullptr){
       return nullptr;
   }   
  //Actually go into the hash-table....

}

And an advance function:

Node* AdvanceQuarterSpan(Node* node, unsigned span_index){
    if(node==nullptr){
        return nullptr;
    }
    //Actually divide the node up advance the sub-quadrants and advance the resulting centre quadrant.
}

I'm loosely assuming you've got some structure like:

class Node {
    Node* NW; //North West Quadrant. nullptr if empty.
    Node* NE; //North East Quadrant. nullptr if empty.
    Node* SW; //South West Quadrant. nullptr if empty.
    Node* SE; //South East Quadrant. nullptr if empty.
    //Other fields...

};

I looked at ideas of having separate trees but handling trees with massive voids in them is so efficient this way it was entirely pointless.

Persixty
  • 8,165
  • 2
  • 13
  • 35