使用Go语言实现跳表程序


跳表是一种动态数据结构,它提供高效的插入、搜索和删除操作。在本文中,我们将演示跳表的功能,并探讨使用Go语言实现跳表的算法概念。我们将为此演示编写两个不同的示例。在第一个示例中,我们将使用随机化方法,在第二个示例中,我们将直接从随机塔结构构建跳表以实现更快的遍历。

解释

跳表作为一种数据结构,维护一个已排序的元素列表,以便能够快速搜索而无需平衡树的复杂性。这是通过创建多个具有不同跳跃距离的链表级别来实现的,这有助于更快地遍历到所需的元素。

语法

func newNode(vlue, lvl int) *node

这段语法表示一个名为`newNode()`的方法,它接受一个值和一个整数来指定塔的高度,并返回一个设置了其值的节点以及一个指向不同级别后续节点的指针数组,用于跳表实现。

func (sl *skipList) randomLvl() int

这段语法表示一个名为`randomLvl()`的方法,它使用概率参数和最大级别来生成跳表中新节点的随机高度,以确定塔的高度。

算法

  • 首先使用基数和最大级别初始化跳表。

  • 为具有随机级别的元素创建节点,最终形成塔状结构。

  • 在水平方向上连接节点,并在垂直方向上跨级别连接。

  • 从左上角开始搜索,然后向右或向下移动。

  • 对于插入操作,请更新节点链接,同时考虑塔的高度。

示例1

在这个示例中,我们将使用随机化方法在Go语言中实现一个跳表。`node`结构体保存值和链接指针。`insert`函数通过遍历级别来添加元素,`search`函数使用多级搜索方法来查找元素。`randomLvl()`方法生成塔的高度。我们使用数组来表示每个节点中的下一个指针,用于创建直接存储每个节点塔的级别及其与下一个数组连接的塔结构。最后,在主函数中,初始化一个包含值的跳表。最后,执行元素搜索。

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))
}

输出

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

示例2

在这个示例中,我们将直接从随机塔结构在Go语言中实现一个跳表以实现更快的遍历。通过创建塔状连接来插入元素,并通过多级比较来执行类似的搜索。生成随机级别以保持平衡分布。单独的链表表示每个级别,连接由链表节点的帮助来管理。在主函数中,初始化一个跳表,最后在其上执行元素搜索。

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))
}

输出

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

现实生活中的应用

  • 游戏开发:跳表是保留游戏中玩家分数排序组织的绝佳方法,当存在排行榜或最高分时。这使得更新和获取最高分更容易。

  • 地理空间索引:跳表已被发现是一种构建地理索引的潜在方法,以帮助有效存储和检索地理数据。使用这些索引允许有效地检索附近的场所或区域,这对于基于位置的应用程序(例如地图服务)非常重要。

结论

跳表是一种多功能的数据结构,它融合了简单性和效率,使其非常适合维护有序数据,并进行快速搜索、插入和删除操作。在本文中,我们探讨了通过两种不同的方法在Go语言中实现跳表的概念:第一种方法比链表实现更节省内存,因为它避免了每个节点的单个指针的开销。下一种方法与数组方法相比提供了更好的灵活性和动态调整大小的功能,但另一方面,由于单个链表节点的开销,它会消耗更多的内存。

更新于:2023年10月18日

228 次浏览

启动您的职业生涯

通过完成课程获得认证

开始学习
广告