普通链表有一个严重的缺陷:查找某对象时需要遍历整个链表,直到找到了该元素或者遍历完了整个链表也没有找到,时间复杂度很高。
跳跃链表中的元素按照从小到达或从大到小的规则排列,该顺序在向链表加入元素时维护,所以,只能指定向链表插入某个值,不能指定插入位置
跳跃链表使用了二分查找的思想, 查找某个元素的复杂度是O(logn)
跳跃链表最底层(对应代码中的0层)包含了所有元素
跳跃链表第1层(对应代码中的1层)包含了从0层中随机抽取的一半的元素(理论上)
跳跃链表第2层(对应代码中的2层)包含了从1层中随机抽取的一半的元素(理论上)
以此类推
因为跳跃表在何处插入和删除值未知,很难使用预定算法使得这个链表“平衡”(即上层索引较为平均的分隔下层索引)。所以使用随机数决定,要将新加入的结点存入哪些层
加入元素时采用随机值决定要插入多少层;但是删除时,必须一律从最高层开始删除,多次添加和删除之后,较高层的插入是根据随机数,删除是必须删除,所以会发生较高层结点较少的情况,降低查找效率。
另外,算法根据随机数进行层数分配,有一定的不可控因素。
下方是Java代码:
package skiplist;
import java.util.Random;
//结点类
class Node{
int value; //该结点的值
Node next; //该结点的下一个结点
Node down; //该结点对应的下一层结点
Node(int aValue){
value = aValue;
next = null;
down = null;
}
Node(int aValue, Node aNext){
value = aValue;
next = aNext;
down = null;
}
Node(int aValue, Node aNext, Node aDown){
value = aValue;
next = aNext;
down = aDown;
}
};
//顺序表,链表中的元素按升序排列
class SortedList{
Node head; //表头结点
Node tail; //表尾结点
int length; //链表长度
SortedList(){
head = tail = null;
length = 0;
}
//插入结点
//参数:要插入新节点的值;要插入的结点的前一个结点的引用
Node insertNode(int value, Node aheadOfInsert){
//被插入的结点
Node InsertedNode = null;
//如果链表为空,调用向头部插入方法
if (aheadOfInsert==null){
InsertedNode = insertToHead(value);
}
//要在链表尾部插入,直接更新tail
else if (aheadOfInsert == tail){
InsertedNode = tail = aheadOfInsert.next = new Node(value, null);
}
//在链表中间插入
else {
InsertedNode = aheadOfInsert.next = new Node(value, aheadOfInsert.next);
}
length++;
return InsertedNode;
}
//删除结点
//参数:要删除的结点的前一个结点的引用
//如果要删除第一个结点,则该参数为空
int deleteNode(Node aheadOfRemove){
//链表为空,直接返回-1
if (head == null){
return -1;
}
Node removedNode = null;
//链表只有一个结点,删除该结点
if (head == tail){
removedNode = head;
head = tail = null;
}
//参数为空,代表要删除第一个结点。同时更新head
else if (aheadOfRemove == null){
removedNode = head;
head = head.next;
}
//正常删除结点
else{
removedNode = aheadOfRemove.next;
aheadOfRemove.next = aheadOfRemove.next.next;
}
length--;
return 0;
}
//判断链表是否为空
int isEmpty(){
return head == null?1:0;
}
//打印链表内容及相关参数
void printSelf(){
System.out.print("SortedList: [");
for (Node now=head; now!=null; now=now.next)
if (now.down == null)
System.out.print(String.format("%d, ", now.value));
else
System.out.print(String.format("%d( %d), ", now.value, now.down.value));
System.out.print(String.format("]\t\t%d\n", length));
}
private Node insertToHead(int value){
if (head == null){
head = tail = new Node(value, null, null);
}
else {
head = new Node(value, head);
}
return head;
}
};
//跳跃表类
public class SkipList {
int length; //跳跃表最底层长度
int layerNum; //跳跃表最大层数
SortedList[] layers; //跳跃表层数的引用
//为什么这里使用了数组?
//因为跳跃表的层数是log(n),100层能够容纳2^100个数据,使用数组占用的空间可以忽略
SkipList(){
length = 0;
layerNum = 0;
layers = new SortedList[100];
for (int i=0; i<100; i++) {
layers[i] = new SortedList();
}
}
//向跳跃表插入值
void insert(int value){
//get the necessary layerNum by number of nodes
//先获得增加结点之后,该结点需要加入到哪些层
int newLayerNum = getLayerNumRandomly(length+1);
//更新最大层数量
if (layerNum < newLayerNum){
layerNum++;
}
//因为先要从上向下查找到要将新结点插入到哪里,并且需要将查找的路径记录下来
//这样才能在每一层都插入该结点
//该数组用于记录从上向下寻找的过程中,经过了哪些层的哪个结点(下标为层数,数组内容为经过的结点)
Node[] aheadOfInsert = new Node[100];
//数组初始化为NULL
for (int i=0; i<100; i++)
aheadOfInsert[i] = null;
//获取每一层需要插入结点的位置
getInsertIndex(value, aheadOfInsert);
Node[] InsertedNode = new Node[100];
for (int i=0; i<100; i++)
InsertedNode[i] = null;
//遍历每一层,插入结点
for (int i=layerNum;i>=0;i--){
InsertedNode[i] = layers[i].insertNode(value, aheadOfInsert[i]);
}
//更新层与层之间的指针
for (int i=layerNum; i>=1; i--){
InsertedNode[i].down = InsertedNode[i-1];
}
length++;
//打印各个层
System.out.println("================================");
layers[0].printSelf();
layers[1].printSelf();
layers[2].printSelf();
layers[3].printSelf();
layers[4].printSelf();
System.out.println("================================");
}
//因为先要从上向下查找到要将新结点插入到哪里,并且需要将查找的路径记录下来
//这样才能在每一层都插入该结点
//这个函用于查找该路径,该路径用要插入的结点位置的前一个结点表示,存入aheadOfInsert数组
void getInsertIndex(int value, Node[] aheadOfInsert){
int i;
Node currentNode = null;
//最高的层可能为空,需要在链表头插入结点,所以前一个结点为空
for (i=layerNum; i>=0 && (layers[i].head==null || layers[i].head.value>value); i--){
aheadOfInsert[i] = null;
}
//如果所有层都为空,直接返回
if (i==-1)
return ;
currentNode = layers[i].head;
//从上层到下层遍历,寻找该路径
for (; i>=0; i--){
Node now;
//该循环从一层的链表前方向后方遍历,循环停止的条件有两个:
//当遇到链表尾:说明该值只可能在下一层找到
//当遇到比当前值还小的结点,说明要找的值,在当前结点和下一和结点之间,转向下一层寻找
for (now=currentNode; now.next!=null && now.next.value=0 && (layers[i].head==null || layers[i].head.value>value); i--){
aheadOfRemove[i] = null;
deleteOrNot[i] = false;
}
//循环退出之后,i指向需要有结点有可能被删除的第一个层
if (i==-1)
return ;
//遍历可能需要删除结点的层
//如果要删除的结点是底层链表的第一个元素,那么向上找,把所有头元素是该元素的链表首元素全部删除
if (layers[0].head.value == value){
i=0;
while(layers[i].head!=null && layers[i].head.value == value){
deleteOrNot[i] = true;
aheadOfRemove[i] = null;
i++;
}
return ;
}
currentNode = layers[i].head;
//否则向下沿路径查找,寻找应该删除的结点
for (; i>=0; i--){
Node now;
for (now=currentNode; now.next!=null && now.next.value=0; i--)
if (removeOrNot[i] == true){
layers[i].deleteNode(aheadOfRemove[i]);
}
length--;
//打印结果
System.out.println("================================");
layers[0].printSelf();
layers[1].printSelf();
layers[2].printSelf();
layers[3].printSelf();
layers[4].printSelf();
System.out.println("================================");
}
//在跳跃表中搜索某个元素
Node search(int value){
int i;
Node currentNode = null;
//从上层向下层遍历,如果该层为空,或该层第一个值已经大于要查找的值,说明这一层不需要查找
for (i=layerNum; i>=0 && (layers[i].head==null || layers[i].head.value>value); i--);
if (i==-1)
return null;
currentNode = layers[i].head;
Node now = null;
//从上层向下层遍历查找
for (; i>=0; i--){
for (now=currentNode; now.next!=null && now.next.value<=value; now=now.next);
currentNode = now.down;
}
return now;
}
//随机获取某个结点应该被插入到0-k层
int getLayerNumRandomly(int supposeLength){
int k = 0;
Random rand = new Random();
//随机数
int a = rand.nextInt(2);
while(a==0){
k++;
a = rand.nextInt(2);
}
//根据二分的思想,查找n个结点,需要log(n)次
//跳跃表里也就需要log(n)层,所以这里对随机产生的层数有最大值限制
int maxLayer = getMaxLayer(supposeLength);
if (k > maxLayer)
k = maxLayer;
return k;
}
int getMaxLayer(int supposeLength){
return (int)Math.ceil(Math.log10(supposeLength)/Math.log10(2));
}
static public void main(String[] argv) {
SkipList list = new SkipList();
list.insert(33);
list.insert(1);
list.insert(11);
list.insert(21);
list.insert(-1);
list.insert(45);
list.insert(37);
Node result = list.search(11);
System.out.println(result.value);
System.out.println("next Search result: " + result.next.value);
list.remove(11);
list.remove(-1);
list.remove(45);
}
}