-
Notifications
You must be signed in to change notification settings - Fork 16
bfs #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bfs #17
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package src.algorithms.graphs; | ||
|
||
import java.util.*; | ||
|
||
import src.algorithms.graphs.util.BinaryTreeNode; | ||
import src.algorithms.graphs.util.GraphNode; | ||
|
||
/** | ||
* Implementation of BFS | ||
* | ||
* Breadth-First search is a graph traversal algorithm that utilizes a queue (FIFO) data structure | ||
* It is useful in finding a shortest-hops solution in graphs | ||
* This method is also used to obtain level order traversals of trees. | ||
* | ||
* In general, BFS works as such: | ||
* - Start with a queue that contains the root node | ||
* - While the queue is not empty: | ||
* - Pop a vertex from the queue | ||
* - Push all neighbours to the queue if they have not been visited yet | ||
* - Update any variables as needed | ||
* | ||
* Time: O(V + E), where V is the number of vertices/nodes, and E is the number of edges in the graph | ||
* Explanation: Each vertex is popped in O(1) time from the stack exactly once, hence O(V) | ||
* For each edge, we must check in O(1) time if we have visited the adjacent vertex to know | ||
* whether to push it into the queue, hence O(E). | ||
* Note that if the graph is a forest, BFS will likely terminate earlier, as fewer vertices are traversed | ||
* | ||
* Space: O(V): We utilize a Hashset to store the vertices we have visited already. In the worst case we have a | ||
* connected graph where all vertices are traversed, and our Hashset stores all O(V) vertices. | ||
* Further, we use a queue to hold the vertices to be traversed. In the worst case, the root will have all | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, but this isn't the only worst case. You can just mention the queue can possibly hold up to v-1 nodes, for instance when the root is connected to all other v-1 nodes. |
||
* other vertices as neighbours, and our queue will contain O(V) vertices. | ||
* | ||
* ** Note: The above description assumes an adjacency list in order to consider neighbours in an efficient manner | ||
* If an adjacency matrix were used, it would cost O(V) to find neighbours for a single vertex, making our | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not just average! Best as well. If we have a connected graph of v nodes, we will surely 'explore' all of them. Since we do not have an efficient way of querying and getting their neighbors instantly, we have no choice but to do O(v) search of all other nodes to check if it is a neighbor of this node. |
||
* average case time complexity O(V^2) for a connected graph | ||
* | ||
* The implementation demonstrates the use of BFS in finding the level-order (Root, Left, Right) traversal of a binary tree | ||
* The tree is represented using a custom BinaryTreeNode class | ||
* | ||
*/ | ||
public class breadthFirstSearch { | ||
|
||
// Prints level order traversal from left to right | ||
public static List<Integer> levelOrder(BinaryTreeNode root) { | ||
if (root == null) { return new ArrayList<>(); } | ||
List<Integer> traversal = new ArrayList<>(); | ||
Queue<BinaryTreeNode> queue = new LinkedList<>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can also mention in a general BFS algorithm, if the order at which the nodes to be visited at a certain level does not matter, a stack/array/arraylist/list actually suffice. The queue here is to specifically maintain the left to right order required by binary tree level-order traversal |
||
queue.add(root); | ||
|
||
while (!queue.isEmpty()) { | ||
BinaryTreeNode curr = queue.remove(); | ||
traversal.add((Integer) curr.getVal()); | ||
if (curr.getLeft() != null) { queue.add(curr.getLeft()); } | ||
if (curr.getRight() != null) { queue.add(curr.getRight()); } | ||
} | ||
|
||
return traversal; | ||
} | ||
|
||
// Finds the number of friend hops needed to go from person A to person B | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. great explanation. But for standardisation, we use // as in-line comments to birefly explain logic or parts of the code that aren't clear. If you're explaining high-level overview eg what the method does, can use the /** */ or see how cx and kt did it! |
||
// Uses GraphNode<String>, where a node holds a String of the persons name, and an edge represents a friendship | ||
public static int friendHops(GraphNode<String> personA, GraphNode<String> personB) { | ||
// Hashset to store the people we have seen already | ||
HashSet<GraphNode> checked = new HashSet<>(); | ||
// Hashmap to remember how many hops were needed to get to a specific friend * | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. great comment, can also link to hashmap implementation by chang xian once up |
||
HashMap<GraphNode, Integer> map = new HashMap<>(); | ||
|
||
Queue<GraphNode> queue = new LinkedList<>(); | ||
queue.add(personA); | ||
// the number of hops to the person themselves is 0, so we map: personA -> 0 | ||
map.put(personA, 0); | ||
int hops = 0; | ||
|
||
while (!queue.isEmpty()) { | ||
// poll the queue to get the next person to consider | ||
GraphNode<String> currPerson = queue.remove(); | ||
// add the person to the checked hashset so we don't consider them again | ||
checked.add(currPerson); | ||
// grab the number of hops from the hashmap and add 1 | ||
hops = map.get(currPerson) + 1; | ||
|
||
List<GraphNode> neighbours = currPerson.neighbours(); | ||
for (GraphNode neighbour : neighbours) { | ||
if (neighbour == personB) { return hops; } | ||
if (!checked.contains(neighbour)) { | ||
queue.add(neighbour); | ||
map.put(neighbour, hops); | ||
} | ||
} | ||
} | ||
// Returns -1 if person not found | ||
return -1; | ||
|
||
// * Note that we can actually use just the hashmap instead of both the hashmap and the hashset! | ||
// This is because the hashmap supports the map.containsKey() function. | ||
} | ||
|
||
public static int friendHopsVisualize(GraphNode<String> personA, GraphNode<String> personB) { | ||
// Hashset to store the people we have seen already | ||
HashSet<GraphNode> checked = new HashSet<>(); | ||
// Hashmap to remember how many hops were needed to get to a specific friend * | ||
HashMap<GraphNode, Integer> map = new HashMap<>(); | ||
|
||
Queue<GraphNode> queue = new LinkedList<>(); | ||
queue.add(personA); | ||
// the number of hops to the person themselves is 0, so we map: personA -> 0 | ||
map.put(personA, 0); | ||
int hops = 0; | ||
|
||
while (!queue.isEmpty()) { | ||
System.out.println("Current queue: " + String.valueOf(queue) + ", current hops: " + hops); | ||
// poll the queue to get the next person to consider | ||
GraphNode<String> currPerson = queue.remove(); | ||
// add the person to the checked hashset so we don't consider them again | ||
checked.add(currPerson); | ||
// grab the number of hops from the hashmap and add 1 | ||
hops = map.get(currPerson) + 1; | ||
|
||
System.out.println("Looking at friends of: " + currPerson.toString()); | ||
List<GraphNode> neighbours = currPerson.neighbours(); | ||
for (GraphNode neighbour : neighbours) { | ||
if (neighbour == personB) { return hops; } | ||
if (!checked.contains(neighbour)) { | ||
queue.add(neighbour); | ||
map.put(neighbour, hops); | ||
} | ||
} | ||
System.out.println("Current queue: " + String.valueOf(queue) + ", current hops: " + hops); | ||
} | ||
// Returns -1 if person not found | ||
return -1; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package src.algorithms.graphs.util; | ||
|
||
public class BinaryTreeNode { | ||
int val; | ||
BinaryTreeNode left = null; | ||
BinaryTreeNode right = null; | ||
|
||
public BinaryTreeNode(int val) { | ||
this.val = val; | ||
} | ||
|
||
public BinaryTreeNode(int val, BinaryTreeNode left, BinaryTreeNode right) { | ||
this.val = val; | ||
this.left = left; | ||
this.right = right; | ||
} | ||
|
||
public int getVal() { return this.val; } | ||
public BinaryTreeNode getLeft() { return this.left; } | ||
public BinaryTreeNode getRight() { return this.right; } | ||
|
||
public void setLeft(BinaryTreeNode left) { this.left = left; } | ||
public void setRight(BinaryTreeNode right) { this.right = right; } | ||
|
||
@Override | ||
public boolean equals(Object other) { | ||
if (other == this) { return true; } | ||
if (!(other instanceof BinaryTreeNode)) { return false; } | ||
BinaryTreeNode node = (BinaryTreeNode) other; | ||
return this.val == node.val; | ||
} | ||
|
||
@Override | ||
public int hashCode() { return this.val; } | ||
|
||
@Override | ||
public String toString() { return String.valueOf(this.val); } | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package src.algorithms.graphs.util; | ||
|
||
import java.util.*; | ||
|
||
public class GraphNode <T> { | ||
public T val; | ||
public List<GraphNode> neighbour; | ||
|
||
public GraphNode(T val) { | ||
this.val = val; | ||
neighbour = new ArrayList<>(); | ||
} | ||
|
||
public GraphNode(T val, List<GraphNode> neighbours) { | ||
this.val = val; | ||
this.neighbour = neighbours; | ||
} | ||
|
||
public static <T> void connect(GraphNode<T> a, GraphNode<T> b) { | ||
a.neighbour.add(b); | ||
b.neighbour.add(a); | ||
} | ||
|
||
|
||
public List<GraphNode> neighbours() { return this.neighbour; } | ||
|
||
public T getVal() { return this.val; } | ||
|
||
@Override | ||
public String toString() { return String.valueOf(this.val); } | ||
|
||
@Override | ||
public boolean equals(Object other) { | ||
if (other == this) { return true; } | ||
if (!(other instanceof GraphNode)) { return false; } | ||
GraphNode node = (GraphNode) other; | ||
return this.val == node.val; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return val.hashCode(); | ||
} | ||
} |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interestingly, do you know when its strictly (or close to maybe 1-off) to V+E? follow-up: would a complete graph take O(V+E)?