Golang program to implement a Skip List


Skip lists are a dynamic data structure which offer efficient insertion, search, and deletion operations. In this article, we will demonstrate the function and explore the algorithmic concepts to implement a Skip List in Go programming language. We are going to write two different examples for this demonstration. In the first example we are going to use a randomization approach and in the second example we will build the Skip List directly from randomised tower structures for quicker traversal.

Explanation

A Skip List as a data structure, maintains a sorted list of elements, to enable fast searching without the complexity of balanced trees. This is achieved through the creation of multiple lvls of linked lists with varying skip distances, helping in quicker traversal to desired elements.

Syntax

func newNode(vlue, lvl int) *node

The syntax represents a method called `newNode()` defined to accept a value and an integer to specify the tower height and return a node with its value set and an array of pointers to subsequent nodes at different levels, for the Skip List implementation.

func (sl *skipList) randomLvl() int

The syntax represents a method called `randomLvl()`defined to generate a random height for a new node in the Skip List using a probability parameter and a maximum level to determine the tower's height.

Algorithm

  • Start by initializing the Skip List with a base and a maximum level.

  • Create nodes for elements with random levels to finally form a tower-like structures.

  • Connect nodes horizontally within and vertically across levels.

  • Start searching from the upper-left corner and then move either right or down.

  • For insertion, update node links, while considering the tower height.

Example 1

In this example, we are going to implement a Skip List in go, using randomization. The structure node holds the values and linking pointers. The function insert adds elements by traversing levels, and the function search is defined to find an element using a multi-level search approach. The randomLvl() method generates tower heights. We have utilized arrays for the next pointers in each node for the creation of tower structure that directly stores the level of each node's tower and its connections with the next array. Finally in the main function, a Skip List is initialized with values. Lastly, element searches are performed.

package main
 
import (
    "fmt"
    "math/rand"
)
const (
    maxLvl = 16
	p    	= 0.5
)
type node struct {
	vlue int
	nxt  []*node
}
type skipList struct{ head *node }
func newNode(vlue, lvl int) *node {
    return &node{vlue: vlue, nxt: make([]*node, lvl)}
}
func (sl *skipList) insert(vlue int) {
    lvl := sl.randomLvl()
    newNode := newNode(vlue, lvl)
	crrent := sl.head
    for i := lvl - 1; i >= 0; i-- {
        for crrent.nxt[i] != nil && crrent.nxt[i].vlue < vlue { crrent = crrent.nxt[i] }
        newNode.nxt[i], crrent.nxt[i] = crrent.nxt[i], newNode
    }
}
func (sl *skipList) search(vlue int) bool {
    crrent := sl.head
    for i := len(crrent.nxt) - 1; i >= 0; i-- {
        	for crrent.nxt[i] != nil && crrent.nxt[i].vlue < vlue { crrent = crrent.nxt[i] }
    }
	return crrent.nxt[0] != nil && crrent.nxt[0].vlue == vlue
}
func (sl *skipList) randomLvl() int {
	lvl := 1
	for rand.Float64() < p && lvl < maxLvl { lvl++ }
	return lvl
}
func main() {
    sl := &skipList{head: newNode(0, maxLvl)}
    vlues := []int{3, 6, 9, 2, 5, 8, 1, 4, 7}
	for _, v := range vlues { sl.insert(v) }
	fmt.Print("Skip List: ")
	crrent := sl.head.nxt[0]
	for crrent != nil { fmt.Printf("%d -> ", crrent.vlue); crrent = crrent.nxt[0] }
	fmt.Println("nil")
	fmt.Println("Search 5:", sl.search(5))
	fmt.Println("Search 10:", sl.search(10))
}

Output

Skip List: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> nil
Search 5: true
Search 10: false

Example 2

In this example, we will implement a Skip List in go directly from randomized tower structures for quicker traversal. Elements are inserted through the creation of tower-like connections, and searches are performed similarly through multi-level comparisons. Randomized levels are generated in order to maintain balanced distribution. A separate linked list represents each level, and the connections are managed by the help of linked list nodes. In the main function, a Skip List is initiated and finally the element search is performed on it.

package main
import (
    "fmt"
	"math/rand"
)
const (
	maxLvl = 16
	p    	= 0.5
)
type node struct {
	vlue int
	nxt  []*node
}
type skipList struct{ head *node }
func newNode(vlue, lvl int) *node {
    return &node{vlue: vlue, nxt: make([]*node, lvl)}
}
func (sl *skipList) insert(vlue int) {
	lvl, crrent := sl.randomLvl(), sl.head
	for i := lvl - 1; i >= 0; i-- {
     	for crrent.nxt[i] != nil && crrent.nxt[i].vlue < vlue {
            crrent = crrent.nxt[i]
        }
    newNode := newNode(vlue, i+1)
    newNode.nxt[i], crrent.nxt[i] = crrent.nxt[i], newNode
    }
}
func (sl *skipList) search(vlue int) bool {
    crrent := sl.head
    for i := len(crrent.nxt) - 1; i >= 0; i-- {
    	for crrent.nxt[i] != nil && crrent.nxt[i].vlue < vlue {
           crrent = crrent.nxt[i]
        }
    }
    return crrent.nxt[0] != nil && crrent.nxt[0].vlue == vlue
}
func (sl *skipList) randomLvl() int {
    lvl := 1
    for rand.Float64() < p && lvl < maxLvl {
        lvl++
    }
    return lvl
}
func main() {
    sl := &skipList{head: newNode(0, maxLvl)}
    vlues := []int{3, 6, 9, 2, 5, 8, 1, 4, 7}
    for _, v := range vlues {
    	sl.insert(v)
    }
    fmt.Println("Skip List:")
    for crrent := sl.head.nxt[0]; crrent != nil; crrent = crrent.nxt[0] {
        fmt.Printf("%d -> ", crrent.vlue)
    }
    fmt.Println("nil")
    fmt.Println("Search 5:", sl.search(5))
    fmt.Println("Search 10:", sl.search(10))
}

Output

Skip List:
1 -> 2 -> 3 -> 5 -> 7 -> 8 -> 9 -> nil
Search 5: false
Search 10: false

Real Life Implementation

  • Game Development: Skip lists are an excellent way of retaining the sorted organization of player scores in a gaming scenario when leaderboards or top scores are available. This makes updating and obtaining the top scores easier.

  • Geospatial Indexing: Skip lists have been discovered as a potential way for building geographical indexes to assist efficient storing and retrieval of geographic data. The usage of these indexes allows for the efficient retrieval of nearby places or regions, which is important for location-centric applications such as mapping services.

Conclusion

A Skip List is a versatile data structure which blends simplicity and efficiency, making it quite appropriate for maintaining ordered data with quick search, insertion, and deletion operations. In this article, we explored the concepts to implement a Skip List in go through 2 different approaches: the first approach is more memory-efficient compared to the linked list implementation because it avoids the overhead of individual pointers for each node. The next approach provides better flexibility and dynamic resizing compared to the array approach, but on the greyer side, it consumes slightly more memory due to the overhead of individual linked list nodes.

Updated on: 18-Oct-2023

92 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements