C#如何实现一个高效的有序集合?SkipList跳表实现方法详解

来源:APP编程网作者:深圳网站建设头衔:草根站长
导读:本期聚焦于小伙伴创作的《C#如何实现一个高效的有序集合?SkipList跳表实现方法详解》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C#如何实现一个高效的有序集合?SkipList跳表实现方法详解》有用,将其分享出去将是对创作者最好的鼓励。

跳表是一种概率型数据结构,通过给有序链表添加多层索引,大幅提升了链表的查找效率,其平均时间复杂度为O(log n),和平衡二叉搜索树相当,但实现难度更低,非常适合用来实现高效的有序集合。在C#中我们可以手动实现跳表,替代部分场景下对内置有序集合的使用。

C#如何实现一个高效的有序集合?SkipList跳表实现方法详解

跳表核心原理

跳表的基础结构是一个有序的多层链表,最底层是完整的有序链表,上层链表是下层链表的索引,每个节点会随机生成所在层数,层数越高表示该节点出现在越多的索引层中。查找时从最高层索引开始遍历,遇到比目标值大的节点就下降到下一层继续查找,直到找到目标节点或者确认节点不存在。

跳表节点设计

跳表的每个节点需要存储当前节点的值、指向下一个节点的指针数组,指针数组的长度就是节点所在的层数。我们首先定义跳表节点的泛型类:

using System;
using System.Collections.Generic;

namespace SkipListDemo
{
    /// <summary>
    /// 跳表节点类
    /// </summary>
    /// <typeparam name="T">节点存储的值类型,需要支持比较</typeparam>
    public class SkipListNode<T> where T : IComparable<T>
    {
        /// <summary>
        /// 节点存储的值
        /// </summary>
        public T Value { get; set; }

        /// <summary>
        /// 节点的前向指针数组,索引i表示第i层的下一个节点
        /// </summary>
        public SkipListNode<T>[] Forward { get; set; }

        /// <summary>
        /// 节点所在的最大层数
        /// </summary>
        public int Level { get; set; }

        public SkipListNode(T value, int level)
        {
            Value = value;
            Level = level;
            Forward = new SkipListNode<T>[level];
        }
    }
}

跳表核心实现

跳表的实现需要包含初始化、随机层数生成、查找、插入、删除、遍历这几个核心功能,下面我们逐步实现这些功能。

跳表初始化与随机层数生成

跳表需要有一个头节点,头节点的层数可以设置为最大允许的层数,方便后续操作。随机层数生成采用概率算法,每次有50%的概率增加一层,直到达到最大层数限制。

public class SkipList<T> where T : IComparable<T>
{
    // 跳表最大层数
    private readonly int _maxLevel;
    // 跳表当前最大层数
    private int _currentLevel;
    // 头节点,不存储实际值
    private readonly SkipListNode<T> _head;
    // 随机数生成器,用于生成节点层数
    private readonly Random _random;
    // 概率因子,这里设置为0.5,即每层晋升的概率
    private readonly double _probability;

    public SkipList(int maxLevel = 16, double probability = 0.5)
    {
        _maxLevel = maxLevel;
        _currentLevel = 1;
        // 头节点的值设为默认值,不影响实际逻辑
        _head = new SkipListNode<T>(default, maxLevel);
        _random = new Random();
        _probability = probability;
    }

    /// <summary>
    /// 随机生成节点的层数
    /// </summary>
    private int RandomLevel()
    {
        int level = 1;
        // 每次随机生成0-1之间的数,小于概率因子则层数加1,直到达到最大层数
        while (_random.NextDouble() < _probability && level < _maxLevel)
        {
            level++;
        }
        return level;
    }
}

查找操作实现

查找操作从最高层开始,逐层向下遍历,记录每一层最后一个小于等于目标值的节点,最终如果最底层的下一个节点等于目标值,则查找成功。

/// <summary>
/// 查找目标值是否存在于跳表中
/// </summary>
public bool Contains(T target)
{
    SkipListNode<T> current = _head;
    // 从最高层开始向下遍历
    for (int i = _currentLevel - 1; i >= 0; i--)
    {
        // 当前层的节点值小于目标值则继续向后移动
        while (current.Forward[i] != null && current.Forward[i].Value.CompareTo(target) < 0)
        {
            current = current.Forward[i];
        }
    }
    // 遍历到最底层后,检查下一个节点是否为目标值
    current = current.Forward[0];
    return current != null && current.Value.CompareTo(target) == 0;
}

插入操作实现

插入操作首先需要找到每一层插入位置的前驱节点,然后随机生成新节点的层数,更新前驱节点的指针指向新节点,同时更新跳表的当前最大层数。

/// <summary>
/// 向跳表中插入值
/// </summary>
public void Insert(T value)
{
    // 记录每一层插入位置的前驱节点
    SkipListNode<T>[] update = new SkipListNode<T>[_maxLevel];
    SkipListNode<T> current = _head;
    // 从最高层开始找到每一层的前驱节点
    for (int i = _currentLevel - 1; i >= 0; i--)
    {
        while (current.Forward[i] != null && current.Forward[i].Value.CompareTo(value) < 0)
        {
            current = current.Forward[i];
        }
        update[i] = current;
    }
    // 检查值是否已经存在,跳表默认不允许重复值
    current = current.Forward[0];
    if (current != null && current.Value.CompareTo(value) == 0)
    {
        return;
    }
    // 随机生成新节点的层数
    int newLevel = RandomLevel();
    // 如果新节点的层数超过当前最大层数,更新前驱节点和当前最大层数
    if (newLevel > _currentLevel)
    {
        for (int i = _currentLevel; i < newLevel; i++)
        {
            update[i] = _head;
        }
        _currentLevel = newLevel;
    }
    // 创建新节点
    SkipListNode<T> newNode = new SkipListNode<T>(value, newLevel);
    // 更新每一层的指针
    for (int i = 0; i < newLevel; i++)
    {
        newNode.Forward[i] = update[i].Forward[i];
        update[i].Forward[i] = newNode;
    }
}

删除操作实现

删除操作同样先找到每一层待删除节点的前驱节点,然后检查最底层的下一个节点是否为待删除节点,如果是则逐层更新前驱节点的指针,移除待删除节点。

/// <summary>
/// 从跳表中删除值
/// </summary>
public bool Remove(T value)
{
    // 记录每一层待删除节点的前驱节点
    SkipListNode<T>[] update = new SkipListNode<T>[_maxLevel];
    SkipListNode<T> current = _head;
    // 从最高层开始找到每一层的前驱节点
    for (int i = _currentLevel - 1; i >= 0; i--)
    {
        while (current.Forward[i] != null && current.Forward[i].Value.CompareTo(value) < 0)
        {
            current = current.Forward[i];
        }
        update[i] = current;
    }
    // 检查待删除节点是否存在
    current = current.Forward[0];
    if (current == null || current.Value.CompareTo(value) != 0)
    {
        return false;
    }
    // 逐层更新指针,移除待删除节点
    for (int i = 0; i < _currentLevel; i++)
    {
        if (update[i].Forward[i] != current)
        {
            break;
        }
        update[i].Forward[i] = current.Forward[i];
    }
    // 如果删除的节点是最高层的节点,更新当前最大层数
    while (_currentLevel > 1 && _head.Forward[_currentLevel - 1] == null)
    {
        _currentLevel--;
    }
    return true;
}

遍历操作实现

遍历操作只需要从最底层的头节点开始,依次向后访问所有节点即可,因为最底层是完整的有序链表。

/// <summary>
/// 获取跳表中所有值的有序集合
/// </summary>
public List<T> GetAll()
{
    List<T> result = new List<T>();
    SkipListNode<T> current = _head.Forward[0];
    while (current != null)
    {
        result.Add(current.Value);
        current = current.Forward[0];
    }
    return result;
}

使用示例

下面我们通过一个简单的示例来演示跳表的使用:

class Program
{
    static void Main(string[] args)
    {
        SkipList<int> skipList = new SkipList<int>();
        // 插入数据
        skipList.Insert(3);
        skipList.Insert(1);
        skipList.Insert(5);
        skipList.Insert(2);
        skipList.Insert(4);
        // 遍历输出所有值
        Console.WriteLine("跳表中的所有值:");
        foreach (var item in skipList.GetAll())
        {
            Console.Write(item + " ");
        }
        Console.WriteLine();
        // 查找值
        Console.WriteLine("是否包含3:" + skipList.Contains(3));
        Console.WriteLine("是否包含6:" + skipList.Contains(6));
        // 删除值
        skipList.Remove(3);
        Console.WriteLine("删除3后是否包含3:" + skipList.Contains(3));
        Console.WriteLine("删除3后的所有值:");
        foreach (var item in skipList.GetAll())
        {
            Console.Write(item + " ");
        }
    }
}

跳表与内置SortedSet的对比

C#内置的SortedSet<T>底层采用的是红黑树实现,同样能提供O(log n)的插入、删除、查找效率,和跳表的效率相当。跳表的优势在于实现逻辑更简单,调试起来更容易,且在范围查询场景下遍历效率更高;而红黑树的性能更稳定,最坏情况下也能保证O(log n)的时间复杂度,跳表的性能依赖随机算法,极端情况下可能退化为普通链表。开发者可以根据实际场景选择使用内置的有序集合还是自定义跳表。

C#SkipList有序集合跳表实现修改时间:2026-07-24 11:15:44

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。