Weighted Quick Union Find - java

I am taking an algorithms course where they go over weighted quick union find. I am confused about why we are concerned about the size of a tree as opposed to the depth?
When I tried writing out the code, my code looked different than the solution provided.
From my understanding, the size of the tree (total number of nodes in a tree) is not as important as the depth of the tree when it comes to the run time of the union function (lg n) because it is the depth that will determine how many look ups are needed to get to the root of a node?
Thanks
My code:
public void union(int p, int q) {
int root_p = root(p);
int root_q = root(q);
// If the two trees are not already connected, union them
if(root_p != root_q) {
// The two trees aren't connected, check which is deeper
// Attach the one that is more shallow to the deeper one
if (depth[root_p] > depth[root_q]) {
// p is deeper, point q's root to p
id[root_q] = root_p;
} else if (depth[root_q] > depth[root_p]) {
// q is deeper, point p's root to p
id[root_p] = root_q;
} else {
// They are of equal depth, point q's root to p and increment p's depth by 1
id[root_q] = root_p;
depth[root_p] += 1;
}
}
}
Solution code provided:
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
// make smaller root point to larger one
if (size[rootP] < size[rootQ]) {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
else {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
}
count--;
}

You are correct that the depth (actually height) is more directly related to the run time, but using either one will result in O(log N) run time for union and find.
The proof is easy -- Given that when we begin (when all sets are disjoint), every root with height h has at least 2(h-1) nodes, this invariant is maintained by union and find operations. Therefore, if a node has size n, then its height will be at most floor(log2(n))+1
So either one will do. BUT, very soon you will learn about path compression, which makes it difficult to keep track of the height of roots, but the size will still be available. At that point you will be able to use rank, which is kind of like height, or continue to use the size. Again either one will do, but I find the size easier to reason about so I always use that.

Related

Really slow Dijkstra algorithm, what am I doing wrong?

I was tasked to perform the Dijkstra Algorithm on big graphs (25 million nodes). These are represented as a 2D array: -each node as a double[] with latitude, longitude and offset (offset meaning index of the first outgoing edge of that node)
-each edge as a int[] with sourceNodeId,targetNodeId and weight of that edge
Below is the code, I used int[] as a tupel for the comparison in the priority queue.
The algorithm is working and gets the right results HOWEVER it is required to be finished in 15s but takes like 8min on my laptop. Is my algorithm fundamentally slow? Am I using the wrong data structures? Am I missing something? I tried my best optimizing as far as I saw fit.
Any help or any ideas would be greatly appreciated <3
public static int[] oneToAllArray(double[][]nodeList, int[][]edgeList,int sourceNodeId) {
int[] distance = new int[nodeList[0].length]; //the array that will be returned
//the priorityQueue will use arrays with the length 2, representing [index, weight] for each node and order them by their weight
PriorityQueue<int[]> prioQueue = new PriorityQueue<>((a, b) -> ((int[])a)[1] - ((int[])b)[1]);
int offset1; //used for determining the amount of outgoing edges
int offset2;
int newWeight; //declared here so we dont need to declare it a lot of times later (not sure if that makes a difference)
//currentSourceNode here means the node that will be looked at for OUTGOING edges
int[] currentSourceNode= {sourceNodeId,0};
prioQueue.add(currentSourceNode);
//at the start we only add the sourceNode, then we start the actual algorithm
while(!prioQueue.isEmpty()) {
if(prioQueue.size() % 55 == 2) {
System.out.println(prioQueue.size());
}
currentSourceNode=prioQueue.poll();
int sourceIndex = currentSourceNode[0];
if(sourceIndex == nodeList[0].length-1) {
offset1= (int) nodeList[2][sourceIndex];
offset2= edgeList[0].length;
} else {
offset1= (int) nodeList[2][sourceIndex];
offset2= (int) nodeList[2][sourceIndex+1];
}
//checking every outgoing edge for the currentNode
for(int i=offset1;i<offset2;i++) {
int targetIndex = edgeList[1][i];
//if the node hasnt been looked at yet, the weight is just the weight of this edge + distance to sourceNode
if(distance[targetIndex]==0&&targetIndex!=sourceNodeId) {
distance[targetIndex] = distance[sourceIndex] + edgeList[2][i];
int[]targetArray = {targetIndex, distance[targetIndex]};
prioQueue.add(targetArray);
} else if(prioQueue.stream().anyMatch(e -> e[0]==targetIndex)) {
//above else if checks if this index is already in the prioQueue
newWeight=distance[sourceIndex]+edgeList[2][i];
//if new weight is better, we have to update the distance + the prio queue
if(newWeight<distance[targetIndex]) {
distance[targetIndex]=newWeight;
int[] targetArray;
targetArray=prioQueue.stream().filter(e->e[0]==targetIndex).toList().get(0);
prioQueue.remove(targetArray);
targetArray[1]=newWeight;
prioQueue.add(targetArray);
}
}
}
}
return distance;
}
For each node that you process, you are doing a linear scan of the priority queue to see if something is already queued, and a second scan to find all the things that are queued if you have to update the distance. Instead, keep a separate multi-set of things that are in the queue.
This is not a proper Dijkstra's implementation.
One of the key elements of Dijkstra is that you mark nodes as "visited" when they have been evaluated and prevent looking at them again because you can't do any better. You are not doing that, so your algorithm is doing many many more computations than necessary. The only place where a priority queue or sort is required is to pick the next node to visit, from amongst the unvisited. You should re-read the algorithm, implement the "visitation tracking" and re-formulate.

Algorithm to detect and combine overlapping / colliding circles

I'm trying to write a time efficient algorithm that can detect a group of overlapping circles and make a single circle in the "middle" of the group that will represent that group. The practical application of this is representing GPS locations over a map, put the conversion in to Cartesian co-ordinates is already handled so that's not relevant, the desired effect is that at different zoom levels clusters of close together points just appear as a single circle (that will have the number of points printed in the centre in the final version)
In this example the circles just have a radius of 15 so the distance calculation (Pythagoras) is not being square rooted and compared to 225 for the collision detection. I was trying anything to shave off time, but the problem is this really needs to happen very quickly becasue it's a user facing bit of code that needs to be snappy and good looking.
I've given this a go and I it works with small data sets pretty well. 2 big problems, it takes too long and it can run out of memory if all the points are on top of one another.
The route I've taken is to calculate distance between each point in a first pass, and then take the shortest distance first and start to combine from there, anything that's been combined becomes ineligible for combination on that pass, and the whole list is passed back around to the distance calculations again until nothing changes.
To be honest I think it needs a radical shift in approach and I think it's a little beyond me. I've re factored my code in to one class for ease of posting and generated random points to give an example.
package mergepoints;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Merger {
public static void main(String[] args) {
Merger m = new Merger();
m.subProcess(m.createRandomList());
}
private List<Plottable> createRandomList() {
List<Plottable> points = new ArrayList<>();
for (int i = 0; i < 50000; i++) {
Plottable p = new Plottable();
p.location = new Point((int) Math.floor(Math.random() * 1000),
(int) Math.floor(Math.random() * 1000));
points.add(p);
}
return points;
}
private List<Plottable> subProcess(List<Plottable> visible) {
List<PlottableTuple> tuples = new ArrayList<PlottableTuple>();
// create a tuple to store distance and matching objects together,
for (Plottable p : visible) {
PlottableTuple tuple = new PlottableTuple();
tuple.a = p;
tuples.add(tuple);
}
// work out each Plottable relative distance from
// one another and order them by shortest first.
// We may need to do this multiple times for one set so going in own
// method.
// this is the bit that takes ages
setDistances(tuples);
// Sort so that smallest distances are at the top.
// parse the set and combine any pair less than the smallest distance in
// to a combined pin.
// any plottable thats been combine is no longer eligable for combining
// so ignore on this parse.
List<PlottableTuple> sorted = new ArrayList<>(tuples);
Collections.sort(sorted);
Set<Plottable> done = new HashSet<>();
Set<Plottable> mergedSet = new HashSet<>();
for (PlottableTuple pt : sorted) {
if (!done.contains(pt.a) && pt.distance <= 225) {
Plottable merged = combine(pt, done);
done.add(pt.a);
for (PlottableTuple tup : pt.others) {
done.add(tup.a);
}
mergedSet.add(merged);
}
}
// if we haven't processed anything we are done just return visible
// list.
if (done.size() == 0) {
return visible;
} else {
// change the list to represent the new combined plottables and
// repeat the process.
visible.removeAll(done);
visible.addAll(mergedSet);
return subProcess(visible);
}
}
private Plottable combine(PlottableTuple pt, Set<Plottable> done) {
List<Plottable> plottables = new ArrayList<>();
plottables.addAll(pt.a.containingPlottables);
for (PlottableTuple otherTuple : pt.others) {
if (!done.contains(otherTuple.a)) {
plottables.addAll(otherTuple.a.containingPlottables);
}
}
int x = 0;
int y = 0;
for (Plottable p : plottables) {
Point position = p.location;
x += position.x;
y += position.y;
}
x = x / plottables.size();
y = y / plottables.size();
Plottable merged = new Plottable();
merged.containingPlottables.addAll(plottables);
merged.location = new Point(x, y);
return merged;
}
private void setDistances(List<PlottableTuple> tuples) {
System.out.println("pins: " + tuples.size());
int loops = 0;
// Start from the first item and loop through, then repeat but starting
// with the next item.
for (int startIndex = 0; startIndex < tuples.size() - 1; startIndex++) {
// Get the data for the start Plottable
PlottableTuple startTuple = tuples.get(startIndex);
Point startLocation = startTuple.a.location;
for (int i = startIndex + 1; i < tuples.size(); i++) {
loops++;
PlottableTuple compareTuple = tuples.get(i);
double distance = distance(startLocation, compareTuple.a.location);
setDistance(startTuple, compareTuple, distance);
setDistance(compareTuple, startTuple, distance);
}
}
System.out.println("loops " + loops);
}
private void setDistance(PlottableTuple from, PlottableTuple to,
double distance) {
if (distance < from.distance || from.others == null) {
from.distance = distance;
from.others = new HashSet<>();
from.others.add(to);
} else if (distance == from.distance) {
from.others.add(to);
}
}
private double distance(Point a, Point b) {
if (a.equals(b)) {
return 0.0;
}
double result = (((double) a.x - (double) b.x) * ((double) a.x - (double) b.x))
+ (((double) a.y - (double) b.y) * ((double) a.y - (double) b.y));
return result;
}
class PlottableTuple implements Comparable<PlottableTuple> {
public Plottable a;
public Set<PlottableTuple> others;
public double distance;
#Override
public int compareTo(PlottableTuple other) {
return (new Double(distance)).compareTo(other.distance);
}
}
class Plottable {
public Point location;
private Set<Plottable> containingPlottables;
public Plottable(Set<Plottable> plots) {
this.containingPlottables = plots;
}
public Plottable() {
this.containingPlottables = new HashSet<>();
this.containingPlottables.add(this);
}
public Set<Plottable> getContainingPlottables() {
return containingPlottables;
}
}
}
Map all your circles on a 2D grid first. You then only need to compare the circles in a cell with the other circles in that cell and in it's 9 neighbors (you can reduce that to five by using a brick pattern instead of a regular grid).
If you only need to be really approximate, then you can just group all the circles that fall into a cell together. You will probably also want to merge cells that only have a small number of circles together with there neighbors, but this will be fast.
This problem is going to take a reasonable amount of computation no matter how you do it, the question then is: can you do all the computation up-front so that at run-time it's just doing a look-up? I would build a tree-like structure where each layer is all the points that need to be drawn for a given zoom level. It takes more computation up-front, but at run-time you are simply drawing a list of point, fast.
My idea is to decide what the resolution of each zoom level is (ie at zoom level 1 points closer than 15 get merged; at zoom level 2 points closer than 30 get merged), then go through your points making groups of points that are within the 15 of each other and pick a point to represent group that group at the higher zoom. Now you have a 2 layer tree. Then you pass over the second layer grouping all points that are within 30 of each other, and so on all the way up to your highest zoom level. Now save this tree structure to file, and at run-time you can very quickly change zoom levels by simply drawing all points at the appropriate tree level. If you need to add or remove points, that can be done dynamically by figuring out where to attach them to the tree.
There are two downsides to this method that come to mind: 1) it will take a long time to compute the tree, but you only have to do this once, and 2) you'll have to think really carefully about how you build the tree, based on how you want the groupings to be done at higher levels. For example, in the image below the top level may not be the right grouping that you want. Maybe instead building the tree based off the previous layer, you always want to go back to the original points. That said, some loss of precision always happens when you're trying to trade-off for faster run-time.
EDIT
So you have a problem which requires O(n^2) comparisons, you say it has to be done in real-time, can not be pre-computed, and has to be fast. Good luck with that.
Let's analyze the problem a bit; if you do no pre-computation then in order to decide which points can be merged you have to compare every pair of points, that's O(n^2) comparisons. I suggested building a tree before-hand, O(n^2 log n) once, but then runtime is just a lookup, O(1). You could also do something in between where you do some work before and some at run-time, but that's how these problems always go, you have to do a certain amount of computation, you can play games by doing some of it earlier, but at the end of the day you still have to do the computation.
For example, if you're willing to do some pre-computation, you could try keeping two copies of the list of points, one sorted by x-value and one sorted by y-value, then instead of comparing every pair of points, you can do 4 binary searches to find all the points within, say, a 30 unit box of the current point. More complicated so would be slower for a small number of points (say <100), but would reduce the overall complexity to O(n log n), making it faster for large amounts of data.
EDIT 2
If you're worried about multiple points at the same location, then why don't you do a first pass removing the redundant points, then you'll have a smaller "search list"
list searchList = new list()
for pt1 in points :
boolean clean = true
for pt2 in searchList :
if distance(pt1, pt2) < epsilon :
clean = false
break
if clean :
searchList.add(pt1)
// Now you have a smaller list to act on with only 1 point per cluster
// ... I guess this is actually the same as my first suggestion if you make one of these search lists per zoom level. huh.
EDIT 3: Graph Traversal
A totally new approach would be to build a graph out of the points and do some sort of longest-edge-first graph traversal on them. So pick a point, draw it, and traverse its longest edge, draw that point, etc. Repeat this until you come to a point which doesn't have any untraversed edges longer than your zoom resolution. The number of edges per point gives you an easy way to tradeoff speed for correctness. If the number of edges per point was small and constant, say 4, then with a bit of cleverness you could build the graph in O(n) time and also traverse it to draw points in O(n) time. Fast enough to do it on the fly with no pre-computation.
Just a wild guess and something that occurred to me while reading responses from others.
Do a multi-step comparison. Assume your combining distance at the current zoom level is 20 meters. First, subtract (X1 - X2). If This is bigger than 20 meters then you are done, the points are too far. Next, subtract (Y1 - Y2) and do the same thing to reject combining the points.
You could stop here and be happy if you are good with using only horizontal/vertical distances as your metric for combining. Much less math (no squaring or square roots). Pythagoras wouldn't be happy but your users might.
If you really insist on exact answers, do the two subtraction/comparison steps above. If the points are within horizontal and vertical limits, THEN you do the full Pythagoras check with square roots.
Assuming all your points are not highly clustered very close to the combining limit, this should save some CPU cycles.
This is still approximately an O(n^2) technique, but the math should be simpler. If you have the memory, you could store distances between each set of points and then you never have to compute it again. This could take up more memory than you have and also grows at a rate of approximately O(n^2), so be careful.
Also, you could make a linked list or sorted array of all your points, sorted in order of increasing X or increasing Y. (I don't think you need both, just one). Then walk through the list in sorted order. For each point, check the neighbors out until (X1 - X2) is bigger than your combining distance. and then stop. You don't have to compare each set of points for O(N^2), you only have to compare neighbors that are close in one dimension to quickly prune your large list to a small one. As you move through the list, you only have to compare points that have a bigger X than your current candidate, because you already compared and combined with all previous X values. This gets you closer to the O(n) complexity you want. Of course, you would need to check the Y dimension and fully qualify the points to be combined before you actually do it. Don't just use the X distance to make your combining decision.

A* (A star) algorithm optimization

I'm a student and me and my team have to make a simulation of student's behaviour in a campus (like making "groups of friends") walking etc. For finding path that student has to go, I used A* algorithm (as I found out that its one of fastest path-finding algorithms). Unfortunately our simulation doesn't run fluently (it takes like 1-2 sec between successive iterations). I wanted to optimize the algorithm but I don't have any idea what I can do more. Can you guys help me out and share with me information if its possible to optimize my A* algorithm? Here goes code:
public LinkedList<Field> getPath(Field start, Field exit) {
LinkedList<Field> foundPath = new LinkedList<Field>();
LinkedList<Field> opensList= new LinkedList<Field>();
LinkedList<Field> closedList= new LinkedList<Field>();
Hashtable<Field, Integer> gscore = new Hashtable<Field, Integer>();
Hashtable<Field, Field> cameFrom = new Hashtable<Field, Field>();
Field x = new Field();
gscore.put(start, 0);
opensList.add(start);
while(!opensList.isEmpty()){
int min = -1;
//searching for minimal F score
for(Field f : opensList){
if(min==-1){
min = gscore.get(f)+getH(f,exit);
x = f;
}else{
int currf = gscore.get(f)+getH(f,exit);
if(min > currf){
min = currf;
x = f;
}
}
}
if(x == exit){
//path reconstruction
Field curr = exit;
while(curr != start){
foundPath.addFirst(curr);
curr = cameFrom.get(curr);
}
return foundPath;
}
opensList.remove(x);
closedList.add(x);
for(Field y : x.getNeighbourhood()){
if(!(y.getType()==FieldTypes.PAVEMENT ||y.getType() == FieldTypes.GRASS) || closedList.contains(y) || !(y.getStudent()==null))
{
continue;
}
int tentGScore = gscore.get(x) + getDist(x,y);
boolean distIsBetter = false;
if(!opensList.contains(y)){
opensList.add(y);
distIsBetter = true;
}else if(tentGScore < gscore.get(y)){
distIsBetter = true;
}
if(distIsBetter){
cameFrom.put(y, x);
gscore.put(y, tentGScore);
}
}
}
return foundPath;
}
private int getH(Field start, Field end){
int x;
int y;
x = start.getX()-end.getX();
y = start.getY() - end.getY();
if(x<0){
x = x* (-1);
}
if(y<0){
y = y * (-1);
}
return x+y;
}
private int getDist(Field start, Field end){
int ret = 0;
if(end.getType() == FieldTypes.PAVEMENT){
ret = 8;
}else if(start.getX() == end.getX() || start.getY() == end.getY()){
ret = 10;
}else{
ret = 14;
}
return ret;
}
//EDIT
This is what i got from jProfiler:
So getH is a bottlneck yes? Maybe remembering H score of field would be a good idea?
A linked list is not a good data structure for the open set. You have to find the node with the smallest F from it, you can either search through the list in O(n) or insert in sorted position in O(n), either way it's O(n). With a heap it's only O(log n). Updating the G score would remain O(n) (since you have to find the node first), unless you also added a HashTable from nodes to indexes in the heap.
A linked list is also not a good data structure for the closed set, where you need fast "Contains", which is O(n) in a linked list. You should use a HashSet for that.
You can optimize the problem by using a different algorithm, the following page illustrates and compares many different aglorihms and heuristics:
A*
IDA*
Djikstra
JumpPoint
...
http://qiao.github.io/PathFinding.js/visual/
From your implementation it seems that you are using naive A* algorithm. Use following way:-
A* is algorithm which is implemented using priority queue similar to BFS.
Heuristic function is evaluated at each node to define its fitness to be selected as next node to be visited.
As new node is visited its neighboring unvisited nodes are added into queue with its heuristic values as keys.
Do this till every heuristic value in the queue is less than(or greater) calculated value of goal state.
Find bottlenecks of your implementation using profiler . ex. jprofiler is easy to use
Use threads in areas where algorithm can run simultaneously.
Profile your JavaVM to run faster.
Allocate more RAM
a) As mentioned, you should use a heap in A* - either a basic binary heap or a pairing heap which should be theoretically faster.
b) In larger maps, it always happens that you need some time for the algorithm to run (i.e., when you request a path, it will simply have to take some time). What can be done is to use some local navigation algorithm (e.g., "run directly to the target") while the path computes.
c) If you have reasonable amount of locations (e.g., in a navmesh) and some time at the start of your code, why not to use Floyd-Warshall's algorithm? Using that, you can the information where to go next in O(1).
I built a new pathfinding algorithm. called Fast* or Fastaer, It is a BFS like A* but is faster and efficient than A*, the accuracy is 90% A*. please see this link for info and demo.
https://drbendanilloportfolio.wordpress.com/2015/08/14/fastaer-pathfinder/
It has a fast greedy line tracer, to make path straighter.
The demo file has it all. Check Task manager when using the demo for performance metrics. So far upon building this the profiler results of this has maximum surviving generation of 4 and low to nil GC time.

Need help calculating the depth of left and right subbranches any binary tree

I have to write an AVL tree for my data structures course and am stuck on calculating the balance factor for a subtree so that I know where and how to rotate the tree.
Thanks,
Eric
edit:
I know have to count to number of nodes in a binary tree.
private int countTotalNodes(AVLNode<T> start){
AVLNode<T> current = start;
if(current.getLeft() != null){
return countTotalNodes(current.getLeft());
}
if(current.getRight() != null){
return countTotalNodes(current.getRight());
}
return 1;
}
I think the usual implementation for an AVL tree stores the height of a node in the node itself and gets updated in insert, cut-and-link, operations. After those operations we then have to check if the height of the higher up nodes is still correct with something like this:
/**
* Recursively updates heights starting with given node.
* If height of given node is already correct we know
* that we can stop.
*/
private void updateHeights(AvlNode<T> node){
if(node == null) return;
int heightLeft = node.left != null ? node.left.height : -1;
int heightRight = node.right != null ? node.right.height : -1;
int height = heightLeft > heightRight ? heightLeft + 1 : heightRight + 1;
if(node.height != height){
node.height = height;
updateHeights(node.parent);
}
}
That's always called on the parent of the highest changed node so to speak of. Ah good old times - implementing an AVL tree is a fun little project - good luck.. and test it thouroughly!
The usual approach is to add a balance factor field to the data structure of a tree node. Changes to the balance factor happen on inserts and deletes, and the changes propagate as rotations are made to keep things in balance. There's a nice explanation of this, with pseudocode, here.
Computing the balance at each insert or delete (instead of keeping the balance as a bit of extra bookkeeping at each node) makes those operations much more expensive.
Write the method to calculate the depth of a tree and then apply it to left and right sub-trees.
That's the beauty of the tree data structure: it's naturally self-similar and recursive.
Your counting function for number of nodes is wrong (except for very degenerated trees) - it counts either the left or the right subtree, but never both. Try to correct this first.
Then think about how you can use a similar algorithm to construct the depth.
But as said in other answers, don't use this for balancing your tree, as the performance penalty of this will be more than all the benefits of having a balanced tree. Instead store your depth in the nodes and think about when it needs to be adapted.

Calculating longest path

I have a n-ary tree which contains key values (integers) in each node. I would like to calculate the minimum depth of the tree. Here is what I have come up with so far:
int min = 0;
private int getMinDepth(Node node, int counter, int temp){
if(node == null){
//if it is the first branch record min
//otherwise compare min to this value
//and record the minimum value
if(counter == 0){
temp = min;
}else{
temp = Math.min(temp, min);
min = 0;
}
counter++;//counter should increment by 1 when at end of branch
return temp;
}
min++;
getMinDepth(node.q1, counter, min);
getMinDepth(node.q2, counter, min);
getMinDepth(node.q3, counter, min);
getMinDepth(node.q4, counter, min);
return temp;
}
The code is called like so:
int minDepth = getMinDepth(root, 0, 0);
The idea is that if the tree is traversing down the first branch (branch number is tracked by counter), then we set the temp holder to store this branch depth. From then on, compare the next branch length and if it smaller, then make temp = that length. For some reason counter is not incrementing at all and always staying at zero. Anyone know what I am doing wrong?
I think you're better off doing a breadth-first search. Your current implementation tries to be depth-first, which means it could end up exploring the whole tree if the branches happen to be in an awkward order.
To do a breadth-first search, you need a queue (a ArrayDeque is probably the right choice). You'll then need a little class that holds a node and a depth. The algorithm goes a little something like this:
Queue<NodeWithDepth> q = new ArrayDeque<NodeWithDepth>();
q.add(new NodeWithDepth(root, 1));
while (true) {
NodeWithDepth nwd = q.remove();
if (hasNoChildren(nwd.node())) return nwd.depth();
if (nwd.node().q1 != null) q.add(new NodeWithDepth(nwd.node().q1, nwd.depth() + 1));
if (nwd.node().q2 != null) q.add(new NodeWithDepth(nwd.node().q2, nwd.depth() + 1));
if (nwd.node().q3 != null) q.add(new NodeWithDepth(nwd.node().q3, nwd.depth() + 1));
if (nwd.node().q4 != null) q.add(new NodeWithDepth(nwd.node().q4, nwd.depth() + 1));
}
This looks like it uses more memory than a depth-first search, but when you consider that stack frames consume memory, and that this will explore less of the tree than a depth-first search, you'll see that's not the case. Probably.
Anyway, see how you get on with it.
You are passing the counter variable by value, not by reference. Thus, any changes made to it are local to the current stack frame and are lost as soon as the function returns and that frame is popped of the stack. Java doesn't support passing primitives (or anything really) by reference, so you'd either have to pass it as a single element array or wrap it in an object to get the behavior you're looking for.
Here's a simpler (untested) version that avoids the need to pass a variable by reference:
private int getMinDepth(QuadTreeNode node){
if(node == null)
return 0;
return 1 + Math.min(
Math.min(getMinDepth(node.q1), getMinDepth(node.q2)),
Math.min(getMinDepth(node.q3), getMinDepth(node.q4)));
}
Both your version and the one above are inefficient because they search the entire tree, when really you only need to search down to the shallowest depth. To do it efficiently, use a queue to do a breadth-first search like Tom recommended. Note however, that the trade-off required to get this extra speed is the extra memory used by the queue.
Edit:
I decided to go ahead and write a breadth first search version that doesn't assume you have a class that keeps track of the nodes' depths (like Tom's NodeWithDepth). Once again, I haven't tested it or even compiled it... But I think it should be enough to get you going even if it doesn't work right out of the box. This version should perform faster on large, complex trees, but also uses more memory to store the queue.
private int getMinDepth(QuadTreeNode node){
// Handle the empty tree case
if(node == null)
return 0;
// Perform a breadth first search for the shallowest null child
// while keeping track of how deep into the tree we are.
LinkedList<QuadTreeNode> queue = new LinkedList<QuadTreeNode>();
queue.addLast(node);
int currentCountTilNextDepth = 1;
int nextCountTilNextDepth = 0;
int depth = 1;
while(!queue.isEmpty()){
// Check if we're transitioning to the next depth
if(currentCountTilNextDepth <= 0){
currentCountTilNextDepth = nextCountTilNextDepth;
nextCountTilNextDepth = 0;
depth++;
}
// If this node has a null child, we're done
QuadTreeNode node = queue.removeFirst();
if(node.q1 == null || node.q2 == null || node.q3 == null || node.q4 == null)
break;
// If it didn't have a null child, add all the children to the queue
queue.addLast(node.q1);
queue.addLast(node.q2);
queue.addLast(node.q3);
queue.addLast(node.q4);
// Housekeeping to keep track of when we need to increment our depth
nextCountTilNextDepth += 4;
currentCountTilNextDepth--;
}
// Return the depth of the shallowest node that had a null child
return depth;
}
Counter is always staying at zero because primitives in java are called by value. This means if you overwrite the value in a function call the caller won't see the change. Or if you're familiar with C++ notation it's foo(int x) instead of foo(int& x).
One solution would be to use an Integer object since objects are call-by-reference.
Since you're interested in the minimum depth a breadth first solution will work just fine, but you may get memory problems for large trees.
If you assume that the tree may become rather large an IDS solution would be the best. This way you'll get the time complexity of the breadth first variant with the space complexity of a depth first solution.
Here's a small example since IDS isn't as well known as its brethren (though much more useful for serious stuff!). I assume that every node has a list with children for simplicity (and since it's more general).
public static<T> int getMinDepth(Node<T> root) {
int depth = 0;
while (!getMinDepth(root, depth)) depth++;
return depth;
}
private static<T> boolean getMinDepth(Node<T> node, int depth) {
if (depth == 0)
return node.children.isEmpty();
for (Node<T> child : node.children)
if (getMinDepth(child, depth - 1)) return true;
return false;
}
For a short explanation see http://en.wikipedia.org/wiki/Iterative_deepening_depth-first_search

Categories