Fundamentals 32 min read

Deep Dive into Golang Algorithms: A Practical Go Language Tutorial

This tutorial walks through implementing core algorithms and data structures in Go, covering language basics, arrays, slices, linked lists, stacks, queues, trees, graphs, sorting and searching techniques, as well as dynamic programming and greedy strategies, with concrete code examples and performance considerations.

Golang Shines
Golang Shines
Golang Shines
Deep Dive into Golang Algorithms: A Practical Go Language Tutorial

Go (Golang) is a statically typed, compiled language praised for its concise syntax and strong concurrency support. The "Go-Algorithm Learning Golang Edition" project demonstrates how to implement a wide range of algorithms in Go, helping learners deepen their algorithmic understanding while improving Go programming skills.

1. Go Language Basics and Features

Go offers a simple syntax, garbage collection, and built‑in concurrency primitives (goroutine and channel). Its standard library includes packages such as net/http and encoding/json, which simplify network programming and data handling.

2. Implementing Algorithms in Go

Algorithm implementation starts with basic loops and recursion, then progresses to advanced concurrent designs that leverage Go’s goroutine model. Examples include classic sorting and searching algorithms and parallel versions that use channels for coordination.

3. Performance Optimisation

Optimising Go code focuses on reducing memory allocations, scheduling goroutines efficiently, and exploiting compiler optimisations to lower runtime latency.

4. Fundamental Data Structures

Arrays vs. slices : arrays have fixed length and fast index access; slices are dynamic, built on arrays, and support automatic growth, at the cost of extra bookkeeping.

Linked list implementation using a struct:

type Node struct {
    value int
    next  *Node
}

func insertNode(head *Node, newValue int, position int) *Node {
    newNode := &Node{value: newValue}
    if position == 0 {
        newNode.next = head
        return newNode
    }
    current := head
    for i := 0; current != nil && i < position-1; i++ {
        current = current.next
    }
    if current == nil {
        panic("position out of bounds")
    }
    newNode.next = current.next
    current.next = newNode
    return head
}

Stack using a slice:

type Stack struct {
    items []int
}

func (s *Stack) Push(item int) { s.items = append(s.items, item) }
func (s *Stack) Pop() int {
    if s.IsEmpty() { panic("Stack is empty") }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item
}
func (s *Stack) IsEmpty() bool { return len(s.items) == 0 }
func (s *Stack) Peek() int {
    if s.IsEmpty() { panic("Stack is empty") }
    return s.items[len(s.items)-1]
}

Queue using a slice:

type Queue struct { items []int }
func (q *Queue) Enqueue(item int) { q.items = append(q.items, item) }
func (q *Queue) Dequeue() int {
    if q.IsEmpty() { panic("Queue is empty") }
    item := q.items[0]
    q.items = q.items[1:]
    return item
}
func (q *Queue) IsEmpty() bool { return len(q.items) == 0 }
func (q *Queue) Size() int { return len(q.items) }

5. Tree and Graph Basics

Binary tree node definition and pre‑order traversal:

type TreeNode struct {
    Value int
    Left  *TreeNode
    Right *TreeNode
}

func (t *TreeNode) PreOrderTraversal() {
    if t != nil {
        fmt.Print(t.Value, " ")
        t.Left.PreOrderTraversal()
        t.Right.PreOrderTraversal()
    }
}

Graph represented by adjacency list:

type Graph struct {
    vertices []*Vertex
    edges    []*Edge
}

type Vertex struct { Value int; Edges []*Edge }

type Edge struct { Value int; From *Vertex; To *Vertex }

Breadth‑first search (BFS) implementation:

func (g *Graph) BFS(start *Vertex) {
    visited := make(map[*Vertex]bool)
    queue := NewQueue()
    queue.Enqueue(start)
    visited[start] = true
    for !queue.IsEmpty() {
        vertex := queue.Dequeue().(*Vertex)
        fmt.Printf("Visited %d
", vertex.Value)
        for _, edge := range vertex.Edges {
            neighbor := edge.To
            if _, ok := visited[neighbor]; !ok {
                visited[neighbor] = true
                queue.Enqueue(neighbor)
            }
        }
    }
}

6. Sorting Algorithms and Performance Analysis

Quick sort: average O(n log n), worst‑case O(n²), in‑place, unstable. Merge sort: O(n log n) time, O(n) extra space, stable. Insertion sort and selection sort: O(n²) time, O(1) space, insertion sort is stable and fast on nearly sorted data. Non‑comparison sorts (counting, radix, bucket) can achieve linear time O(n) under specific data‑range conditions.

7. Searching Algorithms

Linear search scans each element (O(n) time, O(1) space). Binary search works on sorted arrays with O(log n) time:

func binarySearch(arr []int, target int) int {
    left, right := 0, len(arr)-1
    for left <= right {
        mid := left + (right-left)/2
        if arr[mid] == target { return mid }
        if arr[mid] < target { left = mid + 1 } else { right = mid - 1 }
    }
    return -1
}

8. Hash Table and Binary Search Tree

Simple hash table with separate chaining:

type HashNode struct { key string; value interface{}; next *HashNode }

type SimpleHashTable struct { table []*HashNode; size int }

func (ht *SimpleHashTable) Hash(key string) int { return len(key) % ht.size }
func (ht *SimpleHashTable) Set(key string, value interface{}) { /* insert with chaining */ }
func (ht *SimpleHashTable) Get(key string) (interface{}, bool) { /* lookup */ }

BST search (iterative):

type TreeNode struct { val int; left, right *TreeNode }

func searchBST(root *TreeNode, key int) *TreeNode {
    for root != nil {
        if key < root.val { root = root.left }
        else if key > root.val { root = root.right }
        else { return root }
    }
    return nil
}

9. Advanced Graph Algorithms

Shortest‑path algorithms: Dijkstra (non‑negative weights) and Bellman‑Ford (handles negative weights and detects negative cycles). Pseudocode for Bellman‑Ford is provided.

Topological sorting using Kahn’s algorithm:

def topological_sort_kahn(graph):
    in_degree = {v:0 for v in graph}
    for v in graph:
        for neighbor in graph[v]:
            in_degree[neighbor] += 1
    queue = [v for v in graph if in_degree[v]==0]
    sorted_list = []
    while queue:
        vertex = queue.pop(0)
        sorted_list.append(vertex)
        for neighbor in graph[vertex]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor]==0:
                queue.append(neighbor)
    return sorted_list if len(sorted_list)==len(graph) else "Graph has a cycle"

10. Tree Traversal and Reconstruction

Depth‑first (DFS) and breadth‑first (BFS) traversals are shown with recursive and queue‑based pseudocode. Tree reconstruction from preorder and inorder sequences is demonstrated.

11. Dynamic Programming and Greedy Strategies

0‑1 knapsack DP implementation:

func knapsack(values []int, weights []int, capacity int) int {
    n := len(values)
    dp := make([][]int, n+1)
    for i := 0; i <= n; i++ { dp[i] = make([]int, capacity+1) }
    for i := 1; i <= n; i++ {
        for j := 1; j <= capacity; j++ {
            if weights[i-1] <= j {
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-weights[i-1]]+values[i-1])
            } else {
                dp[i][j] = dp[i-1][j]
            }
        }
    }
    return dp[n][capacity]
}

Longest Common Subsequence (LCS) DP:

func longestCommonSubsequence(text1, text2 string) int {
    m, n := len(text1), len(text2)
    dp := make([][]int, m+1)
    for i := 0; i <= m; i++ { dp[i] = make([]int, n+1) }
    for i := 1; i <= m; i++ {
        for j := 1; j <= n; j++ {
            if text1[i-1] == text2[j-1] {
                dp[i][j] = dp[i-1][j-1] + 1
            } else {
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
            }
        }
    }
    return dp[m][n]
}

Greedy algorithms are introduced with examples such as minimum spanning tree construction (Kruskal and Prim) and Huffman coding.

The tutorial provides concrete Go code, algorithmic analysis, and performance considerations, enabling readers to implement and evaluate a broad spectrum of algorithms in Go.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Godynamic programmingData Structuresalgorithmssortinggraph algorithmsgreedy
Golang Shines
Written by

Golang Shines

We share daily the latest Golang technical articles, practical resources, language news, tutorials, and real-world projects to help everyone learn and improve.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.