Skip to content

Commit

Permalink
KDTree.Clusters - Performance improvement
Browse files Browse the repository at this point in the history
Hides nodes using bitflag if the deeper branches has been completely traversed to stop traversal. Doing this mitigates the worst case of O(n^2).

Now with somewhat larger 20+ radii this function competes with and can even outperform the O(n) ClusterTPA
  • Loading branch information
slackydev committed Dec 27, 2024
1 parent 36737cf commit 2066a17
Showing 1 changed file with 15 additions and 6 deletions.
21 changes: 15 additions & 6 deletions Source/simba.container_kdtree.pas
Original file line number Diff line number Diff line change
Expand Up @@ -663,11 +663,8 @@ function TKDTree.WeightedKNearestClassify(Vector: TSingleArray; K: Int32): Int32
Implements an efficient n-dimensional spatial clustering algorithm using a KD-Tree.
It supports label-based filtering to cluster points within specific categories.
Average Time Complexity: O(max(n log n, n * n/k))
Worst Case Time Complexity: O(n^2)
Where `k` is the number of clusters output - essentially O(n^2) when there is only 1 group.
Average Time Complexity: O(n log n)
Worst Case Time Complexity: O(n log n)
TODO: Add a version that returns T2DIntegerArray where each version represents
an index in KDTree.Data.
Expand Down Expand Up @@ -701,6 +698,9 @@ function TKDTree.Clusters(Radii: TSingleArray): T2DKDItems;
goleft: Boolean = False;
splitDim: Int32;
begin
// Early exit if this node and its subtree are fully explored
if Byte(this^.hidden) = 2 then Exit;

splitDim := depth mod Self.Dimensions;

goleft := test.Vector[splitDim] - radii[splitDim] <= this^.Split.Vector[splitDim];
Expand All @@ -712,14 +712,23 @@ function TKDTree.Clusters(Radii: TSingleArray): T2DKDItems;
result[rescount] := this^.split;
Inc(rescount);

this^.Hidden := True;
Byte(this^.Hidden) := 1;

Inc(qcount);
queue[qcount] := this;
end;

if goleft and (this^.l <> -1) then Cluster(test, result, @self.data[this^.l], depth+1);
if goright and (this^.r <> -1) then Cluster(test, result, @self.data[this^.r], depth+1);

// Chop off branches completely visted! Avoids O(n^2) worst case
if (this^.Hidden) and
(((this^.l = -1) and (this^.r = -1)) or
((this^.l = -1) and (Byte(self.data[this^.r].hidden) = 2)) or
((this^.r = -1) and (Byte(self.data[this^.l].hidden) = 2)) or
((Byte(self.data[this^.l].hidden) = 2) and (Byte(self.data[this^.r].hidden) = 2)))
then
Byte(this^.Hidden) := 2;
end;

var
Expand Down

0 comments on commit 2066a17

Please sign in to comment.