- 定义
- 增加了向前指针的链表叫作跳表。跳表全称叫做跳跃表,简称跳表。
- 跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。
- 跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。
- 跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。
- 时间复杂度
- 查找、插入、删除均为O(logn),最坏为O(n)
- 空间复杂度
- 实践
- 性能
- 图解实现
- 快速查找
- 跳表在原有的有序链表上面增加了多级索引,通过索引来实现
- 首先在最高级索引上查找最后一个小于当前查找元素的位置,
- 然后再跳到次高级索引继续查找,直到跳到最底层为止
![](https://camo.githubusercontent.com/d006a5b488e721a62685cb1bd06e54d0545dea75419551db8c69e99f85130a72/68747470733a2f2f7374617469633030312e6765656b62616e672e6f72672f7265736f757263652f696d6167652f34362f61392f34366432383363643832633938373135336233666530633736646662613861392e6a7067)
![22.png](https://camo.githubusercontent.com/257297660b7498c5223c5371544a95914fa60f04758c9778739d2adb30053bcd/68747470733a2f2f7069632e6c656574636f64652d636e2e636f6d2f663332373533303730363734393133393933333533646139653735613933393865393734383639393962313530333366303234396236613437643161663762312d32322e706e67)
- 高效的动态插入和删除
- 插入
- 查询:二分( O(logn) )
![](https://camo.githubusercontent.com/2a5dfbfb55f83ea9bcba74b0d2075357ae831b63c881c11e527e8c93ebcd8ddc/68747470733a2f2f7374617469633030312e6765656b62616e672e6f72672f7265736f757263652f696d6167652f36352f36632f36353337396630363531626333613763666431336162383639346334643236632e6a7067)
![](https://camo.githubusercontent.com/83a6ba06c25cbae9a9c39641473f4a33a476099a345483dac4b1b02419d8ae74/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139303933303031303630383532312e6a7065673f782d6f73732d70726f636573733d696d6167652f77617465726d61726b2c747970655f5a6d46755a33706f5a57356e6147567064476b2c736861646f775f31302c746578745f6148523063484d364c7939696247396e4c6d4e7a5a473475626d56304c334678587a4d304e4445794e5463352c73697a655f31362c636f6c6f725f4646464646462c745f3730)
- 删除
- 查询节点,因为是链表,获取它的前驱节点,最后进行链表删除节点操作
![](https://camo.githubusercontent.com/e411208bff50fc28faca123893cd4ae4e526e4296d9489c96581c0eef4fa1587/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f32303139303933303031303630383836362e6a7065673f782d6f73732d70726f636573733d696d6167652f77617465726d61726b2c747970655f5a6d46755a33706f5a57356e6147567064476b2c736861646f775f31302c746578745f6148523063484d364c7939696247396e4c6d4e7a5a473475626d56304c334678587a4d304e4445794e5463352c73697a655f31362c636f6c6f725f4646464646462c745f3730)
- 跳表索引动态更新
- 相关文章
- 代码实现
/**
* 跳表索引默认最大级数
*/
const MAX_LEVEL = 16;
/**
* 随机生成索引层数概率因子
*/
const SKIPLIST_P = 0.5;
/**
* Node 类
* @param {*} data - 存放类每个节点的数据
* @param {number} maxLevel - 当前节点处于整个跳表索引的级数
* @param {array} forwards - 当前层当前节点的下一个节点,如链表的 next 指针
*/
class Node {
constructor (data = -1,maxLevel = 0,forwards = new Array(MAX_LEVEL)){
this.data = data;
this.maxLevel = maxLevel;
this.forwards = forwards;
}
}
/**
* 跳表 类
* 跳表中存储的是正整数,并且存储的是不重复的。
*/
class SkipList {
constructor() {
// 带头链表
this.head = new Node();
// 当前跳表索引的总级数
this.levelCount = 1;
}
/**
* 静态方法:随机生成 1~MAX_LEVEL 之间的索引级数
* 配合概率因子SKIPLIST_P,使得跳表插入新元素时,每一层索引节点数约等于上一级索引节点数的2倍
*/
static randomLevel() {
let level = 1;
while (Math.random() < SKIPLIST_P && level < MAX_LEVEL) {
level++;
}
return level;
}
/**
* 查找返回跳表里面的某个数据节点,没有返回 null
* @param val - 需要查找的数据
* @returns {*}
*/
find(val) {
if(!val) return null;
let p = this.head;
let levelCount = this.levelCount;
// 从顶层开始查找,找到前一节点
// i--,依次移动到下一层级查找前一节点
for(let i = levelCount - 1;i >= 0;i--){
while(p.forwards[i] != null && p.forwards[i].data < val){
// 每层都找到一个当前节点的前一个节点,如链表的pre 指针
p = p.forwards[i];
}
}
// 回到第一层,即原始链表
if(p.forwards[0] !== undefined && p.forwards[0].data === val) {
return p.forwards[0];
}
return null;
}
/**
* 向跳表里插入数据
* @param val - 需要插入的数据
*/
insert(val) {
// 随机层数
const level = SkipList.randomLevel();
// 创建新节点
const newNode = new Node(val,level);
// 每一层小于插入节点的前一个节点数组
const update = new Array(level).fill(new Node());
// 当前节点默认为头节点
let p = this.head;
// 寻找每一层需要插入节点的前一个节点
for(let i = level - 1;i >= 0;i--){
while(p.forwards[i] !== undefined && p.forwards[i].data < val) {
p = p.forwards[i];
}
update[i] = p;
}
// 插入节点
// 将每一层节点和后面节点相关联
// 其实就是链表的插入操作(参考当前目录链表一节)
for(let i = 0;i < level;i++){
newNode.forwards[i] = update[i].forwards[i];
update[i].forwards[i] = newNode;
}
// 更新层高[添加后有可能多了n层]
if(this.levelCount < level) this.levelCount = level;
}
/**
* 移除跳表里的某个数据节点
* @param val - 需要删除的数据
* 和插入方法同理,先找到每层需要删除节点数据的前一个节点
* 然后就是执行链表的删除操作(参考当前目录链表一节)
*/
remove(val) {
const update = new Node();
let p = this.head;
let levelCount = this.levelCount;
for(let i = levelCount - 1;i >= 0;--i){
while(p.forwards[i] !== undefined && p.forwards[i].data < val) {
p = p.forwards[i];
}
update[i] = p;
}
if(p.forwards[0] !== undefined && p.forwards[0].data === val) {
for(let i = levelCount - 1;i >= 0;i--){
if(update[i].forwards[i] !== undefined && update[i].forwards[i].data === val) {
update[i].forwards[i] = update[i].forwards[i].forwards[i];
}
}
}
// 同样更新层高[删除后有可能少了n层]
while(this.levelCount > 1 && this.head.forwards[this.levelCount] === undefined) {
this.levelCount--;
}
}
// 打印跳表里的所有数据
printRes() {
let p = this.head;
while(p.forwards[0] !== undefined) {
console.log(p.forwards[0].data + ' ');
p = p.forwards[0];
}
}
}
// 测试
let test = () => {
let list = new SkipList();
// 顺序插入
for (let i = 1; i <= 10; i++) {
list.insert(i);
}
// 输出
// 1,2,3,4,5,6,7,8,9,10
list.printRes();
// 查找
//
// {
// data: 8
// maxLevel: 1
// forwards: (16) [Node, empty × 15]
// }
list.find(8)
// 删除
list.remove(8)
// 输出
// 1,2,3,4,5,6,7,9,10
list.printRes()
}
test();