Skip to content
Merged

bfs #17

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions src/algorithms/graphs/breadthFirstSearch.java
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
Copy link
Owner

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)?

* 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
Copy link
Owner

Choose a reason for hiding this comment

The 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
Copy link
Owner

Choose a reason for hiding this comment

The 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<>();
Copy link
Owner

Choose a reason for hiding this comment

The 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
Copy link
Owner

Choose a reason for hiding this comment

The 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 *
Copy link
Owner

Choose a reason for hiding this comment

The 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;
}
}
24 changes: 14 additions & 10 deletions src/algorithms/graphs/depthFirstSearch.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package src.algorithms.graphs;

import java.util.*;
import src.algorithms.graphs.util.TreeNode;
import src.algorithms.graphs.util.BinaryTreeNode;

/**
* Implementation of DFS
*
* Depth-First search is a graph traversal algorithm that utilizes a stack (FIFO) data structure
* Depth-First search is a graph traversal algorithm that utilizes a stack (LIFO) data structure
* It is useful in finding a shortest-path solution (IN TREES), or a single solution in graphs
* This method is also used to obtain order-traversals of trees.
*
Expand All @@ -22,28 +22,32 @@
* 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 stack, hence O(E).
* Note that if the graph is a forest, DFS 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 O(V) vertices.
* connected graph where all vertices are traversed, and our Hashset stores all O(V) vertices.
* Further, we use a stack to hold the vertices to be traversed. In the worst case, the root will have all
* other vertices as neighbours, and our stack 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
* average case time complexity O(V^2) for a connected graph
*
* The implementation demonstrates the use of DFS in finding the pre-order (Root, Left, Right) traversal of a binary tree
* The tree is represented using a custom TreeNode class
* The tree is represented using a custom BinaryTreeNode class
*
* TODO: Add new examples, and algo for all orderings
*/

public class depthFirstSearch {

public static List<Integer> preOrder(TreeNode root) {
public static List<Integer> preOrder(BinaryTreeNode root) {
if (root == null) { return new ArrayList<>(); }
List<Integer> traversal = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
Stack<BinaryTreeNode> stack = new Stack<>();
stack.push(root);

while (!stack.empty()) {
TreeNode curr = stack.pop();
BinaryTreeNode curr = stack.pop();
traversal.add(curr.getVal());
if (curr.getRight() != null) { stack.push(curr.getRight()); }
if (curr.getLeft() != null) { stack.push(curr.getLeft()); }
Expand All @@ -53,15 +57,15 @@ public static List<Integer> preOrder(TreeNode root) {
}

// call this for visualization of process
public static List<Integer> preOrderVisualize(TreeNode root) {
public static List<Integer> preOrderVisualize(BinaryTreeNode root) {
if (root == null) { return new ArrayList<>(); }
List<Integer> traversal = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
Stack<BinaryTreeNode> stack = new Stack<>();
stack.push(root);

while (!stack.empty()) {
System.out.println("Current stack: " + stack.toString() + ", Current traversal: " + traversal.toString());
TreeNode curr = stack.pop();
BinaryTreeNode curr = stack.pop();
System.out.println("Popped Node: " + Integer.toString(curr.getVal()));
traversal.add(curr.getVal());
System.out.println("Current stack: " + stack.toString() + ", Current traversal: " + traversal.toString());
Expand Down
39 changes: 39 additions & 0 deletions src/algorithms/graphs/util/BinaryTreeNode.java
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); }

}
44 changes: 44 additions & 0 deletions src/algorithms/graphs/util/GraphNode.java
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();
}
}
39 changes: 0 additions & 39 deletions src/algorithms/graphs/util/TreeNode.java

This file was deleted.

Loading