How do you make Tree Data Structures in C++? - c++

I'm taking a class in AI Methods along with a friend of mine, and we've partenered for the final project, which is coding Othello & an AI for it using C++ and OpenGL.
So far we have the board and the Othello Engine (I'm using an MVC type approach). But the one thing thats proving difficult to grasp is the AI.
We're supposed to write an AI that uses Alpha-Beta pruning on a tree to quickly calculate the next move it should make.
The concepts of Alpha-Beta pruning, as well as the algorithm for detecting which squares are worth more than others, as far as the game is concerned.
However, my partner nor I have yet to take the data structures class, and as such we don't know how to properly create a tree in C++ or even where to get started.
So my question to you, Stack Overflow is: Where do I get started to quickly (and effectively) write and traverse a Tree for Alpha-Beta Pruning in C++ without using STL. (Our assignment states that we're not allowed to use STL).
Any and all help is appreciated, thank you!

The tree for alpha-beta pruning is usually implicit. It is a way of preventing your AI search algorithm from wasting time on bad solutions. Here is the pseudocode from Wikipedia:
function alphabeta(node, depth, α, β, Player)
if depth = 0 or node is a terminal node
return the heuristic value of node
if Player = MaxPlayer
for each child of node
α := max(α, alphabeta(child, depth-1, α, β, not(Player) ))
if β ≤ α
break (* Beta cut-off *)
return α
else
for each child of node
β := min(β, alphabeta(child, depth-1, α, β, not(Player) ))
if β ≤ α
break (* Alpha cut-off *)
return β
(* Initial call *)
alphabeta(origin, depth, -infinity, +infinity, MaxPlayer)
The function recursively evaluates board positions. The "node" is the current position, and where it says "for each child of node" is where you generate new board positions resulting from each possible move at the current one. The depth parameter controls how far ahead you want to evaluate the tree, for analyzing moves to an unlimited depth might be impractical.
Still, if you have to build a tree of some given depth before pruning it for educational purposes, the structure for a tree with nodes that can have variable numbers of children is very simple and could look something like this:
struct Node
{
Node ** children;
int childCount;
double value;
};
Node * tree;
Here children is a Node array with childCount members. Leaf nodes would have childCount=0. To construct the tree, you would search the availabile board positions like this:
Node * CreateTree(Board board, int depth)
{
Node * node = new Node();
node.childCount = board.GeAvailableMoveCount();
node.value = board.GetValue;
if (depth > 0 && node.childCount > 0)
{
node.children = new Node * [node.childCount];
for (int i = 0; i != node.childCount; ++i)
node.children[i] = CreateTree(board.Move(i), depth - 1);
}
else
{
node.children = NULL;
}
return node;
}
void DeleteTree(Node * tree)
{
for (int i = 0; i != tree.childCount; ++i)
DeleteTree(tree.children[i]);
delete [] tree.children; // deleting NULL is OK
delete tree;
}

Related

How to calculate the miss links in a BVH tree?

I am creating an OpenGl based ray tracer for polygon models. To accelerate the application I am using BVH-trees. Because there is no recursion in GLSL, I decided to find an other way to traverse the bounding boxes, sent to the fragment shader as shader storage buffers.
I would like to implement that kind of way:Traversal of BVH tree in shaders
Actually I don't really understand how to calculate the hit and miss links during the construction of the tree. Hit and miss links help the program to navigate to the next node (bounding box) during the traverse, whether it is intersected or not missed.
Until now I created the method to construct the tree, as well as I can also put the tree into a simple array. I have depth-first implementation to flatten the tree into the array.
Here are the depth-first, tree flattening methods:
FlatBvhNode nodeConverter2(BvhNode node, int& ind){
FlatBvhNode result = FlatBvhNode(node.bBox.min, node.bBox.max, ind, node.isLeaf,
node.indices);
return result;
}
void flattenRecursion(const BvhNode &bvhNode, vector<FlatBvhNode>& nodes, int& ind) {
++ind;
nodes.push_back(nodeConverter2(bvhNode, ind));
if (!bvhNode.isLeaf) {
flattenRecursion(*bvhNode.children.at(0), nodes, ind);
flattenRecursion(*bvhNode.children.at(1), nodes,ind);
}
}
vector<FlatBvhNode>* flatten(const BvhNode& root) {
vector<FlatBvhNode>* nodesArray=new vector<FlatBvhNode>;
nodesArray->reserve(root.countNodes());
int ind=0;
flattenRecursion(root, *nodesArray, ind);
return nodesArray;
}
I have to calculate the following "links" :
The image is from: source. The image shows the different linkings. So, for example the ray intersects a bounding box (Hit links), we can move to the next node in the array. This is all right as I have depth-first traversal. The problem is coming when I have to move to the sibling or even to the parent's sibling. How can I implement these linkings / offsets? I know I should create and indices but how to do this with depth-first tree construction.
Any help is appreciated.
I do not have an answer about a depth-first tree, but I have figured out a way to do that if your tree is a heap. So here is some code in GLSL I used
int left(in int index) { // left child
return 2 * index + 1;
}
int right(in int index) { // right child
return 2 * index + 2;
}
int parent(in int index) {
return (index - 1) / 2;
}
int right_sibling(in int index) { // a leaf hit or a miss link
int result = index;
while(result % 2 == 0 && result != 0) {
result = parent(result);
}
return result + 1 * int(result != 0);
}
I am using this and it works with a pretty reasonable speed. The only problem I have is that loop, which slows the performance. I would really like to have a constant complexity expression in that function.

Robotic Path Planning - A* (Star)

I'm implementing A* path planning algorithm for my main robots exploration behavior in C++. As the robot moves, it maps the environment around itself as a 2D graph. From this graph, I have set a Vector2D Tuple {x, y} which holds the location of this waypoint, where I want the robot to navigate too.
The first thing I do with A* is to have a Node class, which holds information about the current node;
double f; // F, final score
double g; // Movement cost
double h; // Hueristic cost (Manhatten)
Node* parent;
Vector2d position;
As A* starts, I have my starting node as my Robots starting position (I also hold this position as a Vector for easy access). Then, I enter a while loop until the end goal is found. The first thing I do in this loop is to generate eight adjacent Nodes (Left, Bottom, Right, Top, Top-left, Top-Right, Bottom-Left, Bottom Right), I then return this in a OpenList vector.
// Open List is current nodes to check
std::vector positions;
positions.push_back(Vector2d(current->position.getX() - gridSize, current->position.getY())); // Left of my current grid space (parent node)
positions.push_back(Vector2d(current->position.getX() + gridSize, current->position.getY())); // right of my current grid space (parent node)
positions.push_back(Vector2d(current->position.getX(), current->position.getY() + gridSize)); // Top of my current grid space (parent node)
positions.push_back(Vector2d(current->position.getX(), current->position.getY() - gridSize)); // Bottom of my current grid space (parent node)
positions.push_back(Vector2d(current->position.getX() + gridSize,current->position.getY() + gridSize)); // Top Right of my current grid space (parent node)
positions.push_back(Vector2d(current->position.getX() - gridSize,current->position.getY() + gridSize)); // Top Left of my current grid space (parent node)
positions.push_back(Vector2d(current->position.getX() + gridSize,current->position.getY() - gridSize)); // Bottom Right of my current grid space (parent node)
positions.push_back(Vector2d(current->position.getX() - gridSize,current->position.getY() - gridSize)); // Bottom Left of my current grid space (parent node)
// moving diagnolly has a bigger cost
int movementCost[8] = { 10, 10, 10, 10, 14, 14, 14, 14 };
// loop through all my positions and calculate their g, h and finally, f score.
for (int i = 0; i < positions.size(); i++)
{
Node* node = new Node(positions[i]);
node->parent = current;
node->movementCost = movementCost[i];
if (!presentInClosedList(node))
{
// if the probability value of the current node is less then 0.5 (not an obstacle) then add to the open list, else skip it as an obstacle
// Set astar grid occupancy
if (grid->getProbabilityValue(node->position) < 0.51)
{
node->g = current->g + movementCost[i];
node->h = (abs(positions[i].getX() - wantedLocation.getX())) + (abs(positions[i].getY() - wantedLocation.getY()));
node->f = node->g + node->h;
openList.push_back(node);
}
}
}
This is the code to see if the current node is present in my closedList
bool exists = false;
for (int i = 0; i < closedList.size(); i++)
{
if (closedList[i]->position == currentNode->position)
{
closedList[i]->f = currentNode->f;
closedList[i]->g = currentNode->g;
closedList[i]->h = currentNode->h;
closedList[i]->parent = currentNode->parent;
exists = true;
break;
}
}
return exists;
This returns an openlist of possible routes. Next, I select the one with the smallest F score, and add this to my closedList. I keep doing this routine until the end goal has been found. Finally, once found I go back down the list using the parent objects. Here is the rest of the code
// If my parents location is the same as my wanted location, then we've found our position.
if (locationFound(current, wantedLocation))
{
// Now we need to backtrack from our wantedNode looking at the parents of each node to reconstruct the AStar path
Node* p = current->parent;
rollingDist = p->g;
while (!wantedFound)
{
if (p->position == startLocation)
{
wantedFound = true;
wantedNodeFound = true;
break;
}
path.push_back(p);
p = p->parent;
}
}
Now this is my issue. On every attempt it always finds the wanted location, but never the shortest path. See figure one below.
Figure one. Where the yellow marker is the wanted location, and the red darts is the "Path" to my wanted location, and finally, the "Blue" marker is where A star began.
This is my issue. I can't seem to reconstruct this path.
To recap the comments, there are two important problems
Manhattan distance is not admissible for your movement costs, since the actual shortest path can take a diagonal shortcut that Manhattan distance wouldn't take into account.
Before adding a new node to the Open list, it not only necessary to check whether it is in the Closed list, but also whether it is already in the Open list. If it is already in the Open list, the G's have to be compared and the smallest must be chosen (together with the corresponding parent pointer).[1]
Since you have octile movement with 10/14 costs, your heuristic function could be (from http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html)
function heuristic(node) =
dx = abs(node.x - goal.x)
dy = abs(node.y - goal.y)
return D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)
With D = 10, D2 = 14. Of course you can also use anything else admissible but this formula already reflects the actual distance on an open plain so it can't easily be improved.
Finding and updating nodes in the Open list is an annoying part of A* that I'm sure many people would like to pretend isn't necessary, since it means you can't reasonably use any pre-defined priority queue (they lack efficient lookup). It can be done by having a manually implemented binary heap and a hash table that maps coordinates to their corresponding indexes in the heap. The hash table has to be updated by the heap whenever a node is moved.
[1]: the relevant snippet of pseudo code from the wikipedia article is:
tentative_gScore := gScore[current] + dist_between(current, neighbor)
if neighbor not in openSet // Discover a new node
openSet.Add(neighbor)
else if tentative_gScore >= gScore[neighbor]
continue // This is not a better path.
// This path is the best until now. Record it!
cameFrom[neighbor] := current
gScore[neighbor] := tentative_gScore
fScore[neighbor] := gScore[neighbor] + heuristic_cost_estimate(neighbor, goal)

Chess AI with alpha beta algorithm

I have implemented the alpha beta algorithm for my chess game, however it takes a lot of time (minutes for 4-ply) to finally make a rather stupid move.
I've been trying to find the mistake (I assume I made one) for 2 days now, I would very much appreciate some outside input on my code.
getMove function: is called for the root node, it calls alphaBeta function for all it's child nodes (possible moves) and then chooses the move with the highest score.
Move AIPlayer::getMove(Board b, MoveGenerator& gen)
{
// defined constants: ALPHA=-20000 and BETA= 20000
int alpha = ALPHA;
Board bTemp(false); // test Board
Move BestMov;
int i = -1; int temp;
int len = gen.moves.getLength(); // moves is a linked list holding all legal moves
BoardCounter++; // private attribute of AIPlayer object, counts analyzed boards
Move mTemp; // mTemp is used to apply the nextmove in the list to the temporary test Board
gen.mouvements.Begin(); // sets the list counter to the first element in the list
while (++i < len && alpha < BETA){
mTemp = gen.moves.nextElement();
bTemp.cloneBoard(b);
bTemp.applyMove(mTemp);
temp = MAX(alpha, alphaBeta(bTemp, alpha, BETA, depth, MIN_NODE));
if (temp > alpha){
alpha = temp;
BestMov = mTemp;
}
}
return BestMov;
}
alphaBeta function:
int AIPlayer::alphaBeta(Board b, int alpha, int beta, char depth, bool nodeType)
{
Move m;
b.changeSide();
compteurBoards++;
MoveGenerator genMoves(b); // when the constructor is given a board, it automatically generates possible moves
// the Board object has a player attribute that holds the current player
if (genMoves.checkMate(b, b.getSide(), moves)){ // if the current player is in checkmate
return 100000;
}
else if (genMoves.checkMate(b, ((b.getSide() == BLACK) ? BLACK : WHITE), moves)){ // if the other player is in checkmate
return -100000;
}
else if (!depth){
return b.evaluateBoard(nodeType);
}
else{
int scoreMove = alpha;
int best;
genMoves.moves.Begin();
short i = -1, len = genMoves.moves.getLength();
Board bTemp(false);
if (nodeType == MAX_NODE){
best = ALPHA;
while (++i < len){
bTemp.cloneBoard(b);
if (bTemp.applyMove(genMoves.moves.nextElement())){
scoreMove = alphaBeta(bTemp, alpha, beta, depth - 1, !nodeType);
best = MAX(best, scoreMove);
alpha = MAX(alpha, best);
if (beta <= alpha){
std::cout << "max cutoff" << std::endl;
break;
}
}
}
return scoreMove;
//return alpha;
}
else{
best = BETA;
while (++i < len){
bTemp.cloneBoard(b);
if (bTemp.applyMove(genMoves.moves.nextElement())){
scoreMove = alphaBeta(bTemp, alpha, beta, depth - 1, !nodeType);
best = MIN(best, scoreMove);
beta = MIN(beta, best);
if (beta <= alpha){
std::cout << "min cutoff" << std::endl;
break;
}
}
}
return scoreMove;
//return beta;
}
return meilleur;
}
}
EDIT: I should note that the evaluateBoard only evaluates the mobility of pieces (number of possible moves, capture moves get a higher score depending on the piece captured)
Thank you.
I can see that you're trying to implement a mini-max algorithm. However, there is something in the code that makes me suspicious. We'll compare the code with the open-source Stockfish chess engine. Please refer to the search algorithm at https://github.com/mcostalba/Stockfish/blob/master/src/search.cpp
1. Passing Board b by value
You have this in your code:
alphaBeta(Board b, int alpha, int beta, char depth, bool nodeType)
I don't know what exactly "Board" is. But it doesn't look right to me. Let's look at Stockfish:
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth
depth, bool cutNode)
The position object is passed by reference in Stockfish. If "Board" is a class, the program will need to make a new copy everytime the alpha-beta function is called. In chess, when we have to evaluate many number of nodes, this is obviously unacceptable.
2. No hashing
Hashing is done in Stockfish as:
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE;
Without hashing, you'll need to evaluate the same position again and again and again and again. You won't go anywhere without hashing implemented.
3. Checking for checkmate
Probably not the most significant slow-down, but we should never check for checkmate in every single node. In Stockfish:
// All legal moves have been searched. A special case: If we're in check
// and no legal moves were found, it is checkmate.
if (InCheck && bestValue == -VALUE_INFINITE)
return mated_in(ss->ply); // Plies to mate from the root
This is done AFTER all possible moves are searched. We do it because we usually have many more non-checkmates node than checkmate-nodes.
4. Board bTemp(false);
This looks like a major slow-down. Let's take at Stockfish:
// Step 14. Make the move
pos.do_move(move, st, ci, givesCheck);
You should not create a temporary object in every node (creating an object of bTemp). The machine would need to allocate some stack space to save bTemp. This could be a serious performance penalty in particular if bTemp is not a primary variable (ie, not likely be cached by the processor). Stockfish simply modifies the internal data-structure without creating a new one.
5. bTemp.cloneBoard(b);
Similar to 4, even worse, this is done for every move in the node.
6. std::cout << "max cutoff" << std::endl;
Maybe it's hard to believe, printing to a terminal is much slower than processing. Here you're creating a potential slow-down that the string would need to be saved to an IO buffer. The function might (I'm not 100% sure) even block your program until the text is shown on the terminal. Stockfish only does it for statistic summary, definitely not everytime when you have a fail-high or fail-low.
7. Not sorting the PV move
Probably not something that you want to do before addressing the other issues. In Stockfish, they have:
std::stable_sort(RootMoves.begin() + PVIdx, RootMoves.end());
This is done for every iteration in an iterative-deepening framework.
I am only going to address the runtime cost problem of your algorithm, because I don't know the implementation details of your board evaluation function.
In order to keep things as simple as possible, I will assume the worst case for the algorithm.
The getMove function makes len1 calls to the alphaBeta function, which in turn makes len2 calls to itself, which in turn makes len3 calls to itself and so on until depth reaches 0 and the recursion stops.
Because of the worst case assumption, let's say n = max(len1, len2, ...), so you have
n * n * n * ... * n calls to alphaBeta with number of multiplications depending on depth d, which leads to n^d calls to alphaBeta which means that you have an exponential runtime behavior. This is ultra slow and only beaten by factorial runtime behavior.
I think you should take a look at the Big O notation for that purpose and try to optimize your algorithm accordingly to get much faster results.
Best regards,
OPM

Will BSP trees work on single, transparent objects?

I've been trying to implement a three dimensional BSP tree to render single objects (a cube, a box with a cylinder in it, etc.) which are transparent. As far as I understand, this should work, but it is not, and I can't figure out why. Everything I've read refers to BSP tree being used in either two dimensions or on multiple objects, so I was wondering if I've just generally misunderstood what BSP trees can be applied to rather than had an error in my code. I've looked at a lot of things online, and my code seems to be the same as Bretton Wade's (ftp://ftp.sgi.com/other/bspfaq/faq/bspfaq.html), so if anybody has any samples of BSP code for single objects/transparency in particular, that would be wonderful.
Thank you.
BSP trees can be abstracted to any N-dimensional space since by definition a N-dimensional hyperplane will bisect a space into two parts. In other words, for a given point in N-dimensional space, it must either be on the hyperplane, or in one of the bisected spaces that the hyperplane creates in the N-dimensional space.
For 2D, a BSP tree would be created by drawing a line, and then testing on what side of that line a point was. This is because a line bisects a 2D-space. For 3D, you would need a plane, which would typically be formed from the normal to the polygonal surface that you're using as the test.
So your algorithm would be something like the following:
Create a queue containing all the polys from the cube. It would be best to randomly add the polys to the queue rather than in some order.
Pop a poly from the front of the queue ... make this the "root" of the BSP tree.
Create a normal from that poly
Pop another poly from the queue
Test whether all the points in that poly are in front of or behind the normal created from the root.
If they are all in-front, then make that poly the right-child of the root. If they are all behind, make that poly the left-child of the root.
If all the points in the poly are not in front or behind the plane defined by the root polygon's normal, then you'll need to split the poly into two parts for the portions that are in-front and behind the plane. For the two new polys created from this split, add them to the back of the queue, and repeat from step #4.
Pop another poly from the queue.
Test this poly against the root. Since the root has a child, once you test whether the poly is in-front or behind the root (keeping in mind step #7 that may require a split), test the poly against the child node that is on the right if it's in-front, or the child node on the left if it's behind. If there is no child-node, then you can stop moving through the tree, and add the polygon as to the tree as that child.
For any child node you run into where the current poly is not in-front or behind, you'll need to perform the split in step #7 and then go back to step #4.
Keep repeating this process until the queue is empty.
Code for this algorithm would conceptually look something like:
struct bsp_node
{
std::vector<poly_t> polys;
bsp_node* rchild;
bsp_node* lchild;
bsp_node(const poly_t& input): rchild(NULL), lchild(NULL)
{
polys.push_back(input);
}
};
std::queue<poly_t> poly_queue;
//...add all the polygons in the scene randomly to the queue
bsp_node* bsp_root = new bsp_node(poly_queue.front());
poly_queue.pop();
while(!poly_queue.empty())
{
//grab a poly from the queue
poly_t current_poly = poly_queue.front();
poly_queue.pop();
//search the binary tree
bsp_node* current_node = bsp_root;
bsp_node* prev_node = NULL;
bool stop_search = false;
while(current_node != NULL && !stop_search)
{
//use a plane defined by the current_node to test current_poly
int result = test(current_poly, current_node);
switch(result)
{
case COINCIDENT:
stop_search = true;
current_node->polys.push_back(current_poly);
break;
case IN_FRONT:
prev_node = current_node;
current_node = current_node->rchild;
break;
case BEHIND:
prev_node = current_node;
current_node = current_node->lchild;
break;
//split the poly and add the newly created polygons back to the queue
case SPLIT:
stop_search = true;
split_current_poly(current_poly, poly_queue);
break;
}
}
//if we reached a NULL child, that means we can add the poly to the tree
if (!current_node)
{
if (prev_node->rchild == NULL)
prev_node->rchild = new bsp_node(current_poly);
else
prev_node->lchild = new bsp_node(current_poly);
}
}
Once you've completed the creation of the tree, you can then do an in-order search of the tree and get the polygons sorted from back-to-front. It won't matter if the objects are transparent or not, since you're sorting based on the polys themselves, not their material properties.

KD tree, slow tree construction

I am trying to build KD Tree (static case). We assume points are sorted on both x and y coordinates.
For even depth of recursion the set is split into two subsets with a vertical line going through median x coordinate.
For odd depth of recursion the set is split into two subsets with a horizontal line going through median y coordinate.
The median can be determined from sorted set according to x / y coordinate. This step I am doing before each splitting of the set. And I think that it causes the slow construction of the tree.
Please could you help me check any and optimize the code?
I can not find the k-th nearest neighbor, could somebody help me with the code?
Thank you very much for your help and patience...
Please see the sample code:
class KDNode
{
private:
Point2D *data;
KDNode *left;
KDNode *right;
....
};
void KDTree::createKDTree(Points2DList *pl)
{
//Create list
KDList kd_list;
//Create KD list (all input points)
for (unsigned int i = 0; i < pl->size(); i++)
{
kd_list.push_back((*pl)[i]);
}
//Sort points by x
std::sort(kd_list.begin(), kd_list.end(), sortPoints2DByY());
//Build KD Tree
root = buildKDTree(&kd_list, 1);
}
KDNode * KDTree::buildKDTree(KDList *kd_list, const unsigned int depth)
{
//Build KD tree
const unsigned int n = kd_list->size();
//No leaf will be built
if (n == 0)
{
return NULL;
}
//Only one point: create leaf of KD Tree
else if (n == 1)
{
//Create one leaft
return new KDNode(new Point2D ((*kd_list)[0]));
}
//At least 2 points: create one leaf, split tree into left and right subtree
else
{
//New KD node
KDNode *node = NULL;
//Get median index
const unsigned int median_index = n/2;
//Create new KD Lists
KDList kd_list1, kd_list2;
//The depth is even, process by x coordinate
if (depth%2 == 0)
{
//Create new median node
node = new KDNode(new Point2D( (*kd_list)[median_index]));
//Split list
for (unsigned int i = 0; i < n; i++)
{
//Geta actual point
Point2D *p = &(*kd_list)[i];
//Add point to the first list: x < median.x
if (p->getX() < (*kd_list)[median_index].getX())
{
kd_list1.push_back(*p);
}
//Add point to the second list: x > median.x
else if (p->getX() > (*kd_list)[median_index].getX())
{
kd_list2.push_back(*p);
}
}
//Sort points by y for the next recursion step: slow construction of the tree???
std::sort(kd_list1.begin(), kd_list1.end(), sortPoints2DByY());
std::sort(kd_list2.begin(), kd_list2.end(), sortPoints2DByY());
}
//The depth is odd, process by y coordinates
else
{
//Create new median node
node = new KDNode(new Point2D((*kd_list)[median_index]));
//Split list
for (unsigned int i = 0; i < n; i++)
{
//Geta actual point
Point2D *p = &(*kd_list)[i];
//Add point to the first list: y < median.y
if (p->getY() < (*kd_list)[median_index].getY())
{
kd_list1.push_back(*p);
}
//Add point to the second list: y < median.y
else if (p->getY() >(*kd_list)[median_index].getY())
{
kd_list2.push_back(*p);
}
}
//Sort points by x for the next recursion step: slow construction of the tree???
std::sort(kd_list1.begin(), kd_list1.end(), sortPoints2DByX());
std::sort(kd_list2.begin(), kd_list2.end(), sortPoints2DByX());
}
//Build left subtree
node->setLeft( buildKDTree(&kd_list1, depth +1 ) );
//Build right subtree
node->setRight( buildKDTree(&kd_list2, depth + 1 ) );
//Return new node
return node;
}
}
The sorting to find the median is probably the worst culprit here, since that is O(nlogn) while the problem is solvable in O(n) time. You should use nth_element instead: http://www.cplusplus.com/reference/algorithm/nth_element/. That'll find the median in linear time on average, after which you can split the vector in linear time.
Memory management in vector is also something that can take a lot of time, especially with large vectors, since every time the vector's size is doubled all the elements have to be moved. You can use the reserve method of vector to reserve exactly enough space for the vectors in the newly created nodes, so they need not increase dynamically as new stuff is added with push_back.
And if you absolutely need the best performance, you should use lower level code, doing away with vector and reserving plain arrays instead. Nth element or 'selection' algorithms are readily available and not too hard to write yourself: http://en.wikipedia.org/wiki/Selection_algorithm
Some hints on optimizing the kd-tree:
Use a linear time median finding algorithm, such as QuickSelect.
Avoid actually using "node" objects. You can store whole tree using the points only, with ZERO additional information. Essentially by just sorting an array of objects. The root node will then be in the middle. A rearrangement that puts the root first, then uses a heap layout will likely be nicer to the CPU memory cache on query time, but more tricky to build.
Not really an answer to your questions, but I would highly recommend the forum at http://ompf.org/forum/
They have some great discussions over there for fast kd-tree constructions in various contexts. Perhaps you'll find some inspiration over there.
Edit:
The OMPF forums have since gone down, although a direct replacement is currently available at http://ompf2.com/
Your first culprit is sorting to find the median. This is almost always the bottleneck for K-d tree construction, and using more efficient algorithms here will really pay off.
However, you're also constructing a pair of variable-sized vectors each time you split and transferring elements to them.
Here I recommend the good ol' singly-linked list. The beauty of the linked list is that you can transfer elements from parent to child by simply changing next pointers to point at the child's root pointer instead of the parent's.
That means no heap overhead whatsoever during construction to transfer elements from parent nodes to child nodes, only to aggregate the initial list of elements to insert to the root. That should do wonders as well, but if you want even faster, you can use a fixed allocator to efficiently allocate nodes for the linked list (as well as for the tree) and with better contiguity/cache hits.
Last but not least, if you're involved in intensive computing tasks that call for K-d trees, you need a profiler. Measure your code and you'll see exactly what lies at the culprit, and with exact time distributions.