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|)
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 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);
}
}
There are boxes and objects. A object stays in a box. Both box and object have a unique index and each object has a weight.
I need to create a method which obtains numerous orders (> 1 million) in which you can see how much weight should be moved with departure and destination box indices, and then returns moving object set and their destination.
It is very clear and easy to implement without performance thought. (Below, the type of box index is Long and object is Integer just for clarification)
public static void main(String[] args) {
Map<Long, Set<Integer>> objectsInBox = new HashMap<>();
objectsInBox.put(1l, new HashSet<>(Arrays.asList(1,2,3)));
objectsInBox.put(2l, new HashSet<>(Arrays.asList(4,5,6)));
// .... a lot of objects
Map<Integer, Double> weightsOfObject = new HashMap<>();
weightsOfObject.put(1, 99.9);
weightsOfObject.put(2, 23.4);
// ....
List<Map<Pair<Long, Long>, Double>> moveOrderList = receiveOrderList();
getFinalDestinationOfMovingObject(moveOrderList);
}
public static Map<Long, Set<Integer>> getFinalDestinationOfMovingObject(
List<Map<Pair<Long, Long>, Double>> moveOrderList){
Map<Long, Set<Integer>> finalDestinationOfObjects = new HashMap<>();
for(Map<Pair<Long, Long>, Double> moveOrder : moveOrderList){
// Convert moving amount into object move is not trivial, but given somewhere.
Map<Integer, Pair<Long,Long>> movingObjects = calculateMovingObjectSet(moveOrder);
for(Map.Entry<Integer, Pair<Long,Long>> movingObject : movingObjects.entrySet()) {
int movingObjectIndex = movingObject.getKey();
long departureIndex = movingObject.getValue().getFirst();
long destinationIndex = movingObject.getValue().getSecond();
if(!finalDestinationOfObjects.containsKey(destinationIndex)){
finalDestinationOfObjects.put(departureIndex, new HashSet<Integer>(Arrays.asList(movingObjectIndex)));
}else{
finalDestinationOfObjects.get(departureIndex).add(movingObjectIndex);
}
if(!finalDestinationOfObjects.containsKey(departureIndex)){
// We need just final destination. Remove past object state.
finalDestinationOfObjects.get(departureIndex).remove(movingObjectIndex);
}
}
}
return finalDestinationOfObjects;
}
When move order contains numerous elements, it takes a lot of time. I guess it's because insert or remove element from HasSet is not efficient. What is more efficient way?
Can't you simply record the final destination based on the object, i.e.,
finalDestination.put(movingObjectIndex, destinationIndex);
instead of all the complicated logic? This handles the case of previous destination existing as well as non-existing.
If you really need finalDestinationOfObjects, you can create it at the end by something like
Multimap<Long, Integer> finalDestinationOfObjects = HashMultimap.create();
for (val e : finalDestination.entrySet()) {
finalDestinationOfObjects.put(e.getValue(), e.getKey());
}
where Multimap comes from Guava (you don't need it, but - unlike your nested Map - it's the right thing).
This will be more efficient in case your objects moves between the boxes a lot and possibly less efficient in case they usually get moved just once.
I'd suggest to give it a try and post the code together with calculateMovingObjectSet on CR, which is better suited for such questions.
so I have a list of basic nodes, for example nodes A B C.
each component can see what it is attached to for example:
a->b
b->c
c->a
I want a way that I can get a list of all the nodes in the graph. However, I'm running into trouble as my current system can't detect if it has already reached a point. EG in the above example it will go a->b->c->a->b etc. How can I detect this or how can I solve this problem.
My current "solution" getList() in the Node class:
ArrayList<Node> tempList = new ArrayList<Node>();
tempList.add(this);
for(int i = 0 ; i < nodesAttachedTo.size();i++){
tempList.addAll(nodesAttachedTo.get(i).getList());
}
return tempList;
You can use a HashSet. It will not allow one element to be added twice.
Here's an example code that first creates the graph similar to your example, then starts at some point in the graph and goes through it.
import java.util.HashSet;
public class Node
{
private HashSet<Node> nextNodes = new HashSet<Node>();
public Node()
{
}
public void addNextNode(Node node)
{
nextNodes.add(node);
}
public static void main(String[] args)
{
// this builds the graph of connected nodes
Node a = new Node();
Node b = new Node();
Node c = new Node();
a.addNextNode(b);
b.addNextNode(c);
c.addNextNode(a);
//this is the set that will lsit all nodes:
HashSet<Node> allNodes = new HashSet<Node>();
// this goes through the graph
a.listAllNodes(allNodes);
System.out.println(allNodes);
}
private void listAllNodes (HashSet<Node> listOfNodes)
{
// try to put all next nodes of the node into the list:
for(Node n : nextNodes)
{
if (listOfNodes.add(n)) // the set returns true if it did in fact add it.
n.listAllNodes(listOfNodes); // recursion
}
}
}
This goes from one node to all the nodes this node knows. (say that really fast three times)
Until it hits a dead end (= a node it already visited)
I chose to use a HashSet in the Node itself to store all the nodes it knows.
This could also be an ArrayList or whatever. But as I don't think there should be a connection twice, a HashSet seems to be a good choice in this situation, too.
I'm not familiar with your notation, but you could use two pointers to solve your issue. Start with two pointers that point to the same node. Increment one pointer until it returns to the start. Some pseudocode is below.
ArrayList<Node> tempList = new ArrayList<Node>();
Node head = nodesAttachedTo.get(0); //get the head of the list
tempList.add(head);
Node runner = head;
runner = runner.next;
while (!runner.equals(head)) {
tempList.add(runner);
runner = runner.next;
}
A hashmap is probably the way to go here. It allows constant time access (some overhead required, but I'm assuming you want a solution that scales well) to any element in the map.
HashMap<String, String> specificSolution = new HashMap<String, String>();
specificSolution.put("a", "b");
specificSolution.put("b", "c");
specificSolution.put("c", "a");
// To get all nodes in the graph
Set<String> nodes = specificSolution.keySet();
I implemented with String here because you don't provide a definition for the Node Class in your question, but it can be easily swapped out.
There are many different ways to represent a graph and each has their own limitations/advantages. Maybe another might be more appropriate but we would need more information about the problem.
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);
}
}
}
}