c# 跳跃表原理
来源:互联网 发布:知乎 日本中国 编辑:程序博客网 时间:2024/06/02 13:12
下面是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; } }
- c# 跳跃表原理
- 跳跃表原理
- 跳跃表原理
- 跳跃表原理
- 跳跃表原理
- 跳跃表 C#
- 跳跃表实现的原理
- 跳跃表实现的原理
- 跳跃表实现的原理
- 跳跃表实现与原理
- 跳跃表-原理及Java实现
- 浅析SkipList跳跃表原理及代码
- 跳跃表 SkipList【数据结构】原理及实现
- 跳跃表-原理及Java实现
- 【转】跳跃表-原理及Java实现
- 跳跃表的原理及实现
- 跳跃表-原理及Java实现
- 跳跃表的原理及Java实现
- pku 1028 模拟法
- Mysql GroupBy 使用注意事项
- 想成为理论物理学家?这里有一份资料
- 一个单身男博士的业余生活:技多不压身,解数独也可撩妹
- Java进阶(七)正确理解Thread Local的原理与适用场景
- c# 跳跃表原理
- Windows10下JAVA环境变量的设置
- 10款效果惊艳的HTML5应用和源码
- GO语言-顺序编程
- Windows 注册和取消注册一个dll或者ocx
- Java普通代码块,构造代码块,静态代码块区别,执行顺序
- Spring AOP控制事务
- Android studio JNI开发的三种方式
- 日记2017-12-23