Skip Lists
What is a Skip List
- A skip list for a set S of distinct (key, element) items is a series of lists S0, S1 , … , Sh such that
- Each list Si contains the special keys +∞ and −∞
- List S0 contains the keys of S in nondecreasing order
- Each list is a subsequence of the previous one, i.e., S0 ⊇S1 ⊇ … ⊇ Sh
- List Sh contains only the two special keys
- We show how to use a skip list to implement the dictionary ADT
![Red_Black_Tree](Images/Skip_List.PNG)
Search
- We search for a key x in a a skip list as follows:
- We start at the first position of the top list
- At the current position p, we compare x with y ←key(after(p))
x = y: we return element(after(p))
x > y: we “scan forward”
x < y: we “drop down”
- If we try to drop down past the bottom list, we return NO_SUCH_KEY
- Example: search for 78
![Skip List Search](Images/Skip_List_Search.PNG)
Randomized Algorithms
Insertion
- To insert an item (x, o) into a skip list, we use a randomized algorithm:
- We repeatedly toss a coin until we get tails, and we denote with i the number of times the coin came up heads
- If i ≥ h, we add to the skip list new lists Sh+1, … , Si +1, each containing only the two special keys
- We search for x in the skip list and find the positions p0, p1 , …, pi of the items with largest key less than x in each list S0, S1, … , Si
- For j ← 0, …, i, we insert item (x, o) into list Sj after position pj
- Example: insert key 15, with i = 2
![Skip List Insert](Images/Skip_List_Insert.PNG)
Removal
- To remove an item with key x from a skip list, we proceed as follows:
- We search for x in the skip list and find the positions p0, p1 , …, pi of the items with key x, where position pj is in list Sj
- We remove positions p0, p1 , …, pi from the lists S0, S1, … , Si
- We remove all but one list containing only the two special keys
- Example: remove key 34
![Skip List Insert Removal](Images/Skip_List_Removal.PNG)
Height
- The running time of the search an insertion algorithms is affected by the height h of the skip list
- We show that with high probability, a skip list with n items has height O(log n)
- We use the following additional probabilistic fact:
- Fact 3: If each of n events has probability p, the probability that at least one event occurs is at most np
- Consider a skip list with n items
- By Fact 1, we insert an item in list Si with probability 1/2^i
- By Fact 3, the probability that list Si has at least one item is at most n/2^i
- By picking i = 3log n, we have that the probability that S3log n has at least one item is at most n/23log n = n/n3 = 1/n2
- Thus a skip list with n items has height at most 3log n with probability at least 1 − 1/n2
Search and Update Times
- The search time in a skip list is proportional to
- the number of drop-down steps, plus
- the number of scan-forward steps
- The drop-down steps are bounded by the height of the skip list and thus are O(log n) with high probability
- To analyze the scan-forward steps, we use yet another probabilistic fact:
- Fact 4: The expected number of coin tosses required in order to get tails is 2
- When we scan forward in a list, the destination key does not belong to a higher list
- A scan-forward step is associated with a former coin toss that gave tails
- By Fact 4, in each list the expected number of scanforward steps is 2
- Thus, the expected number of scan-forward steps is O(log n)
- We conclude that a search in a skip list takes O(log n) expected time
- The analysis of insertion and deletion gives similar results
Space Usage
- The space used by a skip list depends on the random bits used by each invocation of the insertion algorithm
- We use the following two basic probabilistic facts:
- Fact 1: The probability of getting i consecutive heads when flipping a coin is 1/2^i
- Fact 2: If each of n items is present in a set with probability p, the expected size of the set is np
- Consider a skip list with n items
- By Fact 1, we insert an item in list Si with probability 1/2^i
- By Fact 2, the expected size of list Si is n/2^i
- The expected number of nodes used by the skip list is
![Skip_List_Space_Usage](Images/Skip_List_Space_Usage.PNG)
- Thus, the expected space usage of a skip list with n items is O(n)
Summary/Runtime Analysis
- A skip list is a data structure for dictionaries that uses a randomized insertion algorithm
- In a skip list with n items
- The expected space used is O(n)
- The expected search, insertion and deletion time is O(log n)
- Using a more complex probabilistic analysis, one can show that these performance bounds also hold with high probability
- Skip lists are fast and simple to implement in practice
Code
package examples;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
public class MySkipList<K extends Comparable<? super K>, E> implements
OrderedDictionary<K, E> {
class SLNode implements Locator<K,E> {
SLNode prev,next,above,below;
Object owner = MySkipList.this;
K key;
E elem;
@Override
public E element() {
return elem;
}
@Override
public K key() {
return key;
}
}
private SLNode topLeft,bottomLeft,topRight,bottomRight;
private K minKey,maxKey;
private int size;
private Random rand = new Random();
private double p = 0.5;
private int height = 2;
public MySkipList(K min, K max){
topLeft = new SLNode();
topLeft.key = min;
topRight = new SLNode();
topRight.key = max;
bottomLeft = new SLNode();
bottomLeft.key = min;
bottomRight = new SLNode();
bottomRight.key = max;
topLeft.next = topRight;
topRight.prev = topLeft;
bottomLeft.next = bottomRight;
bottomRight.prev = bottomLeft;
topLeft.below = bottomLeft;
topRight.below = bottomRight;
bottomLeft.above = topLeft;
bottomRight.above = topRight;
minKey = min;
maxKey = max;
}
@Override
public int size() {
return size;
}
private void checkKey(K key){
if (key.compareTo(minKey)<=0) throw new RuntimeException("key not bigger than minKey!");
if (key.compareTo(maxKey)>=0) throw new RuntimeException("key not smaller than maxKey!");
}
@Override
public Locator<K, E> find(K key) {
checkKey(key);
SLNode pos = search(key);
if (pos.key.compareTo(key)!=0) return null;
else {
while (pos.prev.key.compareTo(key)== 0) pos=pos.prev;
return pos;
}
}
private SLNode search(K key){
SLNode pos = topLeft;
while (pos.below != null){
pos = pos.below;
while (key.compareTo(pos.next.key) >=0) pos = pos.next;
}
return pos;
}
@Override
public Locator<K, E>[] findAll(K key) {
SLNode n = (SLNode) find(key);
if (n==null) return new Locator[0];
ArrayList<Locator<K,E>> al = new ArrayList<>();
while(n.key.compareTo(key)==0){
al.add(n);
n=n.next;
}
return al.toArray(new Locator[0]);
}
@Override
public Locator<K, E> insert(K key, E o) {
checkKey(key);
SLNode pos = search(key);
while (pos.next.key.compareTo(key)== 0) pos=pos.next;
SLNode nNew = new SLNode();
nNew.key = key;
nNew.elem = o;
nNew.next = pos.next;
nNew.prev = pos;
nNew.next.prev=nNew;
nNew.prev.next=nNew;
SLNode lastIndex = nNew;
while (rand.nextDouble() < p){
while (pos.above==null) pos = pos.prev;
pos = pos.above;
SLNode index = new SLNode();
index.key = key;
index.prev = pos;
index.next = pos.next;
index.next.prev = index;
index.prev.next = index;
index.below = lastIndex;
lastIndex.above = index;
lastIndex=index;
if (pos == topLeft) expand();
}
size++;
return nNew;
}
private void expand(){
SLNode n1 = new SLNode();
n1.key = minKey;
SLNode n2 = new SLNode();
n2.key = maxKey;
n1.next = n2;
n2.prev = n1;
n1.below = topLeft;
n2.below = topRight;
topLeft.above = n1;
topRight.above = n2;
topLeft = n1;
topRight = n2;
height++;
}
@Override
public void remove(Locator<K, E> loc) {
SLNode n = (SLNode) loc;
if (n.owner != this) throw new RuntimeException("invalid locator "+loc.key());
n.owner=null;
int lev=0;
while (n!=null){
n.prev.next=n.next;
n.next.prev=n.prev;
n=n.above;
lev++;
}
if (lev==height-1) shrink();
size--;
}
private void shrink() {
while (height>2 && topLeft.below.next==topRight.below){
topLeft = topLeft.below;
topRight = topRight.below;
topLeft.above = null;
topRight.above = null;
height--;
}
}
@Override
public Locator<K, E> closestBefore(K key) {
if (key.compareTo(minKey)<=0) throw new RuntimeException("key not bigger than minKey!");
if (key.compareTo(maxKey)>=0) throw new RuntimeException("key not smaller than maxKey!");
SLNode pos = search(key);
int comp = key.compareTo(pos.key);
if (comp==0){
pos = pos.prev;
if (pos == bottomLeft) return null;
while (key.compareTo(pos.key)==0) pos=pos.prev;
}
else if (comp>0){
while (pos.key.compareTo(pos.next.key)==0) pos=pos.next;
if (pos == bottomLeft) pos = null;
}
else
throw new RuntimeException("should never happen!");
return pos;
}
@Override
public Locator<K, E> closestAfter(K key) {
if (key.compareTo(minKey)<=0) throw new RuntimeException("key not bigger than minKey!");
if (key.compareTo(maxKey)>=0) throw new RuntimeException("key not smaller than maxKey!");
SLNode pos = search(key);
int comp = key.compareTo(pos.key);
if (comp==0){
pos = pos.next;
while (key.compareTo(pos.key)==0) pos=pos.next;
if (pos == bottomRight) pos = null;
}
else if (comp>0){
while (pos.key.compareTo(pos.next.key)==0) pos=pos.next;
pos = pos.next;
if (pos == bottomRight) pos = null;
}
else throw new RuntimeException("should never happen!");
return pos;
}
@Override
public Locator<K, E> next(Locator<K, E> loc) {
SLNode n = (SLNode) loc;
if (n.owner != this) throw new RuntimeException("invalid locator "+loc.key());
n = n.next;
if (n==bottomRight) n=null;
return n;
}
@Override
public Locator<K, E> previous(Locator<K, E> loc) {
SLNode n = (SLNode) loc;
if (n.owner != this) throw new RuntimeException("invalid locator "+loc.key());
n = n.prev;
if (n==bottomLeft) n=null;
return n;
}
@Override
public Locator<K, E> min() {
if (size>0) return bottomLeft.next;
else return null;
}
@Override
public Locator<K, E> max() {
if (size>0) return bottomRight.prev;
else return null;
}
@Override
public Iterator<Locator<K, E>> sortedLocators() {
return new Iterator<Locator<K, E>>(){
SLNode pos = bottomLeft.next;
@Override
public boolean hasNext() {
return pos != bottomRight;
}
@Override
public Locator<K, E> next() {
SLNode ret = pos;
pos = pos.next;
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException(
"use remove method of MySkipList!");
}
};
}
public void print(){
System.out.println("-------start------");
SLNode n = bottomLeft;
n=n.next;
StringBuffer lev = new StringBuffer();
while (n!=bottomRight){
lev.delete(0,lev.length());
SLNode m = n;
int index = 0;
while (m.above != null) {
index++;
m=m.above;
lev.append("+");
}
while(index<height-2){
index++;
lev.append("|");
}
System.out.println(String.format("%11d", n.key())+lev.toString()+" elem: "+n.elem);
n=n.next;
}
System.out.println("--------end-------");
}
public static void main(String[] args) {
MySkipList<Integer, String> sl = new MySkipList<>(Integer.MIN_VALUE,Integer.MAX_VALUE);
Random rand = new Random();
int n = 1000000;
Locator<Integer,String>[] locs = new Locator[n];
long time1 = System.currentTimeMillis();
for (int i=0;i<n;i++) {
locs[i]=sl.insert(rand.nextInt(n),""+i);
}
for (int i=0;i<n/2;i++) {
sl.remove(locs[i]);
}
Locator<Integer,String>[] ll = sl.findAll(33);
for (int i=0;i<ll.length;i++)System.out.println(ll[i].key());
long time2 = System.currentTimeMillis();
System.out.println("elapsed time: "+(time2-time1)/1000.0+" s");
System.out.println("height of index: "+sl.height);
}
}