下面是skipList的一个介绍,转载来的,源地址:http://kenby.iteye.com/blog/1187303,为防止源地址丢失,故拷贝一份放在这里,望作者原谅。
———————————————转载开始—————————————————
为什么选择跳表
目前经常使用的平衡数据结构有:B树,红黑树,AVL树,Splay Tree, Treep等。
想象一下,给你一张草稿纸,一只笔,一个编辑器,你能立即实现一颗红黑树,或者AVL树
出来吗? 很难吧,这需要时间,要考虑很多细节,要参考一堆算法与数据结构之类的树,
还要参考网上的代码,相当麻烦。
用跳表吧,跳表是一种随机化的数据结构,目前开源软件 Redis 和 LevelDB 都有用到它,
它的效率和红黑树以及 AVL 树不相上下,但跳表的原理相当简单,只要你能熟练操作链表,
就能轻松实现一个 SkipList。
有序表的搜索
考虑一个有序表:
从该有序表中搜索元素 < 23, 43, 59 > ,需要比较的次数分别为 < 2, 4, 6 >,总共比较的次数
为 2 + 4 + 6 = 12 次。有没有优化的算法吗? 链表是有序的,但不能使用二分查找。类似二叉
搜索树,我们把一些节点提取出来,作为索引。得到如下结构:
这里我们把 < 14, 34, 50, 72 > 提取出来作为一级索引,这样搜索的时候就可以减少比较次数了。
我们还可以再从一级索引提取一些元素出来,作为二级索引,变成如下结构:
这里元素不多,体现不出优势,如果元素足够多,这种索引结构就能体现出优势来了。
跳表
下面的结构是就是跳表:
其中 -1 表示 INT_MIN, 链表的最小值,1 表示 INT_MAX,链表的最大值。
跳表具有如下性质:
(1) 由很多层结构组成
(2) 每一层都是一个有序的链表
(3) 最底层(Level 1)的链表包含所有元素
(4) 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。
(5) 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。
跳表的搜索
例子:查找元素 117
(1) 比较 21, 比 21 大,往后面找
(2) 比较 37, 比 37大,比链表最大值小,从 37 的下面一层开始找
(3) 比较 71, 比 71 大,比链表最大值小,从 71 的下面一层开始找
(4) 比较 85, 比 85 大,从后面找
(5) 比较 117, 等于 117, 找到了节点。
c# 实现 https://github.com/kencausey/SkipList
public class SkipList<TKey, TValue> : IDictionary<TKey, TValue> where TKey : IComparable
{
private SkipListNode<TKey, TValue> head;
private int count;
/// <summary>
/// A read-only value representing the current number of items in the
/// map.
/// </summary>
public int Count { get { return count; } }
/// <summary>
/// Skiplists are always read/write structures in this implementation.
/// </summary>
public bool IsReadOnly { get { return false; } }
/// <summary>
/// This implementation supports indexed [] reference for both reading
/// and writing entries of the map. Note that if you set the value
/// for an existing key in the map the current value will be
/// overwritten.
/// </summary>
/// <param name="key">The IComparable key reference</param>
/// <returns>the value</returns>
public TValue this[TKey key]
{
get
{
return get(key);
}
set
{
Add(key, value);
}
}
/// <summary>
/// Returns a collection (List) representing all the keys in the map in
/// key-sorted order.
/// </summary>
public ICollection<TKey> Keys
{
get
{
List<TKey> keys = new List<TKey>(count);
walkEntries(n => keys.Add(n.key));
return keys;
}
}
/// <summary>
/// Returns a collection (List) representing all the value in the map
/// in key-sorted order.
/// </summary>
public ICollection<TValue> Values
{
get
{
List<TValue> values = new List<TValue>(count);
walkEntries(n => values.Add(n.value));
return values;
}
}
private struct SkipListKVPair<W, X>
{
private W key;
public W Key
{
get { return key; }
}
public X Value;
public SkipListKVPair (W key, X value)
{
this.key = key;
this.Value = value;
}
}
private class SkipListNode<TNKey, TNValue>
{
public SkipListNode<TNKey, TNValue> forward, back, up, down;
public SkipListKVPair<TNKey, TNValue> keyValue;
public bool isFront = false;
public TNKey key
{
get { return keyValue.Key; }
}
public TNValue value
{
get { return keyValue.Value; }
set { keyValue.Value = value; }
}
public SkipListNode()
{
this.keyValue = new SkipListKVPair<TNKey, TNValue>(default(TNKey), default(TNValue));
this.isFront = true;
}
public SkipListNode(SkipListKVPair<TNKey, TNValue> keyValue)
{
this.keyValue = keyValue;
}
public SkipListNode(TNKey key, TNValue value)
{
this.keyValue = new SkipListKVPair<TNKey, TNValue>(key, value);
}
}
/// <summary>
/// Creates and returns a new empty skiplist.
/// </summary>
public SkipList()
{
this.head = new SkipListNode<TKey, TValue>();
count = 0;
}
/// <summary>
/// This is an alternative (to indexing) interface to add and modify
/// existing values in the map.
/// </summary>
/// <param name="key">The IComparable key</param>
/// <param name="value">The new value</param>
public void Add(TKey key, TValue value)
{
// Duh, we have to be able to tell when no key is found from when one is found
// and if none is found have a reference to the last place searched.... return
// a bool and use an out value?
SkipListNode<TKey, TValue> position;
bool found = search(key, out position);
if(found)
position.value = value;
else
{
// In this scenario position, rather than the value we searched
// for is the value immediately previous to where it should be inserted.
SkipListNode<TKey, TValue> newEntry = new SkipListNode<TKey, TValue>((TKey)key, value);
count++;
newEntry.back = position;
if(position.forward != null)
newEntry.forward = position.forward;
position.forward = newEntry;
promote(newEntry);
}
}
/// <summary>
/// Add an entry using a System.Collections.Generic.KeyValuePair<>.
/// </summary>
/// <param name="keyValue">The KeyValuePair<> to add. The key must be
/// an IComparable. If a matching entry already exists the value will
/// be updated to the value specified in the KeyValuePair.</param>
public void Add(KeyValuePair<TKey, TValue> keyValue)
{
Add(keyValue.Key, keyValue.Value);
}
/// <summary>
/// Empty the skiplist.
/// </summary>
public void Clear()
{
head = new SkipListNode<TKey, TValue>();
count = 0;
// Must more be done to ensure that all references are released?
}
/// <summary>
/// Test for the existence of an entry with the given key.
/// </summary>
/// <param name="key">The IComparable key to search for.</param>
/// <returns>a bool indicating whether the map contains an entry with
/// the specified key</returns>
public bool ContainsKey(TKey key)
{
SkipListNode<TKey, TValue> notused;
return search(key, out notused);
}
/// <summary>
/// Test for the existence of an entry with a matching key from a
/// System.Collections.Generic.KeyValuePair<>. Note that the value from
/// the KeyValuePair is ignored and only the key is used in this test.
/// </summary>
/// <param name="keyValue">The KeyValuePair<> for which to search the
/// map, note that only the IComparable key is used.</param>
/// <returns>a bool indicating whether or not a matching entry exists
/// in the map</returns>
public bool Contains(KeyValuePair<TKey, TValue> keyValue)
{
return ContainsKey(keyValue.Key);
}
/// <summary>
/// Remove an entry in the map matching the specified key.
/// </summary>
/// <param name="key">The IComparable key to search for. If found the
/// matching entry is removed from the map.</param>
/// <returns>a bool indicating whether the specified key was found in
/// the map and the entry removed</returns>
public bool Remove(TKey key)
{
SkipListNode<TKey, TValue> position;
bool found = search(key, out position);
if(!found)
return false;
else
{
SkipListNode<TKey, TValue> old = position;
do {
old.back.forward = old.forward;
if(old.forward != null)
old.forward.back = old.back;
old = old.up;
} while (old != null);
count--;
// Clean up rows with only a head remaining.
while(head.forward == null) {
head = head.down;
}
return true;
}
}
/// <summary>
/// Remove an entry in the map matching the key from the specified
/// System.Collections.Generic.KeyValuePair<>. Only the key part of the
/// KeyValuePair is used in the search. Note that the value part of
/// the KeyValuePair is not used.
/// </summary>
/// <param name="key">A KeyValuePair<> containing the IComparable key to
/// search for. If found the matching entry is removed from the map.</param>
/// <returns>a bool indicating whether the a matching entry was found
/// in the map and removed</returns>
public bool Remove(KeyValuePair<TKey, TValue> keyValue)
{
return Remove(keyValue.Key);
}
/// <summary>
/// Allows searching for a matching entry by IComparable key returning
/// the value, if found as an out value. Also returns as the standard
/// return value whether or not a matching entry was found.
/// </summary>
/// <param name="key">IComparable key to search for</param>
/// <param name="value">An out value specifying the value of the entry
/// if found, otherwise the default is returned.</param>
/// <returns>a bool indicating whether or not a matching entry was
/// found</returns>
public bool TryGetValue(TKey key, out TValue value)
{
try
{
value = get(key);
return true;
}
catch (KeyNotFoundException)
{
value = default(TValue);
return false;
}
}
/// <summary>
/// Copies all entries in the skiplist to the provided System.Array of
/// System.Collection.Generic.KeyValuePair<>s starting at the given
/// index.
/// </summary>
/// <exception cref="System.ArgumentNullException">Thrown if the array
/// provided is null.</exception>
/// <exception cref="System.ArgumentException">Thrown if the array is
/// read-only, or does not have sufficient space after the specified
/// index for the entries in the skiplist</exception>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if the
/// specified index is less than zero.</exception>
/// <param name="array">The array of KeyValuePair<>s in which to copy
/// the skiplist entries. The array must have sufficient space after
/// the specified index to hold all entries in the skiplist.</param>
/// <param name="index">The index of the array at which to start
/// copying the entries.</param>
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
{
if (array == null)
throw new ArgumentNullException("array");
if (index < 0)
throw new ArgumentOutOfRangeException("index");
if (array.IsReadOnly)
throw new ArgumentException("The array argument is Read Only and new items cannot be added to it.");
if (array.IsFixedSize && array.Length < count + index)
throw new ArgumentException("The array argument does not have sufficient space for the SkipList entries.");
int i = index;
walkEntries(n => array[i++] = new KeyValuePair<TKey, TValue>(n.key, n.value));
}
/// <summary>
/// Provides a System.Collections.Generic.IEnumerator<> interface to a
/// collection of System.Collection.Generic.KeyValuePair<>s
/// representing the entries in the map in key-sorted order.
/// NOTE: The enumerator returned enumerates over internally used
/// values, modifying the value is fine but do not modify the key
/// because that would invalidate the internal structural assumptions.
/// </summary>
/// <returns>An IEnumerator<> of the map entries in key-sorted order</returns>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
SkipListNode<TKey, TValue> position = head;
while (position.down != null)
position = position.down;
while (position.forward != null)
{
position = position.forward;
yield return new KeyValuePair<TKey, TValue>(position.key, position.value);
}
}
/// <summary>
/// Provides a System.Collections.IEnumerator interface to a collection
/// of System.Collection.Generic.KeyValuePair<>s representing the
/// entries in the map in key-sorted order.
/// NOTE: The enumerator returned enumerates over internally used
/// values, modifying the value is fine but do not modify the key
/// because that would invalidate the internal structural assumptions.
/// </summary>
/// <returns>An IEnumerator of the map entries in key-sorted order</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) GetEnumerator();
}
/// <summary>
/// Retrieve the value from the matching entry in the map to the given
/// IComparable key.
/// </summary>
/// <param name="key">The IComparable key to search for</param>
/// <returns>The value found</returns>
/// <exception cref="System.Collections.Generic.KeyNotFoundException">
/// Thrown if no entry is found with the given key</exception>
private TValue get(TKey key)
{
SkipListNode<TKey, TValue> position;
bool found = search(key, out position);
if (!found)
throw new KeyNotFoundException("Unable to find entry with key \"" + key.ToString() + "\"");
return position.value;
}
/// <summary>
/// Takes an Action that accepts one argument representing a
/// SkipListNode in the map and performs the given action on every entry
/// in the map in key-sorted order.
/// </summary>
/// <param name="lambda">A System.Action(T) that accepts one parameter
/// which will be each unique entry as a SkipListNode</param>
private void walkEntries(Action<SkipListNode<TKey, TValue>> lambda)
{
SkipListNode<TKey, TValue> node = head;
while(node.down != null)
node = node.down;
while(node.forward != null) {
node = node.forward;
lambda(node);
}
}
/// <summary>
/// The core search algorithm: Returns a SkipListPair of SkipListNodes
/// representing the matching entry with the given IComparable key and
/// the immediately preceding entry in the map on the fastlane in which
/// the entry was found.
/// </summary>
/// <param name="key">The IComparable key for which to search</param>
/// <param name="position">Either the matching node if the true is
/// returned as the return value, or, if false is returned, the value
/// just before where the new value could be inserted.</param>
/// <returns>Whether or not the search for value was found.</returns>
private bool search(TKey key, out SkipListNode<TKey, TValue> position)
{
if(key == null)
throw new ArgumentNullException("key");
SkipListNode<TKey, TValue> current;
position = current = head;
while ((current.isFront || key.CompareTo(current.key) >= 0) && (current.forward != null || current.down != null))
{
position = current;
if (key.CompareTo(current.key) == 0)
return true;
if (current.forward == null || key.CompareTo(current.forward.key) < 0)
{
if (current.down == null)
return false;
else
current = current.down;
}
else
current = current.forward;
}
position = current;
// If the matching value is found in the last position of the last row, we could end up here with a match.
if (key.CompareTo(position.key) == 0)
return true;
else
return false;
}
/// <summary>
/// This algorithm promotes the newly added node on a probabilistic
/// basis.
/// </summary>
/// <param name="node">The root node (initially added node added to the
/// bottom, primary, row) to consider promoting.</param>
private void promote(SkipListNode<TKey, TValue> node)
{
// up represents our search for the value just prior to the newly
// added value in the next row to which the newly added value
// should be promoted.
// last represents the most recently added node, starting with the
// newly created node.
SkipListNode<TKey, TValue> up = node.back;
SkipListNode<TKey, TValue> last = node;
for (int levels = this.levels(); levels > 0; levels--)
{
// Find the next node back that links to next row up.
// If we find our way back to the head of the row and there is
// no link up then that means it is time to create a new row.
while (up.up == null && !up.isFront)
up = up.back;
if (up.isFront && up.up == null)
{
// As mentioned above is this is the front of the row and
// there is no link up then we need to start a new row and
// update the head to ensure it always points to the start
// of the topmost row.
up.up = new SkipListNode<TKey, TValue>();
head = up.up;
}
up = up.up;
// At this point up should represent the value in the next row
// up immediately prior to where the new node should be
// promoted. If this node has been promoted to a previously
// unreached level, then up will be the head of the new row.
SkipListNode<TKey, TValue> newNode = new SkipListNode<TKey, TValue>(node.keyValue);
newNode.forward = up.forward;
up.forward = newNode;
// Remember last starts as the brand new node but should be
// updated to always point to the representative node in
// the previous row.
newNode.down = last;
newNode.down.up = newNode;
last = newNode;
}
}
/// <summary>
/// The random number of level to promote a newly added node.
/// </summary>
/// <returns>the number of levels of promotion</returns>
private int levels()
{
Random generator = new Random();
int levels = 0;
while (generator.NextDouble() < 0.5)
levels++;
return levels;
}
}