底层:跳跃链表skiplist
跳表
每个node的结构:含键和值,还有一个由多个node组成的list,就是本node在不同层指向下一个node的指针。
每次初始化时初始化最初的节点,也就是最左边的节点,它的高度就是跳表最大高度,初始每个位置指针指向null。
插入元素时首先判断该元素在集合中是否存在,从最初节点的最高位置开始寻找,向前的指针键值比要找的值大就向下移动,小就向前移动。如果不存在就插入,首先用随机函数每次随机一个数0或1,出现一次1就停止,出现0的个数就是层数。然后从初始节点的对应层开始,如果前一个节点值大,就生成一个node,令初始节点层指针指向node,node向前指向本来前面的值,如果前一个节点值小就向下移动然后继续判断,直到走完第一层,对应节点的所有向前指针都已经创建完毕了。
删除时只需要找到该节点该层谁指向它,把指针指向node前面的node即可。
java实现的跳表
跳表的node:
class SkipListNode{
public Integer value;
//指向下一个node,如果本node是5层,那么这个list大小为5,分别指向1-5层的下一个node
public ArrayList<SkipListNode> nextNodes;
public SkipListNode(Integer value) {
this.value = value;
nextNodes = new ArrayList<MySkipList.SkipListNode>();
}
}
跳表list的字段和构造方法:
class SkipList{
//最小值,跳表的起始节点,它的高度和跳表中node的最大高度一致
private SkipListNode head;
//跳表达到的最大高度
private int maxLevel;
//跳表中元素的个数
private int size;
//跳表决定高度的概率,以0.5的概率产生0,如果是1就晋升一层
private static final double PROBABILITY = 0.5;
public SkipList() {
size = 0;
maxLevel = 0;
head = new SkipListNode(null);
head.nextNodes.add(null);
}
}
跳表的add方法:
//添加元素
public void add(Integer newValue) {
//如果不包含这个元素,就插入跳表
if(!contains(newValue)) {
//随机确定新的层高
size++;
int level = 0;
while(Math.random() < PROBABILITY) {
level++;
}
while(level > maxLevel) {
head.nextNodes.add(null);
maxLevel++;
}
//建立node,确定head为查找时的起始node
SkipListNode newNode = new SkipListNode(newValue);
SkipListNode current = head;
//遍历的时候每次要么向前走,要么向下走,按照层数遍历
//每一层都要建立新node,然后连接前后指针
do {
//获得这一层里最后一个大于newValue的node
current = findNext(newValue, current, level);
//生成newNode其中一个向前的指针(指向更大的node),每次都从0添加
//这样最后如果一共有5层,相当于先添加第五层然后第四层。。
//每个指针指向的就是current的前一个节点
//连接从node到前
newNode.nextNodes.add(0, current.nextNodes.get(level));
//使newNode和它后面的节点产生联系,将后面节点的向前指针连接到newNode
//连接从node到后(指向更小的node)
current.nextNodes.set(level, newNode);
}while(level-- > 0);
}
}
查找方法findNext:
//找到要找的目标值e在这一层level里刚刚大于e的node,current是寻找的起始位置
private SkipListNode findNext(Integer e, SkipListNode current, int level) {
//找到current节点在当前层的前一个节点
SkipListNode next = current.nextNodes.get(level);
while(next != null) {
Integer value = next.value;
//如果本节点是8,前一个节点是10,要找的值是9就会出现这种情况
//此时直接返回,进入下一层
if(e < value) {
break;
}
//如果本节点是8,前一个节点是10,要找的值是12就会继续向前找
//更新current和next
//每次如果前一个节点比要找的值小就前进,否则找下一层
current = next;
next = current.nextNodes.get(level);
}
return current;
}
contains方法:
public boolean contains(Integer value) {
SkipListNode node = find(value);
return node != null && node.value != null && node.value == value;
}
//找到e对应的节点
private SkipListNode find(Integer e) {
return find(e, head, maxLevel);
}
delete方法:
public void delete(Integer deleteValue) {
if(contains(deleteValue)) {
SkipListNode deleteNode = find(deleteValue);
size--;
int level = maxLevel;
SkipListNode current = head;
do {
//找到刚刚比deleteNode稍大一点的node,然后将该node的各向前指针修正为
//原来deleteNode向前指针的指向,相当于删除了deleteNode
current = findNext(deleteNode.value, current, level);
if(deleteNode.nextNodes.size() > level) {
current.nextNodes.set(level, deleteNode.nextNodes.get(level));
}
}while(level-- > 0);
}
}
跳表的迭代器(遍历结果一定是从小到大的):
class SkipListIterator implements Iterator<Integer>{
SkipList list;
SkipListNode current;
public SkipListIterator(SkipList list) {
this.list = list;
this.current = list.getHead();
}
@Override
public boolean hasNext() {
// TODO Auto-generated method stub
return current.nextNodes.get(0) != null;
}
@Override
public Integer next() {
// TODO Auto-generated method stub
current = current.nextNodes.get(0);
return current.value;
}
}
redis中的跳表
在组织score时redis采用跳表,而从member到score的映射redis使用字典来存储。
在redis中每一层的晋升概率是25%,它是一种更扁平化的跳表,在单个层上遍历的节点个数就会稍多一些。
在调整元素权重时,redis采用对该节点先删后加的方式来进行。
如果权重都相同redis还会比较value值,使redis中的跳表有序。
redis计算元素排名rank时,是对跳表功能的一种加强,对于每个元素都有它的字段rank值,跳表在变化时会更新这个值。