Sling Academy
Home/Golang/Maps as Adjacency Lists: Building Graph Structures in Go

Maps as Adjacency Lists: Building Graph Structures in Go

Last updated: November 24, 2024

In graph theory, graphs are data structures that consist of a finite set of vertices (or nodes) and a set of edges that connect them. One efficient way to represent graphs is using adjacency lists. This method stores a list of neighbors for each vertex, which can be useful for operations like finding neighbors and traversing a graph. In Go (Golang), maps can be leveraged to implement adjacency lists due to their simplicity and performance benefits. Let's explore how we can build graph structures using maps as adjacency lists.

Basic Graph Structure

We will start by creating a basic representation of a graph using a map in Go, where the keys represent each vertex, and the corresponding values are slices that list adjacent vertices.

package main

import "fmt"

func main() {
    // Create a graph with vertices and edges
    graph := make(map[string][]string)

    // Add edges
    graph["A"] = []string{"B", "C"}
    graph["B"] = []string{"A", "D"}
    graph["C"] = []string{"A", "D"}
    graph["D"] = []string{"B", "C"}

    // Print the graph representation
    fmt.Println(graph)
}

In this code, we define a simple graph with vertices A, B, C, and D where each vertex is connected to one or more other vertices. The map effectively serves as the adjacency list representation of this graph.

Intermediate Operations

Next, we'll extend this basic structure to include some common graph operations such as adding and removing vertices or edges and checking for connectivity.

package main

import "fmt"

func addEdge(graph map[string][]string, v1, v2 string) {
    graph[v1] = append(graph[v1], v2)
    graph[v2] = append(graph[v2], v1)
}

func removeEdge(graph map[string][]string, v1, v2 string) {
    remove := func(s []string, v string) []string {
        for i, val := range s {
            if val == v {
                return append(s[:i], s[i+1:]...)
            }
        }
        return s
    }
    graph[v1] = remove(graph[v1], v2)
    graph[v2] = remove(graph[v2], v1)
}

func hasEdge(graph map[string][]string, v1, v2 string) bool {
    for _, v := range graph[v1] {
        if v == v2 {
            return true
        }
    }
    return false
}

func main() {
    graph := make(map[string][]string)
    addEdge(graph, "A", "B")
    addEdge(graph, "A", "C")
    fmt.Println("Has Edge A-B:", hasEdge(graph, "A", "B")) // true
    removeEdge(graph, "A", "B")
    fmt.Println("Has Edge A-B:", hasEdge(graph, "A", "B")) // false
}

This example introduces three functions: `addEdge` to add a connection between two vertices, `removeEdge` to remove a connection, and `hasEdge` to check if a connection exists. These will help in managing the graph's structure dynamically.

Advanced Graph Operations

Finally, let’s implement some advanced operations such as finding the shortest path using Breadth-First Search (BFS) and checking for cycles with Depth-First Search (DFS).

package main

import (
    "fmt"
    "container/list"
)

func bfs(graph map[string][]string, start, target string) bool {
    // Create a queue for BFS
    queue := list.New()
    queue.PushBack(start)

    // Track visited vertices
    visited := make(map[string]bool)
    visited[start] = true

    // Perform BFS
    for queue.Len() > 0 {
        current := queue.Remove(queue.Front()).(string)
        if current == target {
            return true
        }

        for _, neighbor := range graph[current] {
            if !visited[neighbor] {
                visited[neighbor] = true
                queue.PushBack(neighbor)
            }
        }
    }
    return false
}

func dfsUtil(graph map[string][]string, v string, visited map[string]bool, parent string) bool {
    visited[v] = true
    for _, neighbor := range graph[v] {
        if !visited[neighbor] {
            if dfsUtil(graph, neighbor, visited, v) {
                return true
            }
        } else if neighbor != parent {
            // A cycle detected through a back edge
            return true
        }
    }
    return false
}

func hasCycle(graph map[string][]string) bool {
    visited := make(map[string]bool)
    for v := range graph {
        if !visited[v] {
            if dfsUtil(graph, v, visited, "") {
                return true
            }
        }
    }
    return false
}

func main() {
    // Define an example graph
    graph := map[string][]string{
        "A": {"B", "C"},
        "B": {"A", "D"},
        "C": {"A", "D"},
        "D": {"B", "C"},
    }

    // Check for paths
    fmt.Println("Path A->D:", bfs(graph, "A", "D")) // true
    fmt.Println("Graph has cycle:", hasCycle(graph)) // true
}

The example above shows how to find the shortest path between two nodes using BFS and how to detect if a cycle exists in the graph using DFS. These operations are crucial for many graph-based algorithms in real-world applications.

Using maps as adjacency lists in Go provides a clear and efficient approach to working with graph data structures, offering various ways to manipulate and traverse complex networks.

Next Article: Using Maps to Implement Simple In-Memory Databases in Go

Previous Article: Creating Bi-Directional Maps in Go for Reversible Lookups

Series: Working with Maps in Go

Golang

Related Articles

You May Also Like

  • How to remove HTML tags in a string in Go
  • How to remove special characters in a string in Go
  • How to remove consecutive whitespace in a string in Go
  • How to count words and characters in a string in Go
  • Relative imports in Go: Tutorial & Examples
  • How to run Python code with Go
  • How to generate slug from title in Go
  • How to create an XML sitemap in Go
  • How to redirect in Go (301, 302, etc)
  • Using Go with MongoDB: CRUD example
  • Auto deploy Go apps with CI/ CD and GitHub Actions
  • Fixing Go error: method redeclared with different receiver type
  • Fixing Go error: copy argument must have slice type
  • Fixing Go error: attempted to use nil slice
  • Fixing Go error: assignment to constant variable
  • Fixing Go error: cannot compare X (type Y) with Z (type W)
  • Fixing Go error: method has pointer receiver, not called with pointer
  • Fixing Go error: assignment mismatch: X variables but Y values
  • Fixing Go error: array index must be non-negative integer constant