Graph

Undirected Unweighted Graph
Graph Undirected Unweighted Graph

Table of Contents

Graph Representation & usage

Choosing between map[int][]edge and [][]int{} for representing graph data in Go depends on the specific requirements of your application and the characteristics of the graph you are working with. Here’s a comparison of both approaches:

Adjecancy List map[int][]edge

Advantages

  1. Sparsity Handling: It efficiently represents sparse graphs, where not every node has edges to every other node. Only existing edges are stored, which saves memory.
  2. Flexible Node Identification: Using integers as keys allows for non-continuous or arbitrary node IDs. You can easily add or remove nodes without worrying about their indices.
  3. Easier Edge Management: You can easily append edges to a node and dynamically manage edges without predefined structure.

Disadvantages

  1. Overhead: There’s a slight overhead in using maps compared to slices due to hashing and other internal mechanisms.
  2. Complexity: If you’re performing many operations that assume a dense representation (like matrix-based algorithms), it may require additional conversion logic.

Adjecancy Matrix[][]int{}

Advantages

  1. Simplicity: It can be simpler for dense graphs where the number of edges is close to the maximum possible edges (e.g., fully connected graphs).
  2. Direct Access: Provides O(1) access to edges if you know the indices, which can be beneficial for algorithms that frequently check for edges.
  3. Straightforward Representation: A 2D slice is a straightforward representation for algorithms that rely on adjacency matrices.

Disadvantages

  1. Space Inefficiency: It can waste space in sparse graphs because you may allocate many empty slots for non-existent edges.
  2. Fixed Structure: The structure is fixed; adding or removing nodes may require reshaping the matrix, which can be inefficient.
  3. Node ID Constraints: It’s less flexible with node IDs, requiring them to be contiguous and starting from zero, making it less versatile for dynamic graphs.

Conclusion

  • Use map[int][]edge if you need to represent sparse graphs, have arbitrary node IDs, or require dynamic graph modifications (adding/removing nodes and edges).
  • Use [][]int{} if you are working with dense graphs, need constant-time edge lookups, or are implementing algorithms that naturally fit an adjacency matrix.

In many real-world applications, especially with sparse graphs, map[int][]edge is often the preferred choice for its flexibility and efficiency. However, if your graph is dense and fits well into a fixed structure, then using [][]int{} might be simpler and more efficient.

Graph using Adjacency List

Below code covers basic graph creation.

  • Create Graph
  • Add Vertex and Edges in Graph
  • Print all Vertices and edges corresponding to it

Golang Solution

package main

import "fmt"

type edge struct {
	source      int
	destination int
	weight      int
}

func createEdge(graph map[int][]edge, source, destination, weight int) {
	graph[source] = append(graph[source], edge{
		source:      source,
		destination: destination,
		weight:      weight,
	})
}

func createGraph(graph map[int][]edge) {
	createEdge(graph, 0, 1, 0)
	createEdge(graph, 0, 2, 0)

	createEdge(graph, 1, 0, 0)
	createEdge(graph, 1, 3, 0)

	createEdge(graph, 2, 0, 0)
	createEdge(graph, 2, 4, 0)

	createEdge(graph, 3, 1, 0)
	createEdge(graph, 3, 4, 0)
	createEdge(graph, 3, 5, 0)

	createEdge(graph, 4, 2, 0)
	createEdge(graph, 4, 5, 0)
	createEdge(graph, 4, 3, 0)

	createEdge(graph, 5, 3, 0)
	createEdge(graph, 5, 4, 0)
	createEdge(graph, 5, 6, 0)

	return
}

func printGraph(graph map[int][]edge) {
	for vertex, edges := range graph {
		fmt.Printf("Vertex=%d : ", vertex)

		for _, edge := range edges {
			fmt.Printf("%d,", edge.destination)

		}

		fmt.Println("")
	}
}

func main() {
	graph := make(map[int][]edge)

	createGraph(graph)
	printGraph(graph)
}

Output

Vertex=0 : 1,2,
Vertex=1 : 0,3,
Vertex=2 : 0,4,
Vertex=3 : 1,4,5,
Vertex=4 : 2,5,3,
Vertex=5 : 3,4,6,

Program exited.

BFS + Disconnected Components

BFS Important Logic

func bfs(graph map[int][]edge, visited []bool, startingVertex int) (component []int, err error) {

	component = []int{}
	mq := createQueue()
	mq.enQueue(startingVertex)

	for !mq.isEmpty() {
		vertex := mq.deQueue()

		if !visited[vertex] {
			visited[vertex] = true
			component = append(component, vertex)

			for _, edge := range graph[vertex] {
				if !visited[edge.destination] {
					mq.enQueue(edge.destination)
				}
			}
		}
	}
	return component, nil
}

Full Code

package main

import (
	"errors"
	"fmt"
)

type edge struct {
	source      int
	destination int
	weight      int
}

func createEdge(graph map[int][]edge, source, destination, weight int) {
	graph[source] = append(graph[source], edge{
		source:      source,
		destination: destination,
		weight:      weight,
	})
}

type queue struct {
	q []int
}

func (mq *queue) enQueue(item int) {
	mq.q = append(mq.q, item)
}

func (mq *queue) deQueue() (item int, err error) {
	if mq.isEmpty() {
		return -1, errors.New("Queue is empty")
	}
	item = mq.q[0]
	mq.q = mq.q[1:]
	return item, nil
}

func (mq *queue) isEmpty() bool {
	return len(mq.q) == 0
}

func createQueue() (mq *queue) {
	return &queue{q: []int{}}
}

func createGraph(graph map[int][]edge) {
	createEdge(graph, 0, 1, 0)
	createEdge(graph, 0, 2, 0)

	createEdge(graph, 1, 0, 0)
	createEdge(graph, 1, 3, 0)

	createEdge(graph, 2, 0, 0)
	createEdge(graph, 2, 4, 0)

	createEdge(graph, 3, 1, 0)
	createEdge(graph, 3, 4, 0)
	createEdge(graph, 3, 5, 0)

	createEdge(graph, 4, 2, 0)
	createEdge(graph, 4, 5, 0)
	createEdge(graph, 4, 3, 0)

	createEdge(graph, 5, 3, 0)
	createEdge(graph, 5, 4, 0)
	createEdge(graph, 5, 6, 0)

	createEdge(graph, 7, 8, 0)

	return
}

func printGraph(graph map[int][]edge) {
	for vertex, edges := range graph {
		fmt.Printf("Vertex=%d : ", vertex)

		for _, edge := range edges {
			fmt.Printf("%d,", edge.destination)

		}

		fmt.Println("")
	}
}

func bfs(graph map[int][]edge, visited []bool, startingVertex int) (component []int, err error) {
	// Check if graph is empty
	if len(graph) == 0 {
		return nil, errors.New("graph is empty")
	}

	// Initialize component slice and create a new queue
	component = []int{}
	mq := createQueue()
	mq.enQueue(startingVertex)

	for !mq.isEmpty() {
		vertex, err := mq.deQueue()
		if err != nil {
			return nil, err
		}

		// Proceed if the vertex has not been visited
		if !visited[vertex] {
			visited[vertex] = true
			component = append(component, vertex)
			fmt.Printf("Visited vertex=%d\n", vertex)

			// Enqueue unvisited neighbors
			for _, edge := range graph[vertex] {
				if !visited[edge.destination] {
					mq.enQueue(edge.destination)
				}
			}
		}
	}
	return component, nil
}

func getMaxVertex(graph map[int][]edge) int {
	maxVertex := 0
	for vertex := range graph {
		if maxVertex < vertex {
			maxVertex = vertex
		}
		for _, edge := range graph[vertex] {
			if maxVertex < edge.destination {
				maxVertex = edge.destination
			}
		}
	}
	return maxVertex
}

func main() {
	graph := make(map[int][]edge)

	createGraph(graph)
	fmt.Println(" ---------- Graph representation using adjacency list ---------- ")

	printGraph(graph)

	fmt.Println(" ---------- BFS Started ---------- ")

	maxVertex := getMaxVertex(graph)
	visited := make([]bool, maxVertex+1)

	components := [][]int{}
	for vertex := range graph {
		if !visited[vertex] {
			component, err := bfs(graph, visited, vertex)
			components = append(components, component)
			if err != nil {
				fmt.Println(err)
			}
		}
	}

	fmt.Println(" ---------- Disconnected Component list ---------- ")
	// printing all components
	for _, component := range components {
		fmt.Println("Component : ", component)
	}
}

Output

---------- Graph representation using adjacency list ---------- 
Vertex=4 : 2,5,3,
Vertex=5 : 3,4,6,
Vertex=7 : 8,
Vertex=0 : 1,2,
Vertex=1 : 0,3,
Vertex=2 : 0,4,
Vertex=3 : 1,4,5,
 ---------- BFS Started ---------- 
Visited vertex=7
Visited vertex=8
Visited vertex=0
Visited vertex=1
Visited vertex=2
Visited vertex=3
Visited vertex=4
Visited vertex=5
Visited vertex=6
 ---------- Disconnected Component list ---------- 
Component :  [7 8]
Component :  [0 1 2 3 4 5 6]

Program exited.

DFS (Depth First Search Traversal)

DFS Important Logic

func dfs(graph map[int][]edge, visited []bool, vertex int, component *[]int) {
	visited[vertex] = true
	*component = append(*component, vertex)

	for _, edge := range graph[vertex] {
		if !visited[edge.destination] {
			dfs(graph, visited, edge.destination, component)
		}
	}
}

Full Code

package main

import (
	"fmt"
	"sort"
)

type edge struct {
	source      int
	destination int
}

func createEdge(graph map[int][]edge, source, destination int) {
	graph[source] = append(graph[source], edge{
		source:      source,
		destination: destination,
	})
}

func createGraph() map[int][]edge {
	graph := map[int][]edge{}

	createEdge(graph, 0, 1)
	createEdge(graph, 0, 2)
	createEdge(graph, 1, 3)
	createEdge(graph, 1, 0)
	createEdge(graph, 2, 4)
	createEdge(graph, 2, 0)
	createEdge(graph, 3, 5)
	createEdge(graph, 3, 4)
	createEdge(graph, 3, 1)
	createEdge(graph, 4, 3)
	createEdge(graph, 4, 5)
	createEdge(graph, 4, 2)
	createEdge(graph, 5, 3)
	createEdge(graph, 5, 4)
	createEdge(graph, 5, 6)
	createEdge(graph, 6, 5)

	return graph
}

func getVertices(graph map[int][]edge) []int {
	uniqueVertices := make(map[int]bool)

	for vertex, edges := range graph {
		uniqueVertices[vertex] = true // Add source vertex

		for _, edge := range edges {
			uniqueVertices[edge.destination] = true // Add destination vertex
		}
	}

	vertices := make([]int, 0, len(uniqueVertices))
	for vertex := range uniqueVertices {
		vertices = append(vertices, vertex)
	}

	sort.Ints(vertices) // Sort vertices in ascending order
	return vertices
}

func dfs(graph map[int][]edge, visited []bool, vertex int, component *[]int) {
	visited[vertex] = true
	*component = append(*component, vertex)

	for _, edge := range graph[vertex] {
		if !visited[edge.destination] {
			dfs(graph, visited, edge.destination, component)
		}
	}
}

func main() {
	graph := createGraph()

	vertices := getVertices(graph)

	// Determine the size of `visited` dynamically based on the maximum vertex ID
	maxVertexID := vertices[len(vertices)-1]
	visited := make([]bool, maxVertexID+1)

	// Traverse over all vertices
	for _, vertex := range vertices {
		component := []int{}

		if !visited[vertex] {
			dfs(graph, visited, vertex, &component)
			fmt.Printf("component=%#v\n", component)
		}
	}
}

Output

component=[]int{0, 1, 3, 5, 4, 2, 6}

Program exited.

ALL PATH from Source to All Vertices DFS

Modified DFS

//All Path S to T : modified dfs

package main

import "fmt"

type edge struct {
	source      int
	destination int
}

func createEdge(graph map[int][]edge, s, d int) {
	graph[s] = append(graph[s], edge{
		source:      s,
		destination: d,
	})
}

func createGraph() map[int][]edge {
	graph := map[int][]edge{}

	createEdge(graph, 0, 1)
	createEdge(graph, 1, 0)

	createEdge(graph, 0, 2)
	createEdge(graph, 2, 0)

	createEdge(graph, 1, 3)
	createEdge(graph, 3, 1)

	createEdge(graph, 2, 4)
	createEdge(graph, 4, 2)

	createEdge(graph, 3, 4)
	createEdge(graph, 4, 3)

	createEdge(graph, 3, 5)
	createEdge(graph, 5, 3)

	createEdge(graph, 4, 5)
	createEdge(graph, 5, 4)

	createEdge(graph, 5, 6)
	createEdge(graph, 6, 5)

	return graph
}

func dfs(graph map[int][]edge, visited []bool, current int, path string, target int) {
	// base case
	if current == target {
		fmt.Printf("\nPath= %#v\n", path)
		return
	}

	visited[current] = true

	for _, edge := range graph[current] {
		if !visited[edge.destination] {
			dfs(graph, visited, edge.destination, path+fmt.Sprintf("%d", edge.destination), target)
		}
	}

	visited[current] = false
}

func main() {
	graph := createGraph()

	maxVertex := 6
	source := 0
	target := 5
	path := fmt.Sprintf("%d", source)
	visited := make([]bool, maxVertex+1)

	dfs(graph, visited, source, path, target)

}

Output

Path= "01345"

Path= "0135"

Path= "02435"

Path= "0245"

Program exited.

Cycle Detection (DFS)

UnDirected Graph Cycle Detection

UnDirected Graph Cycle Detection
UnDirected Graph Cycle Detection

Important Logic (UnDir Graph Cycle Detection)

  • Here, in modified DFS, we will start returning value from DFS recursive function.
  • Be careful of accepting this return value and take decision based on that.
  • If neighbour is already visited and it is NOT Parent,
    • then we have found cycle.
func dfs(graph map[int][]edge, visited []bool, currentVertex, parentVertex int) bool {
	visited[currentVertex] = true

	// visit all neighbours
	for _, edge := range graph[currentVertex] {
	
	  // If neighbour is already visited and it is NOT Parent,
    // then we have found cycle.
		if visited[edge.destination] && edge.destination != parentVertex {
			return true
		}
		if !visited[edge.destination] {
		  // Here, in modified DFS, 
		  // we will start returning value from DFS recursive function.

      // Be careful of accepting this return value 
      // and take decision based on that.
			if dfs(graph, visited, edge.destination, currentVertex) {
				return true
			}
		}
	}
	return false
}

Golang Solution

package main

import (
	"fmt"
)

type edge struct {
	source      int
	destination int
}

func createEdge(graph map[int][]edge, source, destination int) {
	graph[source] = append(graph[source], edge{
		source:      source,
		destination: destination,
	})
}

func createGraph() map[int][]edge {
	graph := map[int][]edge{}
	createEdge(graph, 0, 1)
	createEdge(graph, 1, 0)

	createEdge(graph, 0, 2)
	createEdge(graph, 2, 0)

	createEdge(graph, 0, 3)
	createEdge(graph, 3, 0)

	createEdge(graph, 2, 3)
	createEdge(graph, 3, 2)
	return graph
}

func dfs(graph map[int][]edge, visited []bool, currentVertex, parentVertex int) bool {
	visited[currentVertex] = true

	// visit all neighbours
	for _, edge := range graph[currentVertex] {
	
	  // If neighbour is already visited and it is NOT Parent,
    // then we have found cycle.
		if visited[edge.destination] && edge.destination != parentVertex {
			return true
		}
		if !visited[edge.destination] {
			if dfs(graph, visited, edge.destination, currentVertex) {
				return true
			}
		}
	}
	return false
}

func main() {
	graph := createGraph()

	maxVertex := 3

	visited := make([]bool, maxVertex+1)
	isCyclePresent := false

	// traverse all vertices
	for i := 0; i < maxVertex; i++ {
		parentVertex := -1
		startingVertex := i
		if !visited[startingVertex] {
			isCyclePresent = dfs(graph, visited, startingVertex, parentVertex)
			if isCyclePresent {
				fmt.Printf("Cycle exist in given graph")
				break
			}
		}
	}

	if !isCyclePresent {
		fmt.Printf("Cycle does NOT exist in given graph")
	}
}

Output

Cycle exist in given graph
Program exited.

Cycle Detection (Directed Graph) DFS

Important Logic

func dfs(graph map[int][]edge, visited []bool, current int, stack []bool) bool {

	// use stack to keep track of vertex which are called in past
	// if we find a vertex which is unVisited and present on stack then
	// cycle is present in graph

	visited[current] = true
	
	// stack is used to check if certain vertex is present 
	// on current recursion stack or not

	stack[current] = true
	for _, edge := range graph[current] {
		
		// if vertex is present on recursion stack
		// then it mean cycle exist !
		// EASY 
		if stack[edge.destination] {
			return true
		}

		if !visited[edge.destination] {
			if dfs(graph, visited, edge.destination, stack) {
				return true
			}
		}
	}
	
	// important to clean up recursion STACK
	// TRICKY !
	stack[current] = false
	return false
}

Golang Solution

//practice dfs

package main

import (
	"fmt"
)

type edge struct {
	source      int
	destination int
}

func createEdge(graph map[int][]edge, s, d int) {
	graph[s] = append(graph[s], edge{
		source:      s,
		destination: d,
	})
}

func createGraph() map[int][]edge {
	graph := map[int][]edge{}

	createEdge(graph, 0, 1)
	createEdge(graph, 0, 3)
	createEdge(graph, 3, 2)
	// below edge creates cycle
	createEdge(graph, 2, 0)

	return graph
}

func dfs(graph map[int][]edge, visited []bool, current int, stack []bool) bool {

	// use stack to keep track of vertex which are called in past
	// if we find a vertex which is unVisited and present on stack then
	// cycle is present in graph

	visited[current] = true
	stack[current] = true
	for _, edge := range graph[current] {
		
		// if vertex is present on recursion stack
		// then it mean cycle exist !
		// EASY 
		if stack[edge.destination] {
			return true
		}

		if !visited[edge.destination] {
			if dfs(graph, visited, edge.destination, stack) {
				return true
			}
		}
	}
	
	// important to clean up recursion STACK
	// TRICKY !
	stack[current] = false
	return false
}

func main() {

	graph := createGraph()
	maxVertex := 3
	visited := make([]bool, maxVertex+1)
	stack := make([]bool, maxVertex+1)
	isCyclePresent := false
	for i := 0; i <= maxVertex; i++ {
		if !visited[i] {
			isCyclePresent = dfs(graph, visited, i, stack)
			if isCyclePresent {
				fmt.Printf("Cycle Exist")
				break
			}
		}
	}
	if !isCyclePresent {
		fmt.Printf("Cycle does NOT Exist")
	}
}

Output

Cycle Exist
Program exited.

Topological Sort (DAG) (DFS)

Directed ACyclic Graph

  • Directed Acyclic graph (DAG) is a directed graph with no cycles.
    • Directed Graph + NO Cycles
  • Topological sort is used only for DAGs.
  • It is a linear order of vertices such that such that directed edge u->v, the vertex u comes before v in the order.
topological sort graph input
Topological Sort Graph Input

Golang Solution

//Topological Sort

//DAG - Directed Acyclic Graph

package main

import (
	"fmt"
)

type edge struct {
	source      int
	destination int
}

func createEdge(graph map[int][]edge, source, destination int) {
	graph[source] = append(graph[source], edge{
		source:      source,
		destination: destination,
	})
}

func createGraph() map[int][]edge {
	graph := map[int][]edge{}
	createEdge(graph, 2, 3)
	createEdge(graph, 3, 1)
	createEdge(graph, 4, 0)
	createEdge(graph, 4, 1)
	createEdge(graph, 5, 0)
	createEdge(graph, 5, 2)

	return graph
}

func topoSortUtil(graph map[int][]edge, visited []bool, current int, stack *[]int) {
	visited[current] = true

	// visited neighbours
	for _, edge := range graph[current] {
		if !visited[edge.destination] {
			topoSortUtil(graph, visited, edge.destination, stack)
		}
	}

	// PUSH ONTO STACK
	// Simple but Tricky
	*stack = append(*stack, current)
}

func topoSort(graph map[int][]edge, visited []bool, maxVertex int) {

	stack := []int{}
	for i := 0; i <= maxVertex; i++ {
		if !visited[i] {
			topoSortUtil(graph, visited, i, &stack)
		}
	}

	// print stack in REVERSE order
	for j := len(stack) - 1; j >= 0; j -= 1 {
		fmt.Printf("%d ", stack[j])
	}

	fmt.Printf("\n")
}

func main() {

	graph := createGraph()
	maxVertex := 5
	visited := make([]bool, maxVertex+1)

	topoSort(graph, visited, maxVertex)
}

Output

5 4 2 3 1 0

Strongly Connected Components (SCC) DFS

SCC is component in which we can reach every vertex of the component from every other vertex in that component.

1. Kosaraju Algorithm (Topological Sort, transpose, Reverse DFS)

  1. Topological Sort
  2. Transpose Graph
  3. Reverse DFS
    • Use DFS on Transpose Graph using vertices order given by topological sort

Kosaraju’s Algorithm Complexity

  • Two DFS Passes: Kosaraju’s algorithm performs two passes of DFS, making it efficient at O(V+E) complexity, where V is the number of vertices and E is the number of edges.
    • This efficiency makes Kosaraju’s algorithm particularly suitable for large graphs.

Alternative SCC Algorithms (e.g., Tarjan’s Algorithm)

  • Comparison with Tarjan’s Algorithm: While Kosaraju’s algorithm uses two passes of DFS, Tarjan’s algorithm finds SCCs in a single DFS pass using low-link values and a stack.
    • Kosaraju’s is simpler conceptually but requires transposing the graph, where as Tarjan’s is more intricate but avoids graph transposition.

Golang solution

package main

import (
	"fmt"
)

type edge struct {
	source      int
	destination int
}

func createEdge(graph map[int][]edge, source int, destination int) {
	graph[source] = append(graph[source], edge{
		source:      source,
		destination: destination,
	})
}

func createGraph() map[int][]edge {
	graph := map[int][]edge{}
	createEdge(graph, 0, 2)
	createEdge(graph, 2, 1)
	createEdge(graph, 1, 0)
	createEdge(graph, 0, 3)
	createEdge(graph, 3, 4)
	return graph
}

func dfs(transpose map[int][]edge, visited []bool, current int) {
	visited[current] = true
	fmt.Printf("%d ", current)

	for _, edge := range transpose[current] {
		if !visited[edge.destination] {
			dfs(transpose, visited, edge.destination)
		}
	}
}

func topoSortUtil(graph map[int][]edge, visited []bool, current int, stack *[]int) {
	visited[current] = true
	for _, edge := range graph[current] {
		if !visited[edge.destination] {
			topoSortUtil(graph, visited, edge.destination, stack)
		}
	}
	*stack = append(*stack, current)
}

func topoSort(graph map[int][]edge, visited []bool, vertices []int, stack *[]int) {
	for _, vertex := range vertices {
		if !visited[vertex] {
			topoSortUtil(graph, visited, vertex, stack)
		}
	}

}

func kosaRajuAlgo(graph map[int][]edge, visited []bool, vertices []int) {

	// 1. apply topological sort to get order of vertices in stack
	stack := []int{}
	topoSort(graph, visited, vertices, &stack)

	// 2. Transpose graph
	transpose := map[int][]edge{}
	for vertex, edges := range graph {
		visited[vertex] = false
		for _, e := range edges {
			visited[e.destination] = false
			transpose[e.destination] = append(transpose[e.destination], edge{
				source:      e.destination,
				destination: e.source,
			})
		}
	}

	// 3. Pop all item from stack and apply dfs of each vertex to get list of SCC
	// in reverse order as we are using stack
	for j := len(stack) - 1; j >= 0; j -= 1 {
		if !visited[stack[j]] {
			dfs(transpose, visited, stack[j])
			fmt.Println()
		}
	}
}

func main() {
	graph := createGraph()
	vertices := []int{0, 1, 2, 3, 4}

	totalVertices := len(vertices)
	visited := make([]bool, totalVertices)

	kosaRajuAlgo(graph, visited, vertices)

}

Output

stack=[]int{1, 2, 4, 3, 0}
0 1 2 
3 
4 

Program exited.

Tarjan Algorithm (DFS)

articulation_point
Articulation Point

Can be used to find

  • Bridge in graph
  • Articulation point in graph
  • Stongly connected component in graph
  • Topological order

1. Bridge in graph (DFS)

  • Bridge is an edge whose deletion increases the total number of connected components in a graph.
  • We will need to track below metadata
    • dt : discovery time
    • lowest dt : lowest discovery time among neighbour nodes

Golang Solution

// Tarjan Algo- bridge edge
package main

import (
	"fmt"
)

type edge struct {
	source      int
	destination int
}

func createEdge(graph map[int][]edge, source, destination int) {
	graph[source] = append(graph[source], edge{
		source:      source,
		destination: destination,
	})
}

func createGraph() map[int][]edge {
	graph := map[int][]edge{}

	createEdge(graph, 0, 1)
	createEdge(graph, 0, 2)
	createEdge(graph, 0, 3)
	createEdge(graph, 1, 0)
	createEdge(graph, 1, 2)
	createEdge(graph, 2, 1)
	createEdge(graph, 2, 0)
	createEdge(graph, 3, 0)
	createEdge(graph, 3, 4)
	createEdge(graph, 3, 5)
	createEdge(graph, 4, 3)
	createEdge(graph, 4, 5)
	createEdge(graph, 5, 3)
	createEdge(graph, 5, 4)
	return graph
}

func tarjanAlgo(graph map[int][]edge, visited []bool, dt []int, low []int, current int, parentVertex int, time int) {
	time++
	visited[current] = true
	dt[current] = time
	low[current] = dt[current]

	for _, edge := range graph[current] {
		// ignore parent
		if edge.destination == parentVertex {
			continue
		}

		// non visited not
		// regular case
		if !visited[edge.destination] {
			tarjanAlgo(graph, visited, dt, low, edge.destination, current, time)
			// update low
			low[current] = min(low[current], low[edge.destination])

			if dt[edge.source] < low[edge.destination] {
				fmt.Printf("Bridge edge : %d-%d\n", edge.source, edge.destination)
			}
		}

		if visited[edge.destination] {
			// update low using dt of neighbour
			// TRICKY !
			low[current] = min(low[current], dt[edge.destination])
		}
	}
}

func min(a, b int) int {
	if a > b {
		return b
	}
	return a
}

func main() {

	vertices := []int{0, 1, 2, 3, 4, 5}
	totalVertices := len(vertices) + 1
	parentVertex := -1
	time := 0
	graph := createGraph()

	dt := make([]int, totalVertices)
	low := make([]int, totalVertices)
	visited := make([]bool, totalVertices)

	for _, vertex := range vertices {
		if !visited[vertex] {
			tarjanAlgo(graph, visited, dt, low, vertex, parentVertex, time)
		}
	}
}

Output

Bridge edge : 0-3

Program exited.

2. Articulation Point (DFS)

  • Vertex in an undirected connected graph is an Articulation point (or cut vertex) , if removing it (and edges through it) disconnects the graph
  • Ancestor : A node that was discovered before current node in DFS, it an Ancestor of current.

Golang Solution

//Articulation Point - Tarjan Algo

package main

import (
	"fmt"
)

type edge struct {
	source      int
	destination int
}

func createEdge(graph map[int][]edge, source, destination int) {
	graph[source] = append(graph[source], edge{
		source:      source,
		destination: destination,
	})
}

func createGraph() map[int][]edge {
	graph := map[int][]edge{}

	createEdge(graph, 0, 1)
	createEdge(graph, 0, 2)
	createEdge(graph, 0, 3)
	createEdge(graph, 1, 0)
	createEdge(graph, 1, 2)
	createEdge(graph, 2, 1)
	createEdge(graph, 2, 0)
	createEdge(graph, 3, 0)
	createEdge(graph, 3, 4)
	createEdge(graph, 3, 5)
	createEdge(graph, 4, 3)
	createEdge(graph, 4, 5)
	createEdge(graph, 5, 3)
	createEdge(graph, 5, 4)
	return graph
}

func tarjanAlgo(graph map[int][]edge, visited []bool, dt []int, low []int, current int, parentVertex int, time int, ap *[]int) {
	time++
	visited[current] = true
	dt[current] = time
	low[current] = dt[current]
	disconnectedChildren := 0

	for _, edge := range graph[current] {
		// ignore parentVertex
		if edge.destination == parentVertex {
			continue
		}

		// non visited not
		// regular case
		if !visited[edge.destination] {
			tarjanAlgo(graph, visited, dt, low, edge.destination, current, time, ap)
			// update low
			low[current] = min(low[current], low[edge.destination])

      // TRICKY
      // check <= equal to condition here
      // a) cycle  u-> v with backedge
      //      dt[edge.source]==low[edge.destination]
      // b) bridge u-> v
      //      dt[edge.source] <low[edge.destination]

			if dt[edge.source] <= low[edge.destination] && parentVertex != -1 {
				*ap = append(*ap, current)
				//fmt.Printf("Articulation point : %d\n", current)
			}

			disconnectedChildren++
		}

		// similar to cycle condition in undirected graph
		// but without parent check
		if visited[edge.destination] {
			// update low
			// TRICKY !
			low[current] = min(low[current], dt[edge.destination])
		}
	}
	if disconnectedChildren > 1 && parentVertex == -1 {
		*ap = append(*ap, current)
		// fmt.Printf("Articulation point : %d\n", current)
	}

}

func min(a, b int) int {
	if a > b {
		return b
	}
	return a
}

func main() {

	vertices := []int{0, 1, 2, 3, 4, 5}
	totalVertices := len(vertices) + 1
	parentVertex := -1
	time := 0
	graph := createGraph()

	dt := make([]int, totalVertices)
	low := make([]int, totalVertices)
	visited := make([]bool, totalVertices)
	ap := []int{}

	for _, vertex := range vertices {
		if !visited[vertex] {
			tarjanAlgo(graph, visited, dt, low, vertex, parentVertex, time, &ap)
		}
	}
	fmt.Printf("Articulation Points : %#v\n", ap)
}

Output

Articulation Points : []int{3, 0}

SHORTEST PATH from Source to All Vertices (BFS)

Dijkstra's Algorithm
Dijkstra’s Algorithm

1. Dijkstra’s Algorithm (+ve edge) (BFS)

  • Use priority queue with {vertex, dist[vertex]}
  • Use BFS and get min distance vertex for processing

Golang solution

//Dijkstra Algo - Shortest path source to all destination
// expected Source = 0 , distance=[]int{0, 2, 3, 8, 6, 9}
// O(E + ElogV)

package main

import (
	"errors"
	"fmt"
	"math"
	"sort"
)

type pair struct {
	vertex   int
	distance int
}

type priorityQ struct {
	q []pair
}

func (pq *priorityQ) enQueue(entry pair) {
	pq.q = append(pq.q, entry)

	sort.Slice(pq.q, func(i, j int) bool {
		return pq.q[i].distance < pq.q[j].distance
	})
}

func (pq *priorityQ) deQueue() (pair, error) {
	if pq.isEmpty() {
		return pair{}, errors.New("Queue is empty")
	}

	entry := pq.q[0]
	pq.q = pq.q[1:]
	return entry, nil
}

func (pq *priorityQ) isEmpty() bool {
	return len(pq.q) == 0
}

type edge struct {
	source      int
	destination int
	weight      int
}

func createEdge(graph map[int][]edge, source, destination, weight int) {
	graph[source] = append(graph[source], edge{
		source:      source,
		destination: destination,
		weight:      weight,
	})
}

func createGraph() map[int][]edge {
	graph := map[int][]edge{}
	createEdge(graph, 0, 1, 2)
	createEdge(graph, 0, 2, 4)
	createEdge(graph, 1, 2, 1)
	createEdge(graph, 1, 3, 7)
	createEdge(graph, 2, 4, 3)
	createEdge(graph, 3, 5, 1)
	createEdge(graph, 4, 3, 2)
	createEdge(graph, 4, 5, 5)

	return graph
}

func dijkstra(graph map[int][]edge, visited []bool, source int, distance []int) error {
	for i := 0; i < len(distance); i++ {
		if i != source {
			// max signed int value
			distance[i] = math.MaxInt32
			//distance[i] = 1<<31 - 1
		}
	}

	pq := &priorityQ{
		q: []pair{},
	}
	// enqueue 1st item in queue
	pq.enQueue(pair{
		vertex:   source,
		distance: distance[source],
	})

	for !pq.isEmpty() {
		current, err := pq.deQueue()
		if err != nil {
			return err
		}

		if !visited[current.vertex] {
			visited[current.vertex] = true

			// visite all neighbours
			for _, edge := range graph[current.vertex] {
				if !visited[edge.destination] {
					if distance[edge.destination] > distance[current.vertex]+edge.weight {
						distance[edge.destination] = distance[current.vertex] + edge.weight
					}
					pq.enQueue(pair{
						vertex:   edge.destination,
						distance: distance[edge.destination],
					})
				}
			}
		}

	}
	return nil

}

func main() {
	graph := createGraph()
	vertices := []int{0, 1, 2, 3, 4, 5}
	totalVertices := len(vertices)

	visited := make([]bool, totalVertices)
	distance := make([]int, totalVertices)
	source := 0

	err := dijkstra(graph, visited, source, distance)
	if err != nil {
		fmt.Printf("BFS error = %#v", err)
	}
	fmt.Printf("Source = %d , distance=%#v\n", source, distance)
}

Output

Source = 0 , distance=[]int{0, 2, 3, 8, 6, 9}

Program exited.

2. BellmanFord Algorithm (+ve, -ve) (BFS)

  • Dijkstra’s Algorithm does NOT work correctly for negative (-ve) edge weights.
  • Prepare list of all edges
  • Use loop from 0 to V-1
    • Traverse over all edges and update distance of neighbour
  • Edge case : Negative weight cycle
    • Here, BellmanFord algo will FAIL
    • Safety check to detect -ve weight cycle:
      • Apply all edge loop one more time to check if negative weight cycle exist or not

Golang Solution

//Bellman_ford_shorted_path

package main

import (
	"errors"
	"fmt"
	"math"
	"sort"
)

type pair struct {
	vertex int
	dist   int
}

type priorityQ struct {
	q []pair
}

func (pq *priorityQ) enQueue(entry pair) {
	pq.q = append(pq.q, entry)

	sort.Slice(pq.q, func(i, j int) bool {
		return pq.q[i].dist < pq.q[j].dist
	})
}

func (pq *priorityQ) deQueue() (pair, error) {
	if pq.isEmpty() {
		return pair{}, errors.New("Priority Queue is empty")
	}

	entry := pq.q[0]
	pq.q = pq.q[1:]
	return entry, nil
}

func (pq *priorityQ) isEmpty() bool {
	return len(pq.q) == 0
}

type edge struct {
	source      int
	destination int
	weight      int
}

func createEdge(graph map[int][]edge, src, dest, wt int) {
	graph[src] = append(graph[src], edge{
		source:      src,
		destination: dest,
		weight:      wt,
	})
}

func createGraph() map[int][]edge {
	graph := map[int][]edge{}

	createEdge(graph, 0, 1, 2)
	createEdge(graph, 0, 2, 4)

	createEdge(graph, 1, 2, -4)

	createEdge(graph, 2, 3, 2)
	createEdge(graph, 3, 4, 4)

	// for postive weight cycle
	createEdge(graph, 4, 1, -1)

	// for negative weight cycle
	//createEdge(graph, 4, 1, -10)

	return graph
}

func bellmanFord(graph map[int][]edge, totalVertices int) []int {
	distance := make([]int, totalVertices)
	sourceVertex := 0

	for i := 0; i < totalVertices; i++ {
		distance[i] = math.MaxInt32
	}
	distance[sourceVertex] = 0

	pq := &priorityQ{}
	pq.enQueue(pair{
		vertex: 0,
		dist:   0,
	})

	for i := 0; i < totalVertices; i++ {
		for _, edges := range graph {
			for _, edge := range edges {
				if distance[edge.source] != math.MaxInt32 && distance[edge.source]+edge.weight < distance[edge.destination] {
					distance[edge.destination] = distance[edge.source] + edge.weight
				}
			}
		}
	}

	// check for negative weight cycle
	for _, edges := range graph {
		for _, edge := range edges {
			if distance[edge.source] != math.MaxInt32 && distance[edge.source]+edge.weight < distance[edge.destination] {
				fmt.Println("Negative weight cycle detected")
			}
		}
	}

	return distance
}

func main() {
	vertices := []int{0, 1, 2, 3, 4}
	totalVertices := len(vertices)
	//visited := make([]bool, totalVertices)

	graph := createGraph()

	distance := bellmanFord(graph, totalVertices)

	fmt.Printf("%#v\n", distance)
}

Output for positive weight cycle

[]int{0, 2, -2, 0, 4}

Program exited.

Output for negative weight cycle

/ if we update below edge to introduce -ve weight cycle
// for postive weight cycle
//createEdge(graph, 4, 1, -1)

// for negative weight cycle
//createEdge(graph, 4, 1, -10)

Negative weight cycle detected
[]int{0, -30, -26, -24, -20}

Program exited.

Minimum spanning Tree (BFS)

1. Prim’s Algorithm (BFS)

MST Prims Algo
MST Prims Algo

Minimum Spanning Tree

  • Minimum spanning tree (MST) or minimum weight spanning tree is a
    • subset of edges of a connected, edge-weighted, undirected graph
    • that connects all the vertices together
    • without any cycles
    • & with minimum possible total graph edge weight.

Golang Solution

  • Problem
    • In undirected graph reverse edge was missed by creating graph.
//MST Prim's Algo

// Problem
// in undirected graph reverse edge was missed by creating graph.

// connected
// weighted
// no cycles
// undirected

// MST Set
// Non MST Set

// BFS
// as soon as we move nonMST(PQ) to MST (visited)
// add dequeued item into MST
// and update total edge weight
// a) we can also save edges at this point
// but for that we need to push edge s, d vertex in PQ

package main

import (
	"errors"
	"fmt"
	"sort"
)

type edge struct {
	source      int
	destination int
	weight      int
}

func createEdge(graph map[int][]edge, s, d, wt int) {
	graph[s] = append(graph[s], edge{
		source:      s,
		destination: d,
		weight:      wt,
	})
}

func createGraph() map[int][]edge {
	graph := map[int][]edge{}
	createEdge(graph, 0, 1, 10)
	createEdge(graph, 0, 2, 15)
	createEdge(graph, 0, 3, 20)

	createEdge(graph, 1, 0, 10)
	createEdge(graph, 1, 3, 3)

	createEdge(graph, 2, 0, 15)
	createEdge(graph, 2, 3, 4)

	createEdge(graph, 3, 0, 20)
	createEdge(graph, 3, 1, 3)
	createEdge(graph, 3, 2, 4)

	return graph
}

type priorityQ struct {
	q []edge
}

func createPriorityQ() *priorityQ {
	return &priorityQ{
		q: []edge{},
	}
}

func (pq *priorityQ) enQueue(e edge) {
	pq.q = append(pq.q, e)
	sort.Slice(pq.q, func(i, j int) bool {
		return pq.q[i].weight < pq.q[j].weight
	})
}

func (pq *priorityQ) deQueue() (edge, error) {
	if pq.isEmpty() {
		return edge{}, errors.New("PriorityQueue is empty")
	}
	item := pq.q[0]
	pq.q = pq.q[1:]
	return item, nil
}

func (pq *priorityQ) isEmpty() bool {
	return len(pq.q) == 0
}

func mstAlgo(graph map[int][]edge, visited []bool, startingVertex int) ([]edge, int) {
	totalWt := 0
	mst := []edge{}
	pq := createPriorityQ()
	pq.enQueue(edge{
		source:      startingVertex,
		destination: startingVertex,
		weight:      0,
	})

	for !pq.isEmpty() {
		item, err := pq.deQueue()
		if err != nil {
			fmt.Printf("deQueue err = %#v\n", err)
			return []edge{}, -1
		}

		if !visited[item.destination] {
			if item.source != item.destination {
				totalWt += item.weight
				mst = append(mst, edge{
					source:      item.source,
					destination: item.destination,
					weight:      item.weight,
				})
			}

			visited[item.destination] = true

			for _, e := range graph[item.destination] {
				if !visited[e.destination] {
					pq.enQueue(edge{
						source:      e.source,
						destination: e.destination,
						weight:      e.weight,
					})
				}
			}
		}
	}

	return mst, totalWt
}

func main() {
	graph := createGraph()

	vertices := []int{0, 1, 2, 3}
	totalVertices := len(vertices)
	visited := make([]bool, totalVertices)
	startingVertex := 0

	mst, totalWt := mstAlgo(graph, visited, startingVertex)
	fmt.Printf("Total mst graph weight=%d\n", totalWt)

	for _, edge := range mst {
		fmt.Printf("MST edge= %#v\n", edge)
	}
}

Output

Total mst graph weight=17
MST edge= main.edge{source:0, destination:1, weight:10}
MST edge= main.edge{source:1, destination:3, weight:3}
MST edge= main.edge{source:3, destination:2, weight:4}


// Input Graph-2
//	createEdge(graph, 0, 1, 10)
//	createEdge(graph, 0, 2, 15)
//	createEdge(graph, 0, 3, 30)

//	createEdge(graph, 1, 0, 10)
//	createEdge(graph, 1, 3, 40)

//	createEdge(graph, 2, 0, 15)
//	createEdge(graph, 2, 3, 50)

//	createEdge(graph, 3, 0, 30)
//	createEdge(graph, 3, 1, 40)
//	createEdge(graph, 3, 2, 50)

// Output MST-2
Total mst graph weight=55
MST edge= main.edge{source:0, destination:1, weight:10}
MST edge= main.edge{source:0, destination:2, weight:15}
MST edge= main.edge{source:0, destination:3, weight:30}

Graph Interview Questions for practice

Rotting Oranges (M)

Number of Island (M)

Escape the Spreading Fire (H)

Course Schedule (M)

Find Eventual Safe States (M)

Quick Revision

BFS Important Logic

func bfs(graph map[int][]edge, visited []bool, startingVertex int) (component []int, err error) {

	component = []int{}
	mq := createQueue()
	mq.enQueue(startingVertex)

	for !mq.isEmpty() {
		vertex := mq.deQueue()

		if !visited[vertex] {
			visited[vertex] = true
			component = append(component, vertex)

			for _, edge := range graph[vertex] {
				if !visited[edge.destination] {
					mq.enQueue(edge.destination)
				}
			}
		}
	}
	return component, nil
}

DFS Important Logic

func dfs(graph map[int][]edge, visited []bool, vertex int, component *[]int) {
	visited[vertex] = true
	*component = append(*component, vertex)

	for _, edge := range graph[vertex] {
		if !visited[edge.destination] {
			dfs(graph, visited, edge.destination, component)
		}
	}
}

Modified DFS Important Logic All Paths


func dfs(graph map[int][]edge, visited []bool, current int, path string, target int) {
	if current == target {
		fmt.Printf("\nPath= %#v\n", path)
		return
	}

	visited[current] = true

	for _, edge := range graph[current] {
		if !visited[edge.destination] {
			dfs(graph, visited, edge.destination, path+fmt.Sprintf("%d", edge.destination), target)
		}
	}

  // This is extra logic added for modified DFS
  // tricky !
	visited[current] = false
}

UnDirected Graph – Cycle Detection

func dfs(graph map[int][]edge, visited []bool, currentVertex, parentVertex int) bool {
	visited[currentVertex] = true

	// visit all neighbours
	for _, edge := range graph[currentVertex] {
	
	  // If neighbour is already visited and it is NOT Parent,
    // then we have found cycle.
		if visited[edge.destination] && edge.destination != parentVertex {
			return true
		}
		if !visited[edge.destination] {
		  // Here, in modified DFS, 
		  // we will start returning value from DFS recursive function.

      // Be careful of accepting this return value 
      // and take decision based on that.
			if dfs(graph, visited, edge.destination, currentVertex) {
				return true
			}
		}
	}
	return false
}

Directed Graph – Cycle Detection

func dfs(graph map[int][]edge, visited []bool, current int, stack []bool) bool {

	// use stack to keep track of vertex which are called in past
	// if we find a vertex which is unVisited and present on stack then
	// cycle is present in graph

	visited[current] = true
	stack[current] = true
	for _, edge := range graph[current] {
		
		// if vertex is present on recursion stack
		// then it mean cycle exist !
		// EASY 
		if stack[edge.destination] {
			return true
		}

		if !visited[edge.destination] {
			if dfs(graph, visited, edge.destination, stack) {
				return true
			}
		}
	}
	
	// important to clean up recursion STACK
	// TRICKY !
	stack[current] = false
	return false
}

Directed Graph + No Cycle Topological Sort

func topoSortUtil(graph map[int][]edge, visited []bool, current int, stack *[]int) {
	visited[current] = true

	// visited neighbours
	for _, edge := range graph[current] {
		if !visited[edge.destination] {
			topoSortUtil(graph, visited, edge.destination, stack)
		}
	}

	// PUSH ONTO STACK
	// Simple but Tricky
	*stack = append(*stack, current)
}

KosaRaju Algo – SCC

func kosaRajuAlgo(graph map[int][]edge, visited []bool, vertices []int) {

	// 1. apply topological sort to get order of vertices in stack
	stack := []int{}
	topoSort(graph, visited, vertices, &stack)

	// 2. Transpose graph
	transpose := map[int][]edge{}
	for vertex, edges := range graph {
		visited[vertex] = false
		for _, e := range edges {
			visited[e.destination] = false
			transpose[e.destination] = append(transpose[e.destination], edge{
				source:      e.destination,
				destination: e.source,
			})
		}
	}

	// 3. Pop all item from stack and apply dfs of each vertex to get list of SCC
	// in reverse order as we are using stack
	for j := len(stack) - 1; j >= 0; j -= 1 {
		if !visited[stack[j]] {
			dfs(transpose, visited, stack[j])
			fmt.Println()
		}
	}
}

Tarjan Algorithm – Bridge

  • dt : discovery time
  • low: lowest discovery among neighbours

func tarjanAlgo(graph map[int][]edge, visited []bool, dt []int, low []int, current int, parentVertex int, time int) {
	time++
	visited[current] = true
	dt[current] = time
	low[current] = dt[current]

	for _, edge := range graph[current] {
		// ignore parent
		if edge.destination == parentVertex {
			continue
		}

		// non visited not
		// regular case
		if !visited[edge.destination] {
			tarjanAlgo(graph, visited, dt, low, edge.destination, current, time)
			// update low
			low[current] = min(low[current], low[edge.destination])

			if dt[edge.source] < low[edge.destination] {
				fmt.Printf("Bridge edge : %d-%d\n", edge.source, edge.destination)
			}
		}

		if visited[edge.destination] {
			// update low using dt of neighbour
			// TRICKY !
			low[current] = min(low[current], dt[edge.destination])
		}
	}
}

Tarjan Algorithm – Articulation Point

  • dt : discovery time
  • low: lowest discovery among neighbours
  • ancestor : vertex visited earlier before reaching to current vertex
  • #children
func tarjanAlgo(graph map[int][]edge, visited []bool, dt []int, low []int, current int, parentVertex int, time int) {
	time++
	visited[current] = true
	dt[current] = time
	low[current] = dt[current]
	children := 0

	for _, edge := range graph[current] {
		// ignore parent
		if edge.destination == parentVertex {
			continue
		}

		// non visited not
		// regular case
		if !visited[edge.destination] {
		  children++
			tarjanAlgo(graph, visited, dt, low, edge.destination, current, time)
			// update low
			low[current] = min(low[current], low[edge.destination])

      // TRICKY
      // 1. check <= equal to condition here
      // a) cycle  u-> v with backedge
      //      dt[edge.source]==low[edge.destination]
      // b) bridge u-> v
      //      dt[edge.source] <low[edge.destination]

			if dt[edge.source] <= low[edge.destination] && parent != -1 {
				fmt.Printf("AP : %d\n", current)
			}
		}

		if visited[edge.destination] {
			// update low using dt of neighbour
			// TRICKY !
			low[current] = min(low[current], dt[edge.destination])
		}

    if children > 1 && parent == -1 {
      fmt.Printf("AP : %d\n", current)
      // articulation point found i.e. current
    }
	}
}

Visit https: https://codeandalgo.com for more such contents

Leave a Reply

Your email address will not be published. Required fields are marked *