I'm trying to implement a graph algorithm using a HashMap with integer keys, and an ArrayList for the values.
The key is the vertex, and the ArrayList is all of the vertices that are connected to the key vertex.
I'm using a blacklist to keep track of where I've been. If the item is in the blacklist, I HAVE NOT visited that vertex yet. The problem with this code is that I have to be able to call a search multiple times while the program is running. What I'm doing is pointing blacklist to the graph with the vertices. Then, as I visit a vertex, I delete the value in the blackList. The problem being, the blackList is pointing to the value in the original graph. So when I run the search again, the original graph is missing all the vertexes I visited my previous search.
The TL:DR question is this: how do I created a new identical HashMap without pointing.
I understand that I could loop through the HashMap and copy over each entry, but if I'm doing a lot of searches(large searches at that!), it gets slow. I'm not above doing it that way if that is the only way to do it.
//The class variables used for this search
HashMap<Integer, ArrayList<Integer>> mapBlacklist;
Queue<Integer> line = new PriorityQueue<Integer>();
int searchFor;
boolean areTheyConnected;
//The constructor I'm using
GraphSearch(HashMap<Integer, ArrayList<Integer>> graph, int match){
mapBlacklist = new HashMap<Integer, ArrayList<Integer>>(graph);
searchFor = match;
}
//The search method.
void numberOne(int start, HashMap<Integer, ArrayList<Integer>> graph){
if(graph.get(start).contains(this.searchFor)){
this.areTheyConnected = true;
}
else{
while(!this.mapBlacklist.get(start).isEmpty()){
this.line.add(this.mapBlacklist.get(start).get(0) ;
this.mapBlacklist.get(start).remove(0);
}
}
if(!this.line.isEmpty() && !this.areTheyConnected){
numberOne(this.line.remove(), graph);
}
}
In the main method:
/* What it looks like in the command line to see if vertices 2 5 are connected:
1 2 5
To close the program:
0
*/
boolean keepGoing = true;
while(keepGoing){
Scanner sc = new Scanner(System.in);
int number0 = Integer.parseInt(sc.next());
if(number0 == 0){
keepGoing = false;
sc.close();
}
else if(number0 == 1){
int number1 = Integer.parseInt(sc.next());
int number2 = Integer.parseInt(sc.next());
// GraphSearch gsearch = new GraphSearch(graph, number2);
GraphSearch gsearch = new GraphSearch(mapGraph, number2);
gsearch.numberOne(number1, mapGraph);
System.out.println(gsearch.areTheyConnected);
}
Why do you need this mapBlacklist in the first place?
I can see from your algorithm that you can just use your queue to iterate (recursively) over all non-visited items.
In the loop:
while(!this.mapBlacklist.get(start).isEmpty()){
this.line.add(this.mapBlacklist.get(start).get(0) ;
this.mapBlacklist.get(start).remove(0);
}
just don't use black list and don't remove anything from graph. You can only iterate over the list in current vertex and add all the items in it to the queue.
Does it make sence?
I find the way you're using mapBlackList confusing, and I think that confusion is contributing to your problem.
You don't need to know the structure of the map to prevent revisits, just what you've visited during this search. So instead of making a shallow copy of the whole graph in the constructor, why not simply keep a Set<Integer> of the vertices you've visited so far? Your search method than becomes something like:
void numberOne(int start, HashMap<Integer, ArrayList<Integer>> graph){
visited.add(start);
if(graph.get(start).contains(this.searchFor)){
this.areTheyConnected = true;
return;
}
else{
for (Integer destination : graph.get(start)) {
if (!areTheyConnected && !visited.contains(destination)) {
numberOne(destination, graph);
}
}
}
}
Related
I am trying to figure out how to make a method which finds the shortest way between two nodes, but cant seem to figure it out. I was given two files, one file containing actors (which will be the nodes) and one with movies (which will be the edges).
I have represented a graph as a HashMap where the key is a actor, and the value is a ArrayList with the movies that actor has played in:
HashMap<Actor, ArrayList[Movie]> graph;
Now i wanna find the shortest way between two actors but i dont know how to. I was thinking DFS or BFS but im not quite sure how to do that with this hashmap..
You can use your current structure (HashMap<Actor, ArrayList<Movie>>) as an adjacency list, but there is something missing: an efficient way to go from movies back to actors that starred in them. You can build a Map<Movie, List<Actor>>, and that would work... but it is simpler to use a single adjacency list, instead of having one for actors and another for movies. I would do as follows (assuming that Actor and Movie both have equals and hashcode implemented, and no actor equals any movie):
Map<Object, List<Object>> edges = new HashMap<>();
for (Map.Entry<Actor, ArrayList<Movie>> e : graph.entries()) {
Actor a = e.getKey();
edges.put(a, new ArrayList<Object>(e.getValues()));
for (Movie m : e.getValues()) {
List<Actor> otherActors = edges.getOrDefault(m, new ArrayList<Object>());
otherActors.add(a);
edges.put(m, otherActors);
}
}
Now you can use edges to perform a standard breadth-first search. It is guaranteed to give you a shortest path (there may be multiple equally-short paths) between any 2 actors, and the path will contain the movies that link them.
It's not the entire solution just to point you in the direction, you will have to create a Map<Movie, List> as tucuxi suggested. You traverse every actor one by one and mark the distance on the current actor from your reference actor.
private static void fn(HashMap<Actor, List<Movie>> graph, Actor referenceActor) {
var movieMap = new HashMap<Movie, List<Actor>>();
graph.forEach((key, value) -> {
for (var movie : value) {
movieMap.compute(movie, (k, v) -> {
if (v == null) {
return new ArrayList<>(List.of(key));
}
v.add(key);
return v;
});
}
});
final var visited = new HashSet<Actor>();
var dq = new ArrayDeque<Actor>();
var map = new HashMap<Actor, Integer>();
dq.add(referenceActor);
int distance = 0;
while (!dq.isEmpty()) {
var size = dq.size();
for (int i = 0; i < size; i++) {
var actor = dq.poll();
visited.add(actor);
map.putIfAbsent(actor, distance);
for (Movie movie : graph.get(actor)) {
// adding every non visited actor in the same movie in the queue
dq.addAll(movieMap
.get(movie)
.stream()
.filter(x -> !visited.contains(x))
.collect(Collectors.toList()));
}
}
distance++;
}
System.out.println(map);
}
I used ArrayDeque here to traverse in BFS fashion you can mend if you want DFS bear in mind the logic to calculate distance will also need to be changed. Pasting the image from the comments, taking node B as reference actor.
I have a list of lines and points and I need to get the list of connected lines based on points.
The input is Line and its 2 points so in the above example input is
In Tabular format
And the output should be 2 lists
[1,2,3,4,5,6,7] and [8]
I was creating a map of point to list of lines it belongs
A - 1
B - 1,2,3
C - 2
...
and then trying to merge the list of lines where common points are found. But not able to find the correct way to merge those lines.
Can there be simple or another solution to it?
Are 3 and 5 different lines?
If yes, then it is obvious that each line can have no more than 2 points.
If no, then a tabular format is not appropriate.
If I were you, I would do one Map with a line as the key and a List of points as the value.
Then create an interface to define if a point belongs to several lines at once.
One solution is to create an adjacent map of points to point-lines.
You define what are the adjacent points of a given point and what are the lines:
class PointAdj {
String point;
String line;//Or int
}
Define the map as:
Map<String, List<PointAdj>> pontAdjMap = new HashMap<>();
Start reading from the table and fill the map:
(I assume the input is a two dimensional array called input)
for (int i =0; i < input.length; i++) {
String[] row = input[i];
List<PointAdj>> adj = pontAdjMap.putIfAbsent(row[1], new ArrayList<>());
if(adj == null) {
adj = pontAdjMap.get(row[1]);
}
adj.add(new PointAdj(row[2], row[0]));
//Also put the reverse side
}
Now pontAdjMap contains all points with their adjacent points.
Now define a list of Set to add lines and loop the map and add lines as much as possible.
Use a queue to continuously traverse adjacent points;
List<Set<String>> lines = new ArrayList<>();
final Set<String> calculated = new HashSet();//Takes care of redundant processing of points;
pontAdjMap.keySet().foreach(pointInMap->{
Set<String> lineSet = new HashSet();
Queue<String> queue = new LinkedList<String>();
queue.offer(pointInMap);
while(!queue.isEmpty()) {
String point = queue.poll();
if(calculated.add(point)) {
for(PointAdj pa: pontAdjMap.get(point)) {
lineSet.add(pa.line);
queue.offer(pa.point);
}
}
}
if(lineSet.size() > 0) {
lines.add(lineSet);
}
});
Now lines should be the final result.
Please note that I did't test this solution and it may contain some edge case issues. But I think the overall idea is ok.
I wrote this static method in Python to do breadth first search. However, I mainly use Java, and I want to get a sense of how the data structures convert to Java, given generics, etc. My code is:
def bfs(graph, start_vertex, target_value):
path = [start_vertex] #a list that contains start_vertex
vertex_and_path = [start_vertex, path] #a list that contains start_vertex and path
bfs_queue = [vertex_and_path]
visited = set() #visited defined as an empty set
while bfs_queue: #while the queue is not empty
current_vertex, path = bfs_queue.pop(0) #removes element from queue and sets both equal to that first element
visited.add(current_vertex) #adds current vertex to visited list
for neighbor in graph[current_vertex]: #looks at all neighboring vertices
if neighbor not in visited: #if neighbor is not in visited list
if neighbor is target_value: #if it's the target value
return path + [neighbor] #returns path with neighbor appended
else:
bfs_queue.append([neighbor, path + [neighbor]]) #recursive call with path that has neighbor appended
a graph I'd use this on would be:
myGraph = { //I'm not sure how to structure this in Java
'lava': set(['sharks', 'piranhas']),
'sharks': set(['lava', 'bees', 'lasers']),
'piranhas': set(['lava', 'crocodiles']),
'bees': set(['sharks']),
'lasers': set(['sharks', 'crocodiles']),
'crocodiles': set(['piranhas', 'lasers'])
}
and I would call it like
public static void main(String[] args){
System.out.println(bfs(myGraph, "crocodiles", "bees"));
}
So far, here's the Java I have:
public class BreadthFirstSearch{
///NOT DONE YET
public static ArrayList<String> BFS(Map<String, String[]> graph, String start, String target) {
List<String> path = new ArrayList<>();
path.add(start);
List<String> vertexAndPath = new ArrayList<>();
vertexAndPath.add(start);
vertexAndPath.add(path.get(0));
ArrayList<String> queue = new ArrayList<>();
queue.add(vertexAndPath.get(0));
queue.add(vertexAndPath.get(1));
Set<String> visited = new HashSet<String>();
while(!queue.isEmpty()) {
String currentVertex = queue.remove(0);
String curVerValue = currentVertex;
path.add(currentVertex);
.
.
.
}
}
}
Good effort on the translation. Let me offer my code, then an explanation:
import java.util.*;
class BreadthFirstSearch {
public static ArrayList<String> BFS(
Map<String, String[]> graph, String start, String target
) {
Map<String, String> visited = new HashMap<>();
visited.put(start, null);
ArrayDeque<String> deque = new ArrayDeque<>();
deque.offer(start);
while (!deque.isEmpty()) {
String curr = deque.poll();
if (curr.equals(target)) {
ArrayList<String> path = new ArrayList<>();
path.add(curr);
while (visited.get(curr) != null) {
curr = visited.get(curr);
path.add(curr);
}
Collections.reverse(path);
return path;
}
for (String neighbor : graph.get(curr)) {
if (!visited.containsKey(neighbor)) {
visited.put(neighbor, curr);
deque.offer(neighbor);
}
}
}
return null;
}
public static void main(String[] args) {
Map<String, String[]> myGraph = new HashMap<>();
myGraph.put(
"lava", new String[] {"sharks", "piranhas"}
);
myGraph.put(
"sharks", new String[] {"lava", "bees", "lasers"}
);
myGraph.put(
"piranhas", new String[] {"lava", "crocodiles"}
);
myGraph.put(
"bees", new String[] {"sharks"}
);
myGraph.put(
"lasers", new String[] {"sharks", "crocodiles"}
);
myGraph.put(
"crocodiles", new String[] {"piranhas", "lasers"}
);
System.out.println(BFS(myGraph, "crocodiles", "bees"));
System.out.println(BFS(myGraph, "crocodiles", "crocodiles"));
System.out.println(BFS(myGraph, "crocodiles", "zebras"));
}
}
Output
[crocodiles, lasers, sharks, bees]
[crocodiles]
null
Explanation
I made the design decision to avoid copying a path ArrayList on every node in the graph in favor of a visited hash that stores nodes in childNode => parentNode pairs. This way, once I've located the destination node, I retrace my steps to create the path in one shot, instead of building a path for every node, most of which ultimately lead nowhere. This is more efficient in space and time; Python makes it too easy to ruin your time complexity with the [] + [] O(n) list concatenation operator.
Using a child => parent visited HashMap is also simpler to code in Java, which doesn't have a light weight Pair/Tuple/struct that can conveniently store different types as nodes in the queue. To do what you're doing in Python in passing a 2-element list into the queue, you'd either have to write your own Pair class, use two ArrayDeques, or avoid generics and use casting, all of which are ugly (especially the last, which is also unsafe).
Another issue I noticed in your code is usage of an ArrayList as a queue. Insertion and removal on the front of a list is a O(n) operation as all elements in the list must be shifted forward or backward in the underlying array to maintain sequencing. The optimal queue structure in Java is an ArrayDeque, which offers O(1) add and removal at both ends and is not thread safe, unlike the Queue collection.
Similarly, in Python, you'll find performance is best using the deque collection which offers a fast popleft operation for all your queuing needs. Additionally, in your Python implementation, each key in your hash points to a set, which is okay, but seems like an unnecessary structure when a list would do (you've switched to a primitive array in Java). If you're not manipulating the graph and only iterating over neighbors, this seems ideal.
Note that this code also presumes that every node has a key in the hash that represents the graph, as your input does. If you plan to input graphs where nodes may not have keys in the hash, you'll want to make sure that graph.get(curr) is wrapped with a containsKey check to avoid crashing.
Another assumption worth mentioning: ensure your graph doesn't contain nulls since the visited hash relies on null to indicate that a child has no parent and is the start of the search.
You would need to create a separate class to hold nodes of a graph. Those nodes could not be static, as they all have unique vertexes. From there the rest is very similar.
public class Node {
public String name;
public ArrayList<Node> vertices;
public void addEdge(Node node) {
edges.add(node);
}
}
I'm trying to calculate the degree of each node in a graph. However I'm having trouble because the nodes are part of the node class and I don't know how to convert them to String. At least I think that's what's wrong.
Here's what I've been trying, I have a Hashset where I store the nodes and another one for the edges (undirected graph), and I need to get a table with all the degrees that exists followed by the nodes that have those degrees:
public void DegreeList () {
List<Nodes> listnodes = new ArrayList<Nodes>(Node);
Map <Integer, List<Nodes>> DegreeList = new HashMap<Integer, List<Nodes>>();
for (Nodes n: Node){
int degree=0;
for (Edges e: Edge){
if (n.equals(e.start)||n.equals(e.end)){
degree++;
DegreeList.put(degree,n);
}
}
}
}
The error from Eclipse is for the last line and says:
The method put(Integer, List) in the type Map> is not applicable for the arguments (int, Nodes).
I'm open to try other approach.
Edit: Nodes and Edges are classes. Edge and Node are the Hashsets storing the values. (Sorry for any confusion)
Working Assumptions
It looks from your code as if the type Nodes represents a single node, and Node represents a Collection of nodes. (And that assumption was confirmed by your edit.) Those names seem backwards, but I'm going by what the code is doing with them. Please correct me if I'm wrong.
The Immediate Question
There are several problems here, but the immediate one is pretty simple: your Map expects a value of type List<Nodes>, but you're giving it a single instance of Nodes. If you can change your Map to a Guava Multimap then please do so. Otherwise, instead of
DegreeList.put(degree, n);
you'll need something like
List<Nodes> innerList = DegreeList.get(degree);
if (innerList == null) {
innerList = new ArrayList<Nodes>();
DegreeList.put(degree, innerList);
}
innerList.add(n);
That way there's a List associated with each degree. You need this because a Map can only store one value with each key. If your Map was defined like Map<Integer, Nodes> then you could only store one node with each distinct degree number. But that doesn't make any sense, does it? Any number of nodes could share the same degree number. So you need a Map that associates an Integer (representing degree) with a Collection of nodes. You seem to be using List as your chosen Collection. Set would probably be better.
Using Set, you'd define your Map as
Map<Integer, Set<Nodes>> degreeMap = new HashMap<>();
Then, when it came time to put something into the Map you'd do it like this:
Set<Nodes> innerSet = degreeMap.get(degree);
if (innerSet == null) {
innerSet = new HashSet<>();
degreeMap.put(degree, innerSet);
}
innerSet.add(n);
In either case you no longer need your listNodes List.
Other Observations
The code above describes how to put something into the Map. But we also need to think about when to put something into the Map. Right now you have code inserting into the Map each time there's an edge that matches the node you're evaluating:
for (Edges e: Edge){
if (n.equals(e.start)||n.equals(e.end)){
degree++;
DegreeList.put(degree,n); // this shouldn't be here
}
}
// instead, it belongs here
Instead, you should insert into the Map only once per node, after determining the node's degree:
for (Edges e: Edge){
if (n.equals(e.start)||n.equals(e.end)){
degree++;
}
}
// insert into Map here
Set<Nodes> innerSet = degreeMap.get(degree);
if (innerSet == null) {
innerSet = new HashSet<>();
degreeMap.put(degree, innerSet);
}
innerSet.add(n);
While Erick's answer is correct, it is not very efficient. If you want to calculate the degree of all nodes in a graph, and you want to store that in a map keyed by degrees, a faster algorithm would be the following:
public static Map<Integer, ArrayList<Node>> nodesByDegree(
Collection<Edge> edges, Collection<Node> nodes) {
HashMap<Node, Integer> degrees = new HashMap<>();
// initialize all nodes with 0 degrees
for (Node n : nodes) {
degrees.put(n, 0);
}
// calculate all degrees at the same time, in a single pass through E
for (Edge e : edges) {
degrees.put(e.start, degrees.get(n.start)+1);
degrees.put(e.end, degrees.get(n.end)+1);
}
// transform into multimap
HashMap<Integer, ArrayList<Node>> result = new HashMap<>();
for (Map.Entry<Node, Integer> e : degrees) {
if ( ! result.containsKey(e.getValue()) {
result.put(e.getValue(), new ArrayList<Node>());
}
result.get(e.getValue()).add(e.getKey());
}
return result;
}
Note that, in the above code, edges is a collection of Edge, and nodes is a collection of Node. This code requires O(|V|) + O(|E|) run-time, which should be much lower than O(|V|) * O(|E|)
I am working on a traveling salesman problem here and my p-queue isn't operating it is simply taking the last item added. I was wonder if anyone could help me figure out the error. here is my Node class (nodes which are added to the queue):
import java.util.*;
public class Node implements Comparable< Node >{
//level of node
int level;
//stores path of node
ArrayList< Integer > path = new ArrayList< Integer >();
//bound of node
int bound;
/** Over-rides compareTo for priority queue handling
* #return int desired sorting value
*/
public int compareTo(Node aNode)
{
if (this.bound<aNode.bound)
{
return 1;
}
if (this.bound>aNode.bound)
{
return -1;
}
else
{
return 0;
}
}
}
and here is the p-queue implementation:
PriorityQueue< Node > theQ = new PriorityQueue< Node >();
The algorithm is implemented correctly the p-queue simply is not putting the lowest bound as the head. I even reversed the the returns on the compareTo with no effect on the p-queue output (signifying to me that the queue is not sorting. I have wasted hours trying to figure it out and also asking some classmates (no-one can discern the problem) taking a shot here to see if anyone knows why the queue is acting like this..
Your code works perfectly fine for me.
What I suspect you're doing is changing the the bound value of a single object and repeatedly adding it, giving you a queue full of the same object (lots of references to it) which of course has the single (last) value you set it to.
public static void main(String[] args)
{
PriorityQueue< Node > theQ = new PriorityQueue< Node >();
Node n = new Node();
n.bound = 6;
theQ.add(n);
n = new Node();
n.bound = 9;
theQ.add(n);
n = new Node();
n.bound = 4;
theQ.add(n);
while ((n = theQ.poll()) != null)
System.out.println("Bound = " + n.bound);
}
Output:
Bound = 9
Bound = 6
Bound = 4
Make sure you are iterating through the PriorityQueue by using the methods provided by the Queue interface, ex. remove to pop an element off the top. In pseudo code:
for each element in some other collection
priorityQueue.add(element)
while priorityQueue is not empty
Set node to priorityQueue.remove()
Do stuff with node
If you are trying to iterate through a for-each loop or PriorityQueue.iterator:
The Iterator provided in method
iterator() is not guaranteed to
traverse the elements of the priority
queue in any particular order.
Alternatively, if you don't want to destroy/remove elements from your PriorityQueue to iterate in order, you could use, as the documentation suggests,
Arrays.sort(pq.toArray())