c# 跳跃表原理

 下面是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;
        }
    }


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值