golang高效跳表(Skiplist)数据结构实现插件库skiplist的使用

golang高效跳表(Skiplist)数据结构实现插件库skiplist的使用

skiplist是一个参考Redis zskiplist实现的Golang跳表数据结构库。跳表是一种概率平衡的数据结构,能够在O(log n)时间内完成查找、插入和删除操作。

使用方法

下面是一个完整的使用示例,展示了如何创建跳表、插入元素、遍历元素以及获取元素排名:

package main

import (
	"fmt"
	"github.com/gansidui/skiplist"
	"log"
)

// 定义User结构体,作为跳表存储的元素
type User struct {
	score float64
	id    string
}

// 实现Less方法,定义元素的比较规则
func (u *User) Less(other interface{}) bool {
	if u.score > other.(*User).score {
		return true
	}
	if u.score == other.(*User).score && len(u.id) > len(other.(*User).id) {
		return true
	}
	return false
}

func main() {
	// 创建User切片
	us := make([]*User, 7)
	us[0] = &User{6.6, "hi"}
	us[1] = &User{4.4, "hello"}
	us[2] = &User{2.2, "world"}
	us[3] = &User{3.3, "go"}
	us[4] = &User{1.1, "skip"}
	us[5] = &User{2.2, "list"}
	us[6] = &User{3.3, "lang"}

	// 创建跳表并插入元素
	sl := skiplist.New()
	for i := 0; i < len(us); i++ {
		sl.Insert(us[i])
	}

	// 遍历跳表并打印元素
	for e := sl.Front(); e != nil; e = e.Next() {
		fmt.Println(e.Value.(*User).id, "-->", e.Value.(*User).score)
	}
	fmt.Println()

	// 获取元素排名
	rank1 := sl.GetRank(&User{2.2, "list"})
	rank2 := sl.GetRank(&User{6.6, "hi"})
	if rank1 != 6 || rank2 != 1 {
		log.Fatal()
	}
	
	// 根据排名获取元素
	if e := sl.GetElementByRank(2); e.Value.(*User).score != 4.4 || e.Value.(*User).id != "hello" {
		log.Fatal()
	}
}

/* 输出:
hi --> 6.6
hello --> 4.4
lang --> 3.3
go --> 3.3
world --> 2.2
list --> 2.2
skip --> 1.1
*/

主要功能说明

  1. 插入元素:使用Insert方法将元素添加到跳表中
  2. 遍历元素:使用FrontNext方法可以顺序遍历跳表中的所有元素
  3. 获取排名GetRank方法可以获取元素在跳表中的排名
  4. 根据排名获取元素GetElementByRank方法可以根据排名获取对应的元素

比较规则

需要为存储在跳表中的类型实现Less方法,定义元素的比较规则。在上面的例子中,我们首先比较score字段,如果score相同则比较id的长度。

许可证

该项目使用MIT许可证。


更多关于golang高效跳表(Skiplist)数据结构实现插件库skiplist的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高效跳表(Skiplist)数据结构实现插件库skiplist的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 高效跳表(Skiplist)实现与使用

跳表(Skiplist)是一种概率性的有序数据结构,它通过多级索引实现平均O(log n)时间复杂度的查找、插入和删除操作,是平衡树的一种替代方案。

常用跳表库推荐

在Go生态中,有几个优秀的跳表实现库:

  1. github.com/ryszard/goskiplist - 一个稳定且功能完整的跳表实现
  2. github.com/sean-public/skiplist - 高性能并发安全跳表
  3. github.com/huandu/skiplist - 支持泛型的跳表实现

使用 huandu/skiplist 示例

下面以 huandu/skiplist 为例展示跳表的基本用法:

package main

import (
	"fmt"
	"github.com/huandu/skiplist"
)

func main() {
	// 创建一个跳表实例,键为int类型,值为string类型
	list := skiplist.New(skiplist.Int)

	// 插入元素
	list.Set(3, "数据3")
	list.Set(1, "数据1")
	list.Set(4, "数据4")
	list.Set(2, "数据2")

	// 获取元素
	if value, ok := list.GetValue(2); ok {
		fmt.Println("键2对应的值:", value) // 输出: 键2对应的值: 数据2
	}

	// 遍历所有元素
	fmt.Println("按顺序遍历跳表:")
	for elem := list.Front(); elem != nil; elem = elem.Next() {
		fmt.Printf("键: %v, 值: %v\n", elem.Key(), elem.Value)
	}

	// 范围查询
	fmt.Println("范围查询[2,4):")
	elem := list.Find(2)
	end := list.Find(4)
	for elem != nil && elem != end {
		fmt.Printf("键: %v, 值: %v\n", elem.Key(), elem.Value)
		elem = elem.Next()
	}

	// 删除元素
	list.Remove(3)
	fmt.Println("删除键3后跳表长度:", list.Len())

	// 清空跳表
	list.Init()
	fmt.Println("清空后跳表长度:", list.Len())
}

并发安全跳表实现

如果需要并发安全的跳表,可以使用 github.com/sean-public/skiplist:

package main

import (
	"fmt"
	"sync"
	
	"github.com/sean-public/skiplist"
)

func main() {
	list := skiplist.New()
	var wg sync.WaitGroup

	// 并发插入
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			list.Set(float64(i), fmt.Sprintf("value%d", i))
		}(i)
	}
	wg.Wait()

	// 并发读取
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			if val, ok := list.Get(float64(i)); ok {
				fmt.Printf("键%v的值: %v\n", i, val)
			}
		}(i)
	}
	wg.Wait()
}

自定义比较器

huandu/skiplist 支持自定义比较器:

package main

import (
	"fmt"
	"github.com/huandu/skiplist"
)

type Person struct {
	Name string
	Age  int
}

func main() {
	// 创建自定义比较器
	comparator := skiplist.LessThanFunc(func(lhs, rhs interface{}) bool {
		p1 := lhs.(Person)
		p2 := rhs.(Person)
		
		if p1.Name != p2.Name {
			return p1.Name < p2.Name
		}
		return p1.Age < p2.Age
	})

	list := skiplist.New(comparator)
	
	list.Set(Person{"Alice", 30}, "Alice的数据")
	list.Set(Person{"Bob", 25}, "Bob的数据")
	list.Set(Person{"Alice", 25}, "另一个Alice的数据")

	// 查找
	if elem := list.Get(Person{"Alice", 25}); elem != nil {
		fmt.Println("找到:", elem.Value) // 输出: 找到: 另一个Alice的数据
	}
}

性能考虑

  1. 跳表的空间复杂度为O(n),比普通链表高
  2. 对于内存敏感的场景,可以调整跳表的概率因子(P值)
  3. 在Go中,跳表通常比平衡树实现更简单且性能相当
  4. 对于读多写少的场景,考虑使用并发安全实现

跳表特别适合需要有序数据且需要高效查找、插入和删除的场景,如排行榜、范围查询等应用。

回到顶部