Sorry if this a silly or wrong question, I found a solution in Java for short path algorithm. Here is the code:
import java.io.*;
import java.util.*;
public class Dijkstra {
private static final Graph.Edge[] GRAPH = {
new Graph.Edge("a", "b", 7),
new Graph.Edge("a", "c", 9),
new Graph.Edge("a", "f", 14),
new Graph.Edge("b", "c", 10),
new Graph.Edge("b", "d", 15),
new Graph.Edge("c", "d", 11),
new Graph.Edge("c", "f", 2),
new Graph.Edge("d", "e", 6),
new Graph.Edge("e", "f", 9),
};
private static final String START = "a";
private static final String END = "e";
public static void main(String[] args) {
Graph g = new Graph(GRAPH);
g.dijkstra(START);
g.printPath(END);
//g.printAllPaths();
}
}
class Graph {
private final Map<String, Vertex> graph; // mapping of vertex names to Vertex objects, built from a set of Edges
/** One edge of the graph (only used by Graph constructor) */
public static class Edge {
public final String v1, v2;
public final int dist;
public Edge(String v1, String v2, int dist) {
this.v1 = v1;
this.v2 = v2;
this.dist = dist;
}
}
/** One vertex of the graph, complete with mappings to neighbouring vertices */
public static class Vertex implements Comparable<Vertex> {
public final String name;
public int dist = Integer.MAX_VALUE; // MAX_VALUE assumed to be infinity
public Vertex previous = null;
public final Map<Vertex, Integer> neighbours = new HashMap<>();
public Vertex(String name) {
this.name = name;
}
private void printPath() {
if (this == this.previous) {
System.out.printf("%s", this.name);
} else if (this.previous == null) {
System.out.printf("%s(unreached)", this.name);
} else {
this.previous.printPath();
System.out.printf(" -> %s(%d)", this.name, this.dist);
}
}
public int compareTo(Vertex other) {
return Integer.compare(dist, other.dist);
}
}
/** Builds a graph from a set of edges */
public Graph(Edge[] edges) {
graph = new HashMap<>(edges.length);
//one pass to find all vertices
for (Edge e : edges) {
if (!graph.containsKey(e.v1)) graph.put(e.v1, new Vertex(e.v1));
if (!graph.containsKey(e.v2)) graph.put(e.v2, new Vertex(e.v2));
}
//another pass to set neighbouring vertices
for (Edge e : edges) {
graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist);
//graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also do this for an undirected graph
}
}
/** Runs dijkstra using a specified source vertex */
public void dijkstra(String startName) {
if (!graph.containsKey(startName)) {
System.err.printf("Graph doesn't contain start vertex \"%s\"\n", startName);
return;
}
final Vertex source = graph.get(startName);
NavigableSet<Vertex> q = new TreeSet<>();
// set-up vertices
for (Vertex v : graph.values()) {
v.previous = v == source ? source : null;
v.dist = v == source ? 0 : Integer.MAX_VALUE;
q.add(v);
}
dijkstra(q);
}
/** Implementation of dijkstra's algorithm using a binary heap. */
private void dijkstra(final NavigableSet<Vertex> q) {
Vertex u, v;
while (!q.isEmpty()) {
u = q.pollFirst(); // vertex with shortest distance (first iteration will return source)
if (u.dist == Integer.MAX_VALUE) break; // we can ignore u (and any other remaining vertices) since they are unreachable
//look at distances to each neighbour
for (Map.Entry<Vertex, Integer> a : u.neighbours.entrySet()) {
v = a.getKey(); //the neighbour in this iteration
final int alternateDist = u.dist + a.getValue();
if (alternateDist < v.dist) { // shorter path to neighbour found
q.remove(v);
v.dist = alternateDist;
v.previous = u;
q.add(v);
}
}
}
}
/** Prints a path from the source to the specified vertex */
public void printPath(String endName) {
if (!graph.containsKey(endName)) {
System.err.printf("Graph doesn't contain end vertex \"%s\"\n", endName);
return;
}
graph.get(endName).printPath();
System.out.println();
}
/** Prints the path from the source to every vertex (output order is not guaranteed) */
public void printAllPaths() {
for (Vertex v : graph.values()) {
v.printPath();
System.out.println();
}
}
}
I understood most of this algorithm, but the method private void dijkstra(final NavigableSet<Vertex> q) confuses me by the following question:
How is it being evaluated by rest of the code, as it doesnt have any return method?
Is using NavigableSet / TreeSet better than using PriorityQueue?
And, also I have a question about compareTo method which is overriden in class Vertex, how is it being called?
Thanks
How is it being evaluated by rest of the code, as it doesn't have any return method?
The part that is specifically doing the evaluating is:
u = q.pollFirst();
The part that effects what q.pollFirst() returns is:
if (alternateDist < v.dist) { // shorter path to neighbour found
q.remove(v);
v.dist = alternateDist;
v.previous = u;
q.add(v);
}
The v node gets removed from the set, it's distance is being updated, then it is re-added to the set.
The distance being updated is the most important part.
The node being removed then re-added is probably required so that the node is ordered by the new distance value rather than the old distance value.
The point of all that is so q.pollFirst() returns the node with the shortest distance.
Is using NavigableSet / TreeSet better than using PriorityQueue?
"better" is subjective. Are you looking for speed or a nicer interface, or a particular data structure?
From what I understand, both TreeSet and PriorityQueue uses Comparable to order nodes, so in that sense they work similarly.
Besides that, TreeSet is a set, so nodes can only exists once in the set, whereas, PriorityQueue is a queue and can have the same node inserted multiple times.
In this case, a set seems to work fine for the dijkstra algorithm.
How is the compareTo method which is overriden in class Vertex being called?
The compareTo function is used internally by the TreeSet to order the nodes as they are being added.
The new node is compared against the nodes already in the set. The Vertex#compareTo provides the algorithm to determine how two Vertex compare with one another. In this case, the distance value of the Vertex is compared.
This also hints as to why the nodes are removed and re-added to the set in the dijkstra(final NavigableSet<Vertex> q) function.
How is it being evaluated by rest of the code, as it doesnt have any return method?
The parameter of the method is a mutable container which is referenced by the parts of the algorithm.
final Vertex source = graph.get(startName);
NavigableSet<Vertex> q = new TreeSet<>();
// set-up vertices
for (Vertex v : graph.values()) {
v.previous = v == source ? source : null;
v.dist = v == source ? 0 : Integer.MAX_VALUE;
q.add(v);
}
dijkstra(q);
Is using NavigableSet / TreeSet better than using PriorityQueue?
you can find answer in this question Difference between PriorityQueue and TreeSet in Java?
1) There are two methods with the same name:
public void dijkstra(String startName)
private void dijkstra(final NavigableSet<Vertex> q)
If you call dijkstra("a"), the first method will be called, and if you call diijkstra(q) where q is a NavigableSet<Vertex>, then the second method will be called.
2) This depends on the underlying data structure used to implement TreeSet and PriorityQueue, as well as the input Graph. For some input graphs, an unsorted array could perform faster than a min-heap.
3) I suspect it will be a requirement for objects added to NavigableSet to have a working compareTo() method so that NavigableSet can work it's magic.
Related
I have a linked list of strings, each of those strings has an integer value from calculation. To explain things easier, I have a linked list of strings representing nodes. Each node has a distance. To get the distance, you use a method I created in my program to return that node's distance. I want that linked list to be sorted by each of the string's distance, from lowest to highest. How would I do that? Here is a psedocode
Queue<String> someStringList = new LinkedList<String>();
...
for each of the nodes in the list
String node = ((LinkedList<String>) someStringList).get(i);
distance = theNodesDistance(node);
sort the linked list by the node's distance
...
public int theNodesDistance(String str){
return distance;
}
If you can afford to waste CPU in repeated calculations of the distance, then you could use the JDK sorting methods with a Comparator implementation which would figure the distance from any 2 strings and compare them. This is the simplest.
If you would prefer calculating only once the distance for each string (presuming this is costly), then you either:
a) construct a new collection of tuples (string and distance), and sort the tuples by the distance (again with a comparator, or by making the tuple class comparable).
b) or you can try to cache the distances in a hashmap of strings-to-distance which the comparator would rely on.
The first option is to create a comparator and then sort the collection. I would do something like:
public static void main(String... arg) {
LinkedList<String> someStringList = new LinkedList<>();
someStringList.add("2");
someStringList.add("1");
System.out.println(someStringList);
Collections.sort(someStringList, new Comparator<String>() {
#Override
public int compare(String s1, String s2) {
return theNodesDistance(s1).compareTo(theNodesDistance(s2));
}
});
System.out.println(someStringList);
}
public static Integer theNodesDistance(String str){
return Integer.parseInt(str); // here return the distance
}
Another option is to create a class Node with an id and distance:
public class Node implements Comparable<Node> {
String id;
Integer distance;
public Node(String id) {
this.id = id;
this.distance = theNodesDistance(id);
}
#Override
public int compareTo(Node node) {
return this.distance.compareTo(node.distance);
}
public String toString() {
return id;
}
public Integer theNodesDistance(String str){
return Integer.parseInt(str);
}
}
Then, sort your list doing:
LinkedList<Node> nodes = new LinkedList<>();
nodes.add(new Node("2"));
nodes.add(new Node("1"));
System.out.println(nodes);
Collections.sort(nodes);
System.out.println(nodes);
Finally, you can use a PriorityQueue which organizes the elements whenever you insert a new node. I mean, you can remove each node in order.
Queue<Node> nodes = new PriorityQueue<>();
nodes.add(new Node("10"));
nodes.add(new Node("3"));
nodes.add(new Node("2"));
nodes.add(new Node("1"));
nodes.add(new Node("4"));
System.out.println(nodes.remove());
System.out.println(nodes.remove());
System.out.println(nodes.remove());
System.out.println(nodes.remove());
System.out.println(nodes.remove());
In this case, the output will be:
1
2
3
4
10
You can use TreeMap.. Following is a normal demonstration -
TreeMap<Integer, String> tmap = new TreeMap<Integer, String>();
/*Adding elements to TreeMap*/
tmap.put(1, "Data1");
tmap.put(23, "Data2");
tmap.put(70, "Data3");
tmap.put(4, "Data4");
tmap.put(2, "Data5");
/* Display content using Iterator*/
Set set = tmap.entrySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
Map.Entry mentry = (Map.Entry)iterator.next();
System.out.print("key is: "+ mentry.getKey() + " & Value is: ");
System.out.println(mentry.getValue());
}
The output ::
key is: 1 & Value is: Data1
key is: 2 & Value is: Data5
key is: 4 & Value is: Data4
key is: 23 & Value is: Data2
key is: 70 & Value is: Data3
You can use sort algo. like selectionsort, bubblesort...
This is insertionsort
double temp;
for(int i = 1;i<yourList.size();i++)
{
temp = thenodedistance(yourlist.get(i));
int j = i;
while(j>0&&thenodedistance(yourlist.get(j-1))> temp)
{
yourlist.add(j,yourlist.remove(j-1);
j--;
}
yourlist.add(j,yourliste.remove(i));
}
this way you can sort your list (did not try the code....)
What about having a Node class?
class Node{
String str;
int distance;
public Node(String str){
this.str = str;
this.distance = theNodesDistance(str);
}
}
Then you can override a Comparator. I suggest you to use a PriorityQueue instead of a LinkedList so your inserts to the ordered list can be more efficient (O(logn)). In this case you dont really need to call a sort or heapify function since the priority queue always keeps the nodes in order.
PriorityQueue que = new PriorityQueue(new Comparator<Node>(){
public int compare(Node n1, Node n2){
if(n1.distance < n2. distance) return -1;
else if(n1.distance > n2.distance) return 1;
return 0;
}
});
You can add to the que as follows:
for(each str){
que.add(new Node(str));
}
A binary search tree was created by traversing through an array from left to right and inserting each element. This tree may not be a balanced tree. Given a binary search tree with distinct elements, print all possible arrays that could have led to this tree.
To answer to this question I wrote the following code. Still, it seems that it doesn't print all possible arrays that could have lead to the the tree in all the cases. What do you think should be modified ?
public class Main {
public static LinkedList<Integer> passed = new LinkedList<>();
public static LinkedList<BinaryTree> notyet = new LinkedList<>();
public static ArrayList<LinkedList<Integer>> results = new ArrayList<LinkedList<Integer>>();
public static void main(String args[]) {
BinaryTree tr = readTree();
ArrayList<LinkedList<Integer>> result = allSequences(tr);
for (LinkedList<Integer> l : result){
for(int elem: l) System.out.print(elem+" ");
System.out.println("");
}
}
private static BinaryTree readTree() {
BinaryTree tr = new BinaryTree(2, null, null);
tr.left = new BinaryTree(1, null, null);
tr.right = new BinaryTree(3, null, null);
return tr;
}
public static ArrayList<LinkedList<Integer>> allSequences(BinaryTree tr){
// implement here
ArrayList<LinkedList<Integer>> result = new ArrayList<LinkedList<Integer>>();
findseqs(passed,notyet,tr);
//result=results.clone();
for(LinkedList<Integer> sample :results){
result.add(sample);
}
return result;
}
public static void findseqs(LinkedList<Integer> passed, LinkedList<BinaryTree> notyet, BinaryTree tr) {
passed.add(tr.value);
if (tr.left != null) notyet.add(tr.left);
if (tr.right != null) notyet.add(tr.right);
if (notyet.isEmpty()) {
results.add(passed);
}
for (BinaryTree elem: notyet) {
LinkedList<Integer> temp = (LinkedList<Integer>) passed.clone();
LinkedList<BinaryTree> ptemp = (LinkedList<BinaryTree>) notyet.clone();
ptemp.remove(elem);
findseqs(temp, ptemp, elem);
}
}
What holds about the array is that if A is ancestor of B in the graph then A precedes B in the array. Nothing else can be assumed.
So the arrays can be produced by the following recursive function.
function sourceArrays(Tree t)
// leafe node
if t == null
return empty list;
r = root(t);
append r to existing arrays;
la = sourceArrays(t.left);
ra = sourceArrays(t.right);
ac = createArrayCombitations(la, ra);
append ac to existing arrays;
end
function createArrayCombitations(la, ra)
foreach a in la
foreach b in ra
r = combineArrays(a,b);
add r to result;
end
end
end
function combineArrays(a, b)
generate all combinations of elements from two array such that order of elements in each array is preserved.
Ie if x precedes y in a or b the x precedes y in result
I am working on Dijkstra algorithm. The program that I have read the data from a text file. the program works fine if I input the relation in the program but it does not work when I try to read the input from text file.
the input to the program in the program itself is as following:
private static final Graph.Edge[] Arr = {
new Graph.Edge("a", "b", 1),
new Graph.Edge("a", "c", 1),
new Graph.Edge("a", "f", 1),
new Graph.Edge("b", "c", 1),
new Graph.Edge("b", "d", 1),
new Graph.Edge("c", "d", 1),
new Graph.Edge("c", "f", 1),
new Graph.Edge("d", "e", 1),
new Graph.Edge("e", "f", 1),
};
what I have tried to read from text file to be instead of this input is as following:
first, I count the number of the line to be the size of the array:
public static int count;
public static void countLines(String file) throws IOException
{
LineNumberReader lnr = new LineNumberReader(new FileReader(new File(file)));
lnr.skip(Long.MAX_VALUE);
Dijkstra.count=lnr.getLineNumber() + 1;
lnr.close();
}
The function that will do the reading from the text file and saving the data to the array is as following:
public static Graph.Edge[] readTextFile(String fileName) {
String line = null;
Graph.Edge[] Gr=new Graph.Edge[Dijkstra.count-1];
try {
FileReader fileReader = new FileReader("txt2.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader);
int i=0;
while ((line = bufferedReader.readLine()) != null) {
String[] tokens = line.split("\\t+");
String s = tokens[0];
String e = tokens[2];
Gr[i] =new Graph.Edge(s, e, 1);
i=i+1;
}
bufferedReader.close();
} catch (FileNotFoundException ex) {
System.out.println("Unable to open file '" + fileName + "'");
} catch (IOException ex) {
System.out.println("Error reading file '" + fileName + "'");
}
return Gr;
}
these functions and the main are within some class called Dijkstra. the whole code is as following:
package shortestPath;
import java.util.Scanner;
import java.io.*;
import java.util.*;
public class Dijkstra {
/*private static final Graph.Edge[] Arr = {
new Graph.Edge("a", "b", 1),
new Graph.Edge("a", "c", 1),
new Graph.Edge("a", "f", 1),
new Graph.Edge("b", "c", 1),
new Graph.Edge("b", "d", 1),
new Graph.Edge("c", "d", 1),
new Graph.Edge("c", "f", 1),
new Graph.Edge("d", "e", 1),
new Graph.Edge("e", "f", 1),
};*/
public static int count;
//public static Graph.Edge[] GRAPH = new Graph.Edge[count] ;
public static void countLines(String file) throws IOException
{
LineNumberReader lnr = new LineNumberReader(new FileReader(new File(file)));
lnr.skip(Long.MAX_VALUE);
Dijkstra.count=lnr.getLineNumber() + 1; //Add 1 because line index starts at 0
// Finally, the LineNumberReader object should be closed to prevent resource leak
lnr.close();
//return Dijkstra.count;
}
public static Graph.Edge[] readTextFile(String fileName) {
String line = null;
Graph.Edge[] Gr=new Graph.Edge[Dijkstra.count-1];
try {
FileReader fileReader = new FileReader("txt2.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader);
int i=0;
while ((line = bufferedReader.readLine()) != null) {
String[] tokens = line.split("\\t+");
String s = tokens[0];
String e = tokens[2];
Gr[i] =new Graph.Edge(s, e, 1);
i=i+1;
}
// Always close files.
bufferedReader.close();
} catch (FileNotFoundException ex) {
System.out.println("Unable to open file '" + fileName + "'");
} catch (IOException ex) {
System.out.println("Error reading file '" + fileName + "'");
}
//return Dijkstra.GRAPH;
return Gr;
}
private static final String START = "12";
private static final String END = "18";
public static void main(String[] args) throws IOException {
countLines("hsa00072.txt");
Graph.Edge[] GRAPH=readTextFile("hsa00072.txt");
Graph g = new Graph(GRAPH);
g.dijkstra(START);
g.printPath(END);
//g.printAllPaths();
}
}
class Graph {
private final Map<String, Vertex> graph; // mapping of vertex names to Vertex objects, built from a set of Edges
/** One edge of the graph (only used by Graph constructor) */
public static class Edge {
public final String v1, v2;
public final int dist;
public Edge(String v1, String v2, int dist) {
this.v1 = v1;
this.v2 = v2;
this.dist = dist;
}
}
/** One vertex of the graph, complete with mappings to neighbouring vertices */
public static class Vertex implements Comparable<Vertex> {
public final String name;
public int dist = Integer.MAX_VALUE; // MAX_VALUE assumed to be infinity
public Vertex previous = null;
public final Map<Vertex, Integer> neighbours = new HashMap<>();
public Vertex(String name) {
this.name = name;
}
private void printPath() {
if (this == this.previous) {
System.out.printf("%s", this.name);
} else if (this.previous == null) {
System.out.printf("%s(unreached)", this.name);
} else {
this.previous.printPath();
System.out.printf(" -> %s(%d)", this.name, this.dist);
}
}
public int compareTo(Vertex other) {
return Integer.compare(dist, other.dist);
}
}
/** Builds a graph from a set of edges */
public Graph(Edge[] edges) {
graph = new HashMap<>(edges.length);
//one pass to find all vertices
for (Edge e : edges) {
if (!graph.containsKey(e.v1)) graph.put(e.v1, new Vertex(e.v1));
if (!graph.containsKey(e.v2)) graph.put(e.v2, new Vertex(e.v2));
}
//another pass to set neighbouring vertices
for (Edge e : edges) {
graph.get(e.v1).neighbours.put(graph.get(e.v2), e.dist);
//graph.get(e.v2).neighbours.put(graph.get(e.v1), e.dist); // also do this for an undirected graph
}
}
/** Runs dijkstra using a specified source vertex */
public void dijkstra(String startName) {
if (!graph.containsKey(startName)) {
System.err.printf("Graph doesn't contain start vertex \"%s\"\n", startName);
return;
}
final Vertex source = graph.get(startName);
NavigableSet<Vertex> q = new TreeSet<>();
// set-up vertices
for (Vertex v : graph.values()) {
v.previous = v == source ? source : null;
v.dist = v == source ? 0 : Integer.MAX_VALUE;
q.add(v);
}
dijkstra(q);
}
/** Implementation of dijkstra's algorithm using a binary heap. */
private void dijkstra(final NavigableSet<Vertex> q) {
Vertex u, v;
while (!q.isEmpty()) {
u = q.pollFirst(); // vertex with shortest distance (first iteration will return source)
if (u.dist == Integer.MAX_VALUE) break; // we can ignore u (and any other remaining vertices) since they are unreachable
//look at distances to each neighbour
for (Map.Entry<Vertex, Integer> a : u.neighbours.entrySet()) {
v = a.getKey(); //the neighbour in this iteration
final int alternateDist = u.dist + a.getValue();
if (alternateDist < v.dist) { // shorter path to neighbour found
q.remove(v);
v.dist = alternateDist;
v.previous = u;
q.add(v);
}
}
}
}
/** Prints a path from the source to the specified vertex */
public void printPath(String endName) {
if (!graph.containsKey(endName)) {
System.err.printf("Graph doesn't contain end vertex \"%s\"\n", endName);
return;
}
graph.get(endName).printPath();
System.out.println();
}
/** Prints the path from the source to every vertex (output order is not guaranteed) */
public void printAllPaths() {
for (Vertex v : graph.values()) {
v.printPath();
System.out.println();
}
}
}
The text file is as following:
12 ECrel 15
15 ECrel 18
11 ECrel 12
12 ECrel 14
11 ECrel 14
11 ECrel 18
14 maplink 17
the problem is whenever I want to find the path like from node 12 to node 18 it will say 18(unreached) even if there is a path but it will not return the path. in case of the input in the program it works fine and return the path. the problem is only appears when trying to read from the text file.
There is something very strange going on here. I'll babble along as I work through it.
I get randomly different results (sometimes your program works as you wanted, sometimes not). Often they are different depending on whether I am running in debug mode or not.
So . . . it's time to look for a source of randomness. Clearly you're not using randomness directly/intentionally. So we should look somewhere else for the randomness.
I think I see two potential sources of randomness.
When you're traversing the NavigableSet, it's doing a lot of comparing equal values at first. That's necessary and makes sense, but it might lead to randomness, since it doesn't have to pick one particular order of how to present the elements to you.
You also use Maps in your algorithm, and there are no promises about what order their entrySets come back in.
My guess is that your algorithm is actually sensitive to the order in which things are presented to it. I haven't dug in enough to know which bits might be the problem, but maybe this section goes two different directions if two equally good routes are presented to it in different orders:
if (alternateDist < v.dist) { // shorter path to neighbour found
q.remove(v);
v.dist = alternateDist;
v.previous = u;
q.add(v);
}
and possibly one of those routes never leads to the destination you're looking for.
I would actually suggest getting out bits of paper and writing the vertices on them, and shuffling them whenever your algorithm calls for an iterator over map EntrySets.
My suspicion for why this works when reading from the source and fails when reading from code: different random seeds. That's it. It's technically prone to the same failure in both cases, it just happens to pick a working path in one JVM scenario and a non-working path in the other JVM scenario.
Sorry this is kind of fuzzy and really long. Maybe it will help.
For a tree structure as follows
public class Node implements Comparable<Node> {
private List<Node> nodes=new ArrayList<Node>();
private String name="";
private List<String> leaves=new ArrayList<String>();
private Node parent=null;
public List<Node> getNodes() {
return nodes;
}
public void setNodes(List<Node> nodes) {
this.nodes = nodes;
}
public List<String> getLeaves() {
return leaves;
}
public void setLeaves(List<String> leaves) {
this.leaves = leaves;
}
#Override
public int compareTo(Node o) {
return this.getName().compareTo(o.getName());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public int getDepth() {
int depth = 0;
Node parent = this.getParent();
while (parent != null) {
depth++;
parent = parent.getParent();
}
return depth;
}
}
From a node, I wish to have a method that returns all the distinct direct and indirect leaves (In the above case the strings leaves would be the leaves), for that node in sorted order.
Above is a highly torn down data structure to easy testing and demonstration. I have tried the following 3 approaches,
Approach A
Very slow when depth is large ~20, since the deepest leaves are traversed to several times, once for each of its ancestor, hence same path is traversed multiple times.
public List<String> getLeavesDeep1() {
Set<String> leaves = new TreeSet<String>();
leaves.addAll(getLeaves());
for (Node node : getNodes()) {
leaves.addAll(node.getLeavesDeep1());
}
return new ArrayList<String>(leaves);
}
Avg: 12694 ms / Without sort/distinct> Avg: 471 ms
Approach B
Little faster than A, as the number of nodes is comparatively very less than leaves, so using the approach A but for nodes, and then for each of the nodes, getting direct leaves only.
private List<Node> getNodesDeep2() {
Set<Node> nodes = new TreeSet<Node>();
nodes.addAll(getNodes());
for (Node node : getNodes()) {
nodes.addAll(node.getNodesDeep2());
}
return new ArrayList<Node>(nodes);
}
public List<String> getLeavesDeep2() {
Set<String> leaves = new TreeSet<String>();
leaves.addAll(getLeaves());
for (Node node : getNodesDeep2()) {
leaves.addAll(node.getLeaves());
}
return new ArrayList<String>(leaves);
}
Avg: 4355 ms / Without sort/distinct> Avg: 2406 ms
Approach C
Avoid TreeSet, used ArrayList's and sorted & filtered (not the best way to sort/distinct though) just before returning
private List<Node> getNodesDeep3() {
List<Node> nodes = new ArrayList<Node>();
nodes.addAll(getNodes());
for (Node node : getNodes()) {
nodes.addAll(node.getNodesDeep3());
}
return new ArrayList<Node>(new TreeSet<Node>(nodes));
}
public List<String> getLeavesDeep3() {
List<String> leaves = new ArrayList<String>();
leaves.addAll(getLeaves());
for (Node node : getNodesDeep3()) {
leaves.addAll(node.getLeaves());
}
return new ArrayList<String>(new TreeSet<String>(leaves));
}
Avg: 4400
Looking for something faster, I know there are certain tree traversals that can be used, but I would prefer something simpler if there exists. P.S. These is no use case for searching at the moment. In my real class the times are much higher approx 3x to the above cases, as the structure is much more complex with the leaves not being simple strings, but POJOs
Following is the test I have used to get the times
private static final int NODES = 5;
private static final int LEAVES = 25;
private static final int DEPTH = 8;
public void addChildren(Node parent) {
List<Node> nodes = new ArrayList<Node>();
List<String> leaves = new ArrayList<String>();
for (int i = 0; i < LEAVES; i++) {
leaves.add(String.format("%s_leaf_%s", parent.getName(), i));
}
for (int i = 0; i < NODES; i++) {
Node child = new Node();
child.setParent(parent);
child.setName(String.format("%s_%s", parent.getName(), i));
nodes.add(child);
if (child.getDepth() < DEPTH) {
addChildren(child);
}
}
parent.setNodes(nodes);
parent.setLeaves(leaves);
}
#Test
public void testCase() {
long start, tot=0;
long t = 0;
List<String> leaves;
Node target = new Node();
target.setName("Root");
addChildren(target);
for (int i = 0; i < 10; i++) {
start = System.currentTimeMillis();
leaves = target.getLeavesDeep5();
t = System.currentTimeMillis() - start;
tot += t;
System.out.println(leaves.size() + " " + t);
}
System.out.println("Avg: " + (tot / 10));
}
Answers in any language are acceptable including pseudo code, as long as it doesn't tightly tie the solution to that language (Exception: Pure java code is barred from the second clause)
I ran your test and it gave me the following results (i used your version 3, one slightly modified version3 and a new version)
2441400 8038
...
2441400 7890
Avg: 7872
2441400 4850
...
2441400 3990
Avg: 4165
2441400 980
...
2441400 710
Avg: 786
I first changed
return new ArrayList<String>(new TreeSet<String>(leaves));
to
Collections.sort(leaves);
return leaves;
See Is it faster to add to a collection then sort it, or add to a sorted collection?
Which gave an almost 50% reduction in execution time.
Note: The TreeSet will remove duplicates, sort will not.
I then wrote a new Iterator method combining your 2 methods into one and eliminating recursion all together. I also got rid of ArrayLists to avoid the resizing and copying which we don't need because we only iterate and never access by index.
Edit: using ArrayList to store the leaves increases time from 800ms to about 1400ms.
public List<String> getLeavesDeepX()
{
final Deque<Node> nodes = new LinkedList<Node>();
final Collection<String> leaves = new LinkedList<String>();
//final Collection<String> leaves = new LinkedHashSet<String>(); -- use for removing dupes
nodes.add(this);
do
{
final Node current = nodes.pop();
leaves.addAll(current.getLeaves());
nodes.addAll(current.getTreeNodes());
}
while(nodes.isEmpty() == false);
final ArrayList<String> result = new ArrayList<String>(leaves);
Collections.sort(result);
return result;
}
I put all results into different lists and compared those at the end.
System.out.println(Arrays.equals(leaves1.toArray(), leaves2.toArray()));
System.out.println(Arrays.equals(leaves1.toArray(), leaves3.toArray()));
System.out.println(Arrays.equals(leaves2.toArray(), leaves3.toArray()));
Output:
true
true
true
So at least on my system its about a 10 fold increase in speed.
Edit2: Skipping the sorting in case 3 brings it to 140ms. So 600ms are used comparing and sorting. Any further major improvement needs to be done there.
Edit3: Eliminating recursion also has the benefit that the depth of the tree has less impact on performance. Changing the TestTree to 2/2/20 (N/L/D) yields about the same number of leaves(2m) but performs much worse with recursion (>70k) but is not much slower (2500 from 1200) without.
I have to implement a search algorithm in java for a school project. In this algorithm i need to find, in an undirected graph, a path that goes through each link only once and ends in the start node. I'm trying to use a DFS with backtracking to solve this problem, but i'm having trouble implementating it. Here's my code:
import java.util.*;
public class Graph {
private Map<Integer, LinkedHashSet<Integer>> map =
new HashMap<Integer, LinkedHashSet<Integer>>();
private int startNode;
private int numLinks;
public Graph(int startNode, int numLinks) {
super();
this.startNode = startNode;
this.numLinks = numLinks;
}
public void addEdge(int source, int destiny) {
LinkedHashSet<Integer> adjacente = map.get(source);
if(adjacente==null) {
adjacente = new LinkedHashSet<Integer>();
map.put(source, adjacente);
}
adjacente.add(destiny);
}
public void addLink(int source, int destiny) {
addEdge(source, destiny);
addEdge(destiny, source);
}
public LinkedList<Integer> adjacentNodes(int last) {
LinkedHashSet<Integer> adjacente = map.get(last);
System.out.println("adjacentes:" + adjacente);
if(adjacente==null) {
return new LinkedList<Integer>();
}
return new LinkedList<Integer>(adjacente);
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int numVertices = input.nextInt();
int numLinks = input.nextInt();
int startNode = input.nextInt();
int endNode = startNode;
Graph mapa = new Graph(startNode, numLinks);
for(int i = 0; i<numLinks; i++){
mapa.addLink(input.nextInt(), input.nextInt());
}
List<ArrayList<Integer>> paths = new ArrayList<ArrayList<Integer>>();
Integer currentNode = startNode;
List<Integer> visited = new ArrayList<Integer>();
visited.add(startNode);
mapa.findAllPaths(mapa, visited, paths, currentNode);
for(ArrayList<Integer> path : paths){
for (Integer node : path) {
System.out.print(node);
System.out.print(" ");
}
System.out.println();
}
}
private void findAllPaths(Graph mapa, List<Integer> visited,
List<ArrayList<Integer>> paths, Integer currentNode) {
if (currentNode.equals(startNode)) {
paths.add(new ArrayList<Integer>(visited));
return;
}
else {
LinkedList<Integer> nodes = mapa.adjacentNodes(currentNode);
for (Integer node : nodes) {
if (visited.contains(node)) {
continue;
}
List<Integer> temp = new ArrayList<Integer>();
temp.addAll(visited);
temp.add(node);
findAllPaths(mapa, temp, paths, node);
}
}
}
}
The program is supposed to receive integers on his input, where the first one is the number of nodes, the second one is the number of links, the third is the start node(wich is also the end node), all the integers that come after represent the links between nodes.
The goal is to, in the end, print a single line with integers. This integers represent the order i visit each node to complete the path. Currently testing, it only prints a single integer, wich represents the first node.
I think my problem is either populating the graph, populating the adjacent list. Can somebody help me?
The problem is that when you call mapa.findAllPaths(mapa, visited, paths, currentNode);, you don't actually find all paths. You only find one path (i.e. the current node) and you return:
private void findAllPaths(Graph mapa, List<Integer> visited,
List<ArrayList<Integer>> paths, Integer currentNode) {
if (currentNode.equals(startNode)) {
paths.add(new ArrayList<Integer>(visited));
return;// <--- WRONG!!!
} else {
// The else is never executed!
}
}
You should either have a loop or recursively call the findAllPaths until you find all the paths.
The first time you invoke the findAllPaths method you are passing the startNode as the last argument (the currentNode), which leads to currentNode.equals(startNode) being true and as such the only part of the method that gets executed is:
paths.add(new ArrayList<Integer>(visited));
return;
In essence, you only add the first node to your paths and then your algorithm finishes, thus always printing a single integer, the start node.