通过之前的简介,我们已经了解到跳表是一种极为高效的数据结构,其独特之处在于节点层级的设定,这一层级是通过一个随机过程来选择的。为了实现这一过程,我们设计了一个专门的成员函数,通过该函数,可以在跳表中实现随机层级的选择。
1. 随机层级的选择过程
想象你在玩抛硬币游戏,决定你在游戏中可以前进多远:
- 起点:每当添加一个新元素,从地面层(即跳表的最底层,第0层)开始
- 抛硬币决定层级:
正面:向上升一层并继续抛硬币
反面:停止升层,确定当前层级作为元素的最终层级 - 重复过程:持续此过程直至得到反面为止
这个随机过程的结果是:
许多元素会停留在较低层级,一部分元素会到达较高层级,极少数元素可能会到达非常高的层级。
2. 为什么采用随机过程
- 平衡性:随机层级分配自然保持跳表平衡,无需额外操作(如AVL或红黑树的旋转)
- 效率:随机分配层级保证节点在各层均匀分布,实现对数时间复杂度的查找、插入和删除
- 简单性:这种方法易于实现且效果显著,使跳表成为性能优异的简洁数据结构
来看具体代码的实现:
template <typename K, typename V>
int SkipList<K, V>::get_random_level() {
// 初始化层级:每个节点至少出现在第一层。
int k = 1;
// 随机层级增加:使用 rand() % 2 实现抛硬币效果,决定是否升层。
while (rand() % 2) {
k++;
}
// 层级限制:确保节点层级不超过最大值 _max_level。
k = (k < _max_level) ? k : _max_level;
// 返回层级:返回确定的层级值,决定节点插入的层。
return k;
};
相比于前面的跳表简介一节,这一节我们的随机过程的实现方式中多了一行代码。
k = (k < _max_level) ? k : _max_level;
这是因为在具体的实现中,我们的跳表有一个最大层级限制,限制了索引的最高层,所以这里额外添加一行代码对随机生成的层级进行限制。
这个函数通过简单的随机过程(模拟抛硬币),以概率方式决定节点的层级,同时确保层级不会超过设定的最大值。这种随机层级分配策略有助于保持跳表的性能,确保操作(如搜索、插入、删除)的时间复杂度在平均情况下接近 O(log n)。