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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值