跳表是一种用来增加元素查找速度的数据结构。他是由一条有序的单链表演变而成。
他把单链表由一层变为了多层,越往上,元素数量越少。每个在单链表上的元素,都有一个level,level等于几,就从下往上占几层。这个level是有一个算法随机生成的。(实际每个元素都只有一个,占几层是通过切片指针来实现的)
当进行查找的时候,从头节点开始,往右往下查找元素(如图查找117 118 (图是盗的))。
首先是跳表的架构
//跳表的结构
//节点的value
type SkipValue struct {
Score int64
Value interface{}
}
//节点
type skipListNode struct {
next []*skipListNode //指向下一个节点的指针切片 下标对应的是相应的level
prev *skipListNode
Value *SkipValue
}
//跳表的入口
type SkipList struct {
header, tail *skipListNode //头 尾
findcount int
count int
level int //层数
}
type SkipListIterator struct {
list *SkipList
node *skipListNode
}
下图头节点是0 头节点里面有一个next的指针切片,next[0]指向value为1的节点(即指向第0层的下一个元素1),next[1]指向value为2的节点(指向第1层的下一个元素1),next[2]指向value为12的节点,next[3]指向value为12的节点,next[4]指向value为12的节点,next[5]指向nil
该跳表的实现与别的略有不同,这边value最大的尾节点是29,没有占了所有高度
单看前几层 结构图如下
当需要插入一个元素的时候,首先需要查找到他每一层的前驱节点
代码如下
var update [maxLevel]*skipListNode
x := sl.header //头节点地址
for i := sl.level - 1; i >= 0; i-- {
for x.next[i] != nil && x.next[i].Value.Compare(value) < 0 {
x = x.next[i]
}
update[i] = x
}
参照图片,假如需要插入元素15的时候,同时假设他的level为2
先是i=3-1=2,x.next[2]是头节点的指针切片的第3个值,即指向21的指针,x.next[i].Value 的值为21,比15大,不进入下一个for。然后把头节点地址复制给update[i](这边会把每一层的前驱节点都复制到update里,但是后面会根据具体的生成的level来进行赋值)。
继续 i = 1,x.next[1]指向7,x.next[i].Value = 7,7比15小,进入for,然后x指向7,7里的next[1]指向21,21比15大,出循环,同时把7的地址存到update[1]里。
继续i=0,此时x是7,x.next[0]指向14,x.next[i].Value = 14,14比15小,进入for,然后x指向14,14里的next[0]指向21,21比15大,出循环,同时把14的地址存到update[0]里。
此时update[0]是指向14的节点,update[1]是指向7的节点,update[2]是指向-1的节点,不难看出,如果15的level是3,则update[i]存的都是15节点每一层对应的前一个节点,但是此时level为2,所有我们只需要update[0]和update[1]就够了。此时便有了以下代码
level := randomLevel()//随机生成的level
x = newskipListNode(level, value)//新生成的节点
for i := 0; i < level; i++ { //level为2 则只是对update[0],update[1]进行了复制
//新生成节点的指针切片对应层数的值 = 为前驱节点的指针切片对应层数的值
//比如第二层,前驱节点是7,7的next[2]指向21,此时把指向21的指针复制给新的节点,让新的节点指向21
x.next[i] = update[i].next[i]
//update[i]指的是前驱节点地址,将前驱节点的指针切片的第i层变为指向新增的节点
update[i].next[i] = x
}
完整的新增节点代码如下
func (sl *SkipList) Insert(value *SkipValue) int {
var update [maxLevel]*skipListNode
x := sl.header
for i := sl.level - 1; i >= 0; i-- {
for x.next[i] != nil && x.next[i].Value.Compare(value) < 0 {
x = x.next[i]
}
update[i] = x
}
if x.next[0] != nil && x.next[0].Value.Compare(value) == 0 { //update
x.next[0].Value = value
return 0
}
level := randomLevel()
if level > sl.level {
for i := sl.level; i < level; i++ {
update[i] = sl.header
}
sl.level = level
}
x = newskipListNode(level, value)
for i := 0; i < level; i++ {
x.next[i] = update[i].next[i]
update[i].next[i] = x
}
//形成一个双向链表
if update[0] != sl.header {
x.prev = update[0]
}
if x.next[0] != nil {
x.next[0].prev = x
} else {
sl.tail = x
}
sl.count++
return 1
}
查询代码如下
func (sl *SkipList) find(value *SkipValue) *skipListNode {
x := sl.header
for i := sl.level - 1; i >= 0; i-- {
for x.next[i] != nil && x.next[i].Value.Compare(value) < 0 {
sl.findcount++
x = x.next[i]
}
}
return x
}
func (sl *SkipList) Find(value *SkipValue) *SkipValue {
x := sl.find(value)
if x.next[0] != nil && x.next[0].Value.Compare(value) == 0 {
return x.next[0].Value
}
return nil
}
应用场景:
存放交易所的卖单和买单
当存放买单时,由于价格需要从高到低取,当取数据时,从跳表的尾部向前取(该跳表是双向链表)
for data := it.Last(); data != nil; data = it.Prev() {
t := Tick{Price: value.price, Amount: value.amount}
info.Buy = append(info.Buy, t)
}
当存放卖单时,价格由低到高排列,当取数据时,从头部开始取
for data := it.First(); data != nil; data = it.Next() {
t := Tick{Price: value.price, Amount: value.amount}
info.Sell = append(info.Sell, t)
}
当插入订单时(通过价格来定位)
//订单未完成
//把订单加入到orderlist
item := &SkipValue{Score: order.GetPrice()}
var value *SkipValue
if isbuy {
value = match.buy.Find(item)
} else {
value = match.sell.Find(item)
}
var orderlist *OrderList
if value == nil { //new OrderList
orderlist = NewOrderList(order.GetPrice())
item.Value = orderlist
if isbuy {
match.buy.Insert(item)
} else {
match.sell.Insert(item)
}
} else {
orderlist = value.Value.(*OrderList)
}
match.add(orderlist, order)