之前看某个视频或者书总结的,具体是哪个给忘记了。。感谢前人栽树,我这里只是简单实现和使用
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
}
本文介绍了跳表的实现原理,包括其查找和插入操作的时间复杂度,并展示了如何使用跳表维护可用服务器列表。在解决找到处理最多请求的服务器问题时,结合了优先队列来管理按结束时间排序的任务,利用跳表的特性高效查找和更新服务器。通过这种方式,能够快速找到满足条件的服务器,提高了调度效率。

590

被折叠的 条评论
为什么被折叠?



