[go]跳表的实现

go语言-数据结构之跳表 跳表go语言初步实现 阅读详情

之前看某个视频或者书总结的,具体是哪个给忘记了。。感谢前人栽树,我这里只是简单实现和使用

package skip_list

import (
	"math"
	"math/rand"
	"time"
)

const (
	MaxLevel    = 18 //最大层数
	Probability = 1 / math.E //每一个节点是否可以向上的概率基数
)

type (
	// Comparable 大于返回正数,小于返回负数,等于返回0
	Comparable interface {
		CompareTo(Comparable) int
	}
	
	KV struct {
		K Comparable
		V interface{}
	}

	Element struct {
		next []*Element
		kv   *KV
	}

	SkipList struct {
		node          Element     //第一个为头结点,不存值
		maxLevel      int         //最大层数
		len           int         //跳表长度
		randSource    rand.Source //生成随机数
		probTable     []float64   //用于查询每一层生成索引的概率
		prevNodeCache []*Element  // 用于保存查询一个值时经过每一层时的最后一个节点
	}
)

func NewKV(key Comparable, value interface{}) *KV {
	return &KV{
		K: key,
		V: value,
	}
}

func NewSkipList() *SkipList {
	return &SkipList{
		node:          Element{next: make([]*Element, MaxLevel)},
		maxLevel:      MaxLevel,
		randSource:    rand.NewSource(time.Now().UnixNano()),
		probTable:     probabilityTable(Probability, MaxLevel),
		prevNodeCache: make([]*Element, MaxLevel),
	}
}

// Len 获取长度
func (sList *SkipList) Len() int {
	return sList.len
}

// Get 获取数据 如果没有相等的数据,返回它的下一个元素(也可能是nil),bool仍是false
func (sList *SkipList) Get(k Comparable) (*KV, bool) {
	prev := &sList.node //重要 从头结点开始遍历
	var node *Element
	for now := sList.maxLevel - 1; now >= 0; now-- {
		node = prev.next[now]
		for node != nil && k.CompareTo(node.kv.K) > 0 {
			prev = node
			node = node.next[now]
		}
	}
	//指针已经到达第一层
	if node != nil {
		return node.kv, k.CompareTo(node.kv.K) == 0
	}
	return nil, false
}

// PrevNodeCache 每一层下来的节点所组成的切片
func (sList *SkipList) PrevNodeCache(k Comparable) []*Element {
	prev := &sList.node
	var node *Element
	for now := sList.maxLevel - 1; now >= 0; now-- { //在每一层进行搜索合适的位置然后向下一层
		node = prev.next[now]
		for node != nil && k.CompareTo(node.kv.K) > 0 { //发现没到就继续往右走
			prev = node //保留着向下的通道
			node = node.next[now]
		}
		sList.prevNodeCache[now] = prev //存放每层搜索的最后一个节点
	}
	return sList.prevNodeCache
}

// Delete 删除数据
func (sList *SkipList) Delete(key Comparable) {
	prev := sList.PrevNodeCache(key)
	if ele := prev[0].next[0]; ele != nil && key.CompareTo(ele.kv.K) == 0 { //如果找到了那个k
		for k, v := range ele.next { //把连接key的节点的next跳过key
			prev[k].next[k] = v
		}
		sList.len--
	}
}

func newElement(kv *KV, level int) *Element {
	return &Element{
		kv:   kv,
		next: make([]*Element, level),
	}
}

// Put 插入数据
func (sList *SkipList) Put(kv *KV) {
	prev := sList.PrevNodeCache(kv.K)                                        //保存着每层最后一个小于目标值得节点
	if ele := prev[0].next[0]; ele != nil && kv.K.CompareTo(ele.kv.K) == 0 { //找到就替换值
		ele.kv.V = kv.V
		return
	}
	//没找到就新创建一个
	element := newElement(kv, sList.randomLevel()) //随机分配一个层数
	//加入一个索引层
	for k := range element.next { //把k层的element都插入
		element.next[k] = prev[k].next[k]
		prev[k].next[k] = element
	}
	sList.len++
}

// GetMax 获取最后一个元素
func (sList *SkipList) GetMax() (kv *KV) {
	node := &sList.node
	for now := sList.maxLevel - 1; now >= 0; now-- {
		for node.next[now] != nil {
			node = node.next[now]
		}
	}
	return node.kv
}

// GetMin 获取第一个元素
func (sList *SkipList) GetMin() (kv *KV) {
	return sList.node.next[0].kv
}

//计算每一层生成索引的概率-> 生成一张概率表
func probabilityTable(probability float64, level int) (ret []float64) {
	for i := 1; i <= level; i++ {
		ret = append(ret, math.Pow(probability, float64(i-1)))
	}
	return ret
}

//对每一层进行概率评估
func (sList *SkipList) randomLevel() int {
	n := float64(sList.randSource.Int63()) / math.MaxInt64 //随机概率
	level := 1                                             //第1层始终有索引
	for level < sList.maxLevel && n < sList.probTable[level-1] {
		level++
	}
	return level
}

跳表查找数据和插入数据可以达到O(logn)的时间复杂度,内部基于排序,类似二分查找。
1606. 找到处理最多请求的服务器
以这个题为例,需要使用优先队列维护一个处理任务的服务器的列表(根据结束时间排序),使用跳表维护一个可用服务器的列表(因为题目需要按照id来选择>=id的第一个服务器,所以借用跳表内部排序的特点)

type pair1606 struct {
	end, id int
}
type PH struct {
	pairs []pair1606
}

func (p *PH) Len() int           { return len(p.pairs) }
func (p *PH) Less(i, j int) bool { return p.pairs[i].end < p.pairs[j].end }
func (p *PH) Swap(i, j int)      { p.pairs[i], p.pairs[j] = p.pairs[j], p.pairs[i] }
func (p *PH) Push(v interface{}) { p.pairs = append(p.pairs, v.(pair1606)) }
func (p *PH) Pop() interface{} {
	v := p.pairs[len(p.pairs)-1]
	p.pairs = p.pairs[:len(p.pairs)-1]
	return v
}

type M int

func (m M) CompareTo(comparable skip_list.Comparable) int {
	c, _ := comparable.(M)
	return int(m - c)
}

func busiestServers(k int, arrival []int, load []int) (ret []int) {
	var max int
	sList := skip_list.NewSkipList() //存放可用的服务器的跳表,按照id从小到大
	cnts := make([]int, k)           //统计服务器处理任务次数
	work := &PH{}                    //存放正在处理任务的服务器的优先队列,按照完成时间从小到大
	for i := 0; i < k; i++ {
		heap.Push(work, pair1606{id: i})
	}
	for i, arrivalTime := range arrival {
		for work.Len() > 0 && work.pairs[0].end <= arrivalTime { //先把所有已经完成任务的服务器放到slist中
			p := heap.Pop(work).(pair1606)
			sList.Put(skip_list.NewKV(M(p.id), p.end))
		}
		if sList.Len() == 0 { //如果没有可以使用的服务器就抛弃
			continue
		}
		var id int
		if kv, _ := sList.Get(M(i % k)); kv != nil { //如果能找到i%k或者它的下一个就直接使用,否则使用最小的那个
			id = int(kv.K.(M))
		} else {
			id = int(sList.GetMin().K.(M))
		}
		cnts[id]++
		if cnts[id] > max {
			max = cnts[id]
		}
		sList.Delete(M(id))       //记得从slist中删除已经被使用的服务器
		heap.Push(work, pair1606{ //加入到正在工作的服务器的优先队列中,完成时间是当前处理时间加上处理所需要的时间
			end: arrivalTime + load[i],
			id:  id,
		})
	}
	ret = make([]int, 0, len(cnts))
	for i, v := range cnts {
		if v == max {
			ret = append(ret, i)
		}
	}
	return ret
}

Altium Designer20 PCB板子绘制 版层 top layer 顶层 bottom layer 底层 设置板子大小 布局 过孔 top layer层连到bottom layer层。通过via连接。 设置线宽 在design rules里可以设置线宽。 右键new rule 可以新增线宽,电源设置成20mil 还可以设置很多其他的线宽 修改安全距离 安全距离太短容易出现毛刺,然后短路,一般设置成20mil 修改元器件旋转角度 修改rotation step,每次旋转45°。 修改光标大小 下面的cursor type是修改光标 阅读详情

相关推荐

VMware报错系列:虚拟机配置文件(.vmx)损坏或丢失---修复

虚拟机配置文件(.vmx)损坏或丢失---修复

weixin_54626591的博客 6960

golang/java实现跳表的数据结构

最近在写一款中间件,类似于redis的中间件,我准备用golang语言来编写,目前已经实现了redis的大部分数据结构,比如string、hash、set、list,而开始实现zset的时候发现要使用跳表,在网上实现跳表数据结构感觉都不太合适,所以就自己来实现了一个,我也是瞅着空闲的时间来实现了一把,我也不知道性能如何,还没来得及测试,欢迎大家在此基础上进行修正和讨论; 跳表(Skip List)是一种概率性的动态数据结构,通常用于实现有序集合(如集合、映射等)的查找、插入和删除操作。它能够在平均情况下提

JAVA技术爱好者 1181

ECharts地图-自定义32.rar

图表效果及代码实现讲解链接:https://blog.csdn.net/zhangjiujiu/article/details/142059076

GO实现跳跃表

跳跃表(skiplist)是一种有序的数据结构,它通过建立多层"索引",从而达到快速访问节点的目的. 跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。下面是一个跳表结构的示意图,其实跳表就是一个二维链表,只有最底层的链表中存着数据,其他层都是在第一层基础上建立的索引,越靠近上层,节点之间的跨度就越大,跳表的查询范围也越大。依靠着这些索引,跳表可以实现接近二分查找的查找效率。

qq_49723651的博客 1112

Go语言实现跳表(SkipList)

跳表(skiplist)在redis/levelDB中属于核心数据结构,我简单粗暴的用Golang实现了下。 就我的简单理解来说,就一个普通的链表,在insert时,通过Random_level(),把一层变成很多层, 越上数据越小,跨度越大。 查找时从上往下找,如果在一层没找到,在下一层继续时,以此节点作为起始, 继续查找,是一种用空间换时间的方式。

拙言的专栏 3484

go语言实现跳表

go语言实现跳表

qq_42070135的博客 363

Golang实现跳表:动态实现详解

本文深入讲解了如何使用 Golang 动态实现跳表(Skip List),包括节点结构设计、随机层数生成原理、增删查操作流程及层级指针调整。通过逐层扫描与随机化索引,跳表在保持有序链表特性的同时,实现了平均O(logn)的查找、插入和删除效率,并附带完整代码示例,帮助读者快速理解跳表原理与应用

qq_70638240的博客 1907

redis跳表golang实现(1)

欢迎关注公众号《后台开发探索之旅》。 有序集合 日常开发经常需要对数据进行排序,针对不同的场景,采用特定的排序方法,比如: (1)数组排序:将数值存入数组中,对数组进行冒泡、快排等排序方法,得到一个有序数组 (2)二叉搜索:构造二叉平衡树,从根结点开始向左向右搜索,每次查找规模减半,最终找到目标节点 (3)最小根堆:同样将数值存入数组中,对数组进行堆排序,保持最小值始终在堆顶,从而得到...

猫哥 713

跳表的原理与实现 Golang 版

跳表的原理与实现 Golang 版 有时候,我们会说,在计算机世界里,其实只有两种数据结构,一个是数组一个是链表。原因是其他的数据结构都是基于这两种数据结构做的扩展。 数组和链表的优缺点实在是非常的明显。数组可以高效查找,按照下标索引,但是很难进行高效的删除和扩容。链表的优缺点正好相反。很多时候,我们不用链表的原因就是因为它没有办法快速查找和插入。但是,如果我们可以对它进行改造,让它可以使用类似二分查找的方法就很棒了。这个改造的结果就是 SkipList。 跳表的原理 SkipList 的原理是给链表中的某

ANDYJWW的博客 329

Golang实现redis系列-(4)实现跳表容器

实现跳表容器跳表sortedset代码border.goskiplist.gosortedset.go 跳表 golang数据结构篇之跳表 关于跳表先吃透inset代码即可 sortedset redis中的sortedset的类型就是跳表,其中的element有member和score,在跳表的基础上对其封装命令对应的api即可 代码 border.go package sortedset import ( "errors" "strconv" ) const ( negativeInf int

清风 662

带你彻底击溃跳表原理及其Golang实现!(内含图解)

导语|最近在看《Redis设计与实现》这本书,书中简单描述了跳表的性质和数据结构,但对它的具体实现没有详细描述。本文是基于我个人对跳表原理的深入探究,并通过golang实现了一个基础跳...

QcloudCommunity的博客 672

Go语言数据结构之跳表实现与应用

例如,对于链表1 -> 3 -> 5 -> 7 -> 9,跳表可能会构建两层索引,第一层索引为1 -> 5 -> 9,第二层索引为1 -> 9。在Go语言的数据结构大家族中,跳表(Skip List)是一种独特的数据结构,它以其高效的查找、插入和删除操作,在许多场景下展现出与传统数据结构不同的优势。3. 随机层数生成:跳表的层数是随机生成的,通过随机数来决定新插入节点的层数,一般以一定概率增加层数,比如50%的概率新节点层数加1。6. 删除操作:删除节点时,更新各层指针,跳过要删除的节点。

wire290的博客 508

golang skiplist(跳表)实现

前言 首先来了解一下跳表的概念,跳表中的表指的是链表,所以宏观上来看跳表是一种链表结构,那么它与传统的链表有什么不同呢?一般的单链表如果我们需要查找第n个元素,那么就需要从头节点开始一个一个的遍历n次,时间复杂读就是O(n),而跳表就是在单链表之上做了一些优化,使时间复杂度达到O(logn)。 在O(logn)复杂度内查找元素,我们不难想到其他的数据结构,avl树,红黑树,b树,b+树之类。但是这些树状结构有个共性就是需要在元素数量发生变化的时候保持平衡才能继续维持性能,所以就需要做一个额外的操作,旋转

phpduang的博客 543

跳表 Golang 实现

前言 第一次接触跳表就是在 16 年的时候,那时候看《Redis 设计与实现》了解到了跳表,当时还参考了其他的代码用 php 实现了一份。 这次巧了,又一次是看 redis 相关的东西又一次看到了跳表,于是我就找以前的代码,可是找不到了,那么干脆就在实现一次好了 目标 由于之前是参考代码,我觉得理解的并不是很深,尤其是为什么要用那些属性,为什么没有就不行呢?所以这次我参考了比较偏理论的文章 跳表──没听过但很犀利的数据结构,没有任何代码,事后全部靠自己思考来实现出来。 Let’s Go 先来个图 如果是自

写代码的大胖 958

golang如何实现跳表Skip List_golang跳表Skip List实现总结

为什么不用标准库,而要自己写 SkipListGo 标准库没有 SkipList,map 是哈希表实现,不支持按 key 有序遍历或范围查询(比如 “找所有大于 100 的键”);用 sync.RWMutex 锁写操作,读操作(如遍历、查找)可无锁,但需保证指针更新的原子性(靠 Go 的指针赋值天然原子)Insert 和 Delete 怎么写才不漏节点或断链跳表最常崩在更新多层指针时顺序错乱,比如先改高层 next 再改低层,中间被其他 goroutine 读到“半更新”状态,遍历就跳飞了。

qq_20690139的博客 163

跳表SkipList(go语言实现)

网上找到一个开源的代码 https://github.com/AceDarkknight/ConcurrentSkipList 数据结构 我们用 ConcurrentSkipList 这个数据结构代表整个 skip list,可以看到里面是一个包含多个 skipList 的切片。 // ConcurrentSkipList is a struct contains a slice of concurrent skip list. type ConcurrentSkipList struct {

九品神元师 655

SkipList跳表的原理以及Go语言实现

跳表的原理 跳表(Skiplist)是一个特殊的链表,相比一般的链表,有更高的查找效率,可比拟二叉查找树。跳表的查找、插入、删除时间复杂度都是O(logN)。 许多知名的开源软件中的数据结构采用了跳表这种数据结构,例如: Redis中的有序集合zset LevelDB、HBase中Memtable ApacheLucene中的TermDictionary、Posting List 跳表数据结构是由William Pugh发明的,最早出现于他在1990年发表的论文《Skip Lists: A Probab

haming123的专栏 854

快速跳表:高效、易用的Go语言跳表实现

快速跳表:高效、易用的Go语言跳表实现 项目介绍 fast-skiplist 是一个用Go语言实现跳表(Skip List)库,旨在为内存数据结构存储提供一个高效、易用且线程安全的解决方案。跳表是一种有序的数据结构,支持快速的插入、删除和查找操作,其时间复杂度均为O(log N)。fast-skiplist 不仅提供了经典跳表的所有特性,还通过一系列优化手段,使其在性能上超越了现有的其他Go语言...

gitblog_00040的博客 408

go skip list跳表实现之Redis为啥这么快之一的原因_golang skipset(4)

SkipList的设计主要是基于单链表,在单链表的技术上加多层链表数据索引,实现对链表数据的二分查找。

2401_84911119的博客 959
上一篇: [go]树状数组的实现
下一篇: [go]使用channel实现读写锁
CRAJA
博客等级 码龄6年 70粉丝 58原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值