blog bg

July 10, 2024

Implementing a Priority Queue in Go: A Step-by-Step Guide

Share what you learn in this blog to prepare for your interview, create your forever-free profile now, and explore how to monetize your valuable knowledge.

Priority queues are fundamental data structures that allow efficient access to the highest (or lowest) priority element. They are used in various applications like scheduling, simulation, and algorithm optimization. In this blog, we'll explore how to implement a priority queue in Go (Golang) using a heap, which is the most efficient underlying data structure for this purpose.

 

Understanding the Priority Queue

A priority queue is a type of queue where each element is associated with a priority, and elements are dequeued in order of their priority. The most common implementation of a priority queue is a binary heap, which can be either a min-heap (smallest element first) or a max-heap (largest element first).

 

Setting Up the Project

First, create a new directory for your project and initialize a Go module:

mkdir priorityqueue
cd priorityqueue
go mod init priorityqueue

 

Defining the Priority Queue

We'll start by defining the data structures. A heap can be represented as a slice of integers, but for a priority queue, we'll typically use a struct to store elements with their associated priorities.

 

package priorityqueue

type Item struct {
    Value    interface{} // The value of the item; arbitrary data
    Priority int         // The priority of the item
}

type PriorityQueue struct {
    items []Item
}

 

Implementing the Heap Methods

The key operations for a heap are Push, Pop, and Peek. Let's implement these for our priority queue.

 

Push Operation

The Push method adds a new item to the heap and then ensures the heap property is maintained by "bubbling up" the new item to its correct position.

 

func (pq *PriorityQueue) Push(item Item) {
    pq.items = append(pq.items, item)
    pq.bubbleUp(len(pq.items) - 1)
}

func (pq *PriorityQueue) bubbleUp(index int) {
    for {
        parentIndex := (index - 1) / 2
        if index == 0 || pq.items[parentIndex].Priority <= pq.items[index].Priority {
            break
        }
        pq.items[parentIndex], pq.items[index] = pq.items[index], pq.items[parentIndex]
        index = parentIndex
    }
}

 

Pop Operation

The Pop method removes and returns the highest-priority item (for a min-heap, this is the smallest item). After removal, the last item in the heap is moved to the root, and the heap property is restored by "bubbling down" this item.

func (pq *PriorityQueue) Pop() Item {
    if len(pq.items) == 0 {
        panic("cannot pop from an empty priority queue")
    }
    minItem := pq.items[0]
    lastItem := pq.items[len(pq.items) - 1]
    pq.items = pq.items[:len(pq.items) - 1]
    if len(pq.items) > 0 {
        pq.items[0] = lastItem
        pq.bubbleDown(0)
    }
    return minItem
}

func (pq *PriorityQueue) bubbleDown(index int) {
    lastIndex := len(pq.items) - 1
    for {
        leftChildIndex := 2*index + 1
        rightChildIndex := 2*index + 2
        if leftChildIndex > lastIndex {
            break
        }
        minIndex := leftChildIndex
        if rightChildIndex <= lastIndex && pq.items[rightChildIndex].Priority < pq.items[leftChildIndex].Priority {
            minIndex = rightChildIndex
        }
        if pq.items[index].Priority <= pq.items[minIndex].Priority {
            break
        }
        pq.items[index], pq.items[minIndex] = pq.items[minIndex], pq.items[index]
        index = minIndex
    }
}

 

Peek Operation

The Peek method returns the highest-priority item without removing it from the heap

func (pq *PriorityQueue) Peek() Item {
    if len(pq.items) == 0 {
        panic("cannot peek from an empty priority queue")
    }
    return pq.items[0]
}

 

Testing the Priority Queue

Create a main.go file to test your priority queue implementation:

package main

import (
    "fmt"
    "priorityqueue"
)

func main() {
    pq := &priorityqueue.PriorityQueue{}

    pq.Push(priorityqueue.Item{Value: "task 1", Priority: 3})
    pq.Push(priorityqueue.Item{Value: "task 2", Priority: 1})
    pq.Push(priorityqueue.Item{Value: "task 3", Priority: 2})

    fmt.Println("Top priority item:", pq.Peek()) // Should print "task 2"

    for len(pq.items) > 0 {
        item := pq.Pop()
        fmt.Println("Processing item:", item.Value)
    }
}

 

Conclusion

In this blog, we implemented a priority queue in Go using a heap. We covered the essential operations: Push, Pop, and Peek, and demonstrated how to maintain the heap property during these operations. This priority queue can be used in various applications where task scheduling or prioritization is required.

Feel free to expand this implementation by adding features like custom comparison functions or making the priority queue thread-safe for concurrent use

 

324 views

Please Login to create a Question