数据结构——跳表(附C++实现代码)

定义

跳表(Skip List)是一种可以用来快速查找、添加和删除数据的数据结构,它通过维护一个多层次的链表系统来实现高效的搜索性能。每个层次中的链表都是下一个层次链表的“快进”版本,最底层包含所有的元素,而越往上的层次则包含了越少的元素,这些元素是从下层中随机选择出来的。

结构

跳表类定义(参考了Redis的zset源码)
类维护的信息
节点名类型描述
MAXLstatic const unsigned int跳表支持的最大层高
Pstatic const unsigned int每一层上升的概率 1 / P
sizeKey节点键值,允许重复(Redis中zset的score成员变量)
levelValue节点内存储的值,具有唯一性和可排序性(Redis中zset的obj成员变量)
headerNode*指向底层中前一个节点
tailNode*指向每一层的下一个节点以及到达下一个节点的路径长
mpstd::unordered_map<Value,Key>记录Value->Key的哈希表
template<typename Key,typename Value,typename CompareKey=std::less<Key>,typename CompareValue=std::less<Value>>
class SkipList{
private:
    // 最大层数
    static const unsigned int MAXL = 32;
    // 概率 1 / P
    static const unsigned int P = 4;
    struct Node{
       ......
    };
    unsigned int size;
    unsigned int level;
    Node *header,*tail;
    CompareKey compareKey;
    CompareValue compareValue;
    // 用于根据Value找到Key
    std::unordered_map<Value,Key> mp;
    unsigned int randomLevel();
    Node* createNode(unsigned int level,Key key,Value value);
    void deleteNode(Node *node, Node **update);
    Node* findNode(Key key,Value value,Node **update=nullptr,int* rank=nullptr);

public:
    SkipList(){
        this->size = 0;
        this->level = 1;
        this->header = new Node();
        this->header->level = new typename SkipList<Key,Value,CompareKey,CompareValue>::Node::NodeLevel[this->MAXL];
        for(int i=0;i<this->MAXL;i++){
            this->header->level[i].forward = nullptr;
            this->header->level[i].span = 0;
        }
        this->header->backward = nullptr;
        this->tail = nullptr;
        this->compareKey = CompareKey();
        this->compareValue = CompareValue();
    }

    int insert(Key key,Value value);
    int update(Key key,Value value);
    int remove(Value value);
    void traversal();
};
节点维护的信息
节点名类型描述
keyKey节点键值,允许重复(Redis中zset的score成员变量)
valueValue节点内存储的值,具有唯一性和可排序性(Redis中zset的obj成员变量)
backwardNode*指向底层中前一个节点
levelNodeLevel*指向每一层的下一个节点以及到达下一个节点的路径长
节点结构定义
struct Node{
    Key key;
    Value value;
    Node* backward;
    
    // Redis采用灵活数组成员level[],而非*level
    // 优点
    // 1.保证成员的内存空间连续,避免造一个节点却要多次分配使得可用内存空间碎片化
    // 2.减少内存分配次数,一次分配即可
    // 3.由于空间连续,缓存命中率高,提高访问效率
    // 注意
    // 由于采用灵活数组成员时,结构体大小不固定,因此无法采用C++的关键字new来创建,只能用c的malloc来指定分配空间的大小
    // 同时如果使用malloc创建结构体时不会自动调用构造函数,又必须使用new关键字,因此此处还是使用*level
    // Redis的value实质是一个指向内容的结构体指针
    struct NodeLevel{
        // 该层的下一个节点
        Node *forward;
        // 到达该层下一个节点的路径长
        unsigned int span;
    }*level;
};

跳表的查询

实现流程

从头节点开始,以最高层为起点,从高到低进行搜索,不断找到当前层最接近被查询节点但是小于被查询节点的节点,最终如同下楼梯一般找到最底层中第一个大于或等于被查询节点的节点,判断这个节点是否等于被查询节点,相同则返回这个节点,反之则返回nullptr

实现代码

update参数:当其不为nullptr时,用于保存每一层最接近被查询节点但小于被查询节点的节点作为返回值的一部分,用于插入或删除节点

rank参数:当其不为nullptr时,用于保存每一层最接近被查询节点但小于被查询节点的节点在最底层的排名作为返回值的一部分,用于插入新节点时更新span

/**
 * @brief 查找节点
 * @param key 被查找节点的键
 * @param value 被查找节点保存的键值
 * @param update 当其不为nullptr时,用于保存每一层最接近被查询节点但小于被查询节点的节点作为返回值的一部分,用于插入或删除节点
 * @param rank 当其不为nullptr时,用于保存每一层最接近被查询节点但小于被查询节点的节点在最底层的排名作为返回值的一部分,用于插入新节点时更新span
 * @return Node* 若存在则返回节点,不存在则返回nullptr
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
typename SkipList<Key,Value,CompareKey,CompareValue>::Node* SkipList<Key,Value,CompareKey,CompareValue>::findNode(Key key,Value value,Node **update,int* rank){
    Node* x = this->header;
    for(int i = this->level;i>=0;i--){
        if(rank) rank[i] = i==this->level-1 ? 0 : rank[i+1];
        while(x->level[i].forward && 
            (this->compareKey(x->level[i].forward->key,key) ||
                (x->level[i].forward->key==key &&
                    this->compareValue(x->level[i].forward->value,value)))){
            if(rank) rank[i] += x->level[i].span;
            x = x->level[i].forward;
        }
        if(update) update[i] = x;
    }
    x = x->level[0].forward;
    return x && (x->key==key && x->value==value) ? x : nullptr;
}

跳表的插入

获得新节点的层高

实现流程

从第一层开始,每往上一层都有1/P的概率继续往上升一层,直至触发失败,保证最后返回的层数小于等于跳表支持的最大层高

实现代码
/**
 * @brief  随机获得新节点的层高
 * @return unsigned int 新节点的层高
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
unsigned int SkipList<Key,Value,CompareKey,CompareValue>::randomLevel(){
    unsigned int level=1;
    while((rand() & 0xffff) < (0xffff / this->P)) level++;
    return level<this->MAXL ? level : this->MAXL;
}

创建新节点

实现流程

根据获得的新节点的层高,创建新节点,记录该value->key到哈希表中,跳表节点数量加一

实现代码
/**
 * @brief 创建新节点
 * @param level 新节点层高
 * @param key 新节点的键
 * @param value 新节点存储的值
 * @return Node* 创建的新节点
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
typename SkipList<Key,Value,CompareKey,CompareValue>::Node* SkipList<Key,Value,CompareKey,CompareValue>::createNode(unsigned int level,Key key,Value value){
    Node* node = new Node();
    node->key = key;
    node->value = value;
    node->level = new typename SkipList<Key,Value,CompareKey,CompareValue>::Node::NodeLevel[level];
    this->mp[value] = key;
    this->size++;
    return node;
}

插入节点

实现流程
  1. 利用哈希表判断保存的值为value的节点是否存在,若存在,则指向更新操作,返回1

  2. 传递update和rank进行查询操作,其中update[i]保存了第i层最接近插入节点但小于插入节点的节点,rank[i]保存了第i层最接近插入节但小于插入节的节点在最底层的排名

  3. 获得新节点的层高,若新节点的层高高于跳表当前的层高,将跳表的层高提升到新节点的层高,并做相应的更新

  4. 创建并插入新节点node,同时更新update保存的节点的span

    • 高于插入节点的层高的节点update[i],到该层下一个节点的span加一

    • 低于插入节点的层高的节点update[i],其到插入节点node的距离可以分成两段:①update[i]到update[0]的距离,即为rank[0]-rank[i]②update[0]到node的距离,即为1

      而node到下一个节点的距离即为插入前update[i]到下一个节点的距离减去rank[0]-rank[i](update[i]到update[0]的距离)

  5. 继续更新backward

实现代码
/**
 * @brief 插入节点
 * @param key 新节点的key
 * @param value 新节点的value
 * @return int 插入结果,返回0表示插入成功,返回1表示已有节点的保存的值等于value,更新节点的键为key
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
int SkipList<Key,Value,CompareKey,CompareValue>::insert(Key key,Value value){
    if(this->mp.count(value)){
        this->update(key,value);
        return 1;
    }
    Node *update[this->MAXL],*node;
    // rank[i]表示第i层最接近node但小于node的节点排名
    int rank[this->MAXL];
    node = this->findNode(key,value,update,rank);
    assert(!node);
    int level = this->randomLevel();
    while(this->level < level){
        update[this->level] = this->header;
        this->header->level[this->level].span = this->size;
        rank[this->level] = 0;
        this->level++;
    }
    node = this->createNode(level,key,value);
    // 插入新节点并更新span
    for(int i=this->level-1;i>=0;i--){
        if(i>=level){
            update[i]->level[i].span++;
            continue;
        }
        // 插入新节点
        node->level[i].forward = update[i]->level[i].forward;
        update[i]->level[i].forward = node;
        // 更新span
        node->level[i].span = update[i]->level[i].span - (rank[0]-rank[i]);
        update[i]->level[i].span = (rank[0]-rank[i]) + 1;
    }
    // 更新backward
    node->backward = update[0] == this->header ? nullptr:update[0];
    if(node->level[0].forward){
        node->level[0].forward->backward = node;
    }else{
        this->tail = node;
    }
    return 0;
}

跳表的删除

根据已知节点删除节点

实现流程
  1. 令node为被删除的节点,遍历每一层最接近node但小于node的节点,若其下一个节点正是node,则进行逻辑删除,同时修改span为node到该层下一个节点的距离减一;反之若下一个节点不等于node,说明当前层的高度高于node的最高层,其到下一个节点的距离直接减一即可
  2. 重建被删除节点前后的backward
  3. 删除节点可能导致跳表的层高减小,需要进行检查并更新
  4. 删除哈希表中节点的value->key关系,物理删除node,跳表节点数量减一
实现代码
/**
 * @brief 删除节点
 * @param node 被删除的节点
 * @param update 每一层最接近node但小于node的节点
 * @return Node* 创建的新节点
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
void SkipList<Key,Value,CompareKey,CompareValue>::deleteNode(Node *node, Node **update){
    for(int i=this->level-1;i>=0;i--){
        if(update[i]->level[i].forward == node){
            update[i]->level[i].forward = node->level[i].forward;
            update[i]->level[i].span += node->level[i].span -1;
        }else{
            update[i]->level[i].span--;
        }
    }
    if(node->level[0].forward){
        node->level[0].forward->backward = node->backward;
    }else{
        this->tail = node->backward;
    }
    while(this->level>1 && this->header->level[this->level-1].forward == nullptr){
        this->level--;
    }
    this->mp.erase(node->value);
    delete[] node->level;
    delete node;
    this->size--;
}

根据value删除节点

实现流程
  1. 利用哈希表判断保存的键值为value的节点是否存在,不存在则返回1
  2. 查找节点并执行根据已知节点删除节点的操作
实现代码
/**
 * @brief 根据value查找节点并删除节点
 * @param value 被删除节点的value
 * @return int 删除结果,返回0表示删除成功,返回1表示节点不存在删除失败
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
int SkipList<Key,Value,CompareKey,CompareValue>::remove(Value value){
    if(!this->mp.count(value)) return 1;
    Node *update[this->MAXL], *node;
    node = this->findNode(mp[value],value,update);
    assert(node);
    this->deleteNode(node,update);
    return 0;
}

跳表的更新

实现流程

根据value查找节点并更新key值

1.查询并得到被更新节点

2.判断能否直接更新该节点为新key,若能则直接更新,若不能则先执行根据已知节点删除节点的操作,再重新插入该节点

实现代码
/**
 * @brief 根据value查找节点并更新key值
 * @param key 被更新节点的新key
 * @param value 被更新节点的value
 * @return int 插入结果,返回0表示插入成功
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
int SkipList<Key,Value,CompareKey,CompareValue>::update(Key key,Value value){
    assert(this->mp.count(value));
    Node *update[this->MAXL],*node;
    node = this->findNode(mp[value],value,update);
    assert(node);
    if((!node->backward || node->backward->key < key) &&
        (!node->level[0].forward || node->level[0].forward->key > key)){
        node->key = key;
        mp[value] = key;
        return 0;
    }
    this->deleteNode(node,update);
    this->insert(key,value);
    return 0;
}

参考资料

Redis t_zset.c https://github.com/redis/redis/blob/unstable/src/t_zset.c

完整代码

#include <cassert>
#include <functional>
#include <iostream>
#include <unordered_map>

template<typename Key,typename Value,typename CompareKey=std::less<Key>,typename CompareValue=std::less<Value>>
class SkipList{
private:
    // 最大层数
    static const unsigned int MAXL = 32;
    // 概率 1 / P
    static const unsigned int P = 4;

    struct Node{
        Key key;
        Value value;
        Node* backward;

        // Redis采用灵活数组成员level[],而非*level
        // 优点
        // 1.保证成员的内存空间连续,避免造一个节点却要多次分配使得可用内存空间碎片化
        // 2.减少内存分配次数,一次分配即可
        // 3.由于空间连续,缓存命中率高,提高访问效率
        // 注意
        // 由于采用灵活数组成员时,结构体大小不固定,因此无法采用C++的关键字new来创建,只能用c的malloc来指定分配空间的大小
        // 由于使用malloc不会自动调用构造函数,必须使用new关键字,因此此处还是使用*level
        // Redis的value实质是一个指向内容的结构体指针
        struct NodeLevel{
            // 该层的下一个节点
            Node *forward;
            // 到达该层下一个节点的路径长
            unsigned int span;
        }*level;
    };
    unsigned int size;
    unsigned int level;
    Node *header,*tail;
    CompareKey compareKey;
    CompareValue compareValue;
    // 用于根据Value找到Key
    std::unordered_map<Value,Key> mp;
    unsigned int randomLevel();
    Node* createNode(unsigned int level,Key key,Value value);
    void deleteNode(Node *node, Node **update);
    Node* findNode(Key key,Value value,Node **update=nullptr,int* rank=nullptr);

public:
    SkipList(){
        this->size = 0;
        this->level = 1;
        this->header = new Node();
        this->header->level = new typename SkipList<Key,Value,CompareKey,CompareValue>::Node::NodeLevel[this->MAXL];
        for(int i=0;i<this->MAXL;i++){
            this->header->level[i].forward = nullptr;
            this->header->level[i].span = 0;
        }
        this->header->backward = nullptr;
        this->tail = nullptr;
        this->compareKey = CompareKey();
        this->compareValue = CompareValue();
    }

    int insert(Key key,Value value);
    int update(Key key,Value value);
    int remove(Value value);
    void traversal();
};

/**
 * @brief  随机获得新节点的层高
 * @return unsigned int 新节点的层高
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
unsigned int SkipList<Key,Value,CompareKey,CompareValue>::randomLevel(){
    unsigned int level=1;
    while((rand() & 0xffff) < (0xffff / this->P)) level++;
    return level<this->MAXL ? level : this->MAXL;
}

/**
 * @brief 创建新节点
 * @param level 新节点层高
 * @param key 新节点的键
 * @param value 新节点存储的值
 * @return Node* 创建的新节点
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
typename SkipList<Key,Value,CompareKey,CompareValue>::Node* SkipList<Key,Value,CompareKey,CompareValue>::createNode(unsigned int level,Key key,Value value){
    Node* node = new Node();
    node->key = key;
    node->value = value;
    node->level = new typename SkipList<Key,Value,CompareKey,CompareValue>::Node::NodeLevel[level];
    this->mp[value] = key;
    this->size++;
    return node;
}

/**
 * @brief 删除节点
 * @param node 被删除的节点
 * @param update 每一层最接近node但小于node的节点
 * @return Node* 创建的新节点
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
void SkipList<Key,Value,CompareKey,CompareValue>::deleteNode(Node *node, Node **update){
    for(int i=this->level-1;i>=0;i--){
        if(update[i]->level[i].forward == node){
            update[i]->level[i].forward = node->level[i].forward;
            update[i]->level[i].span += node->level[i].span -1;
        }else{
            update[i]->level[i].span--;
        }
    }
    if(node->level[0].forward){
        node->level[0].forward->backward = node->backward;
    }else{
        this->tail = node->backward;
    }
    while(this->level>1 && this->header->level[this->level-1].forward == nullptr){
        this->level--;
    }
    this->mp.erase(node->value);
    delete[] node->level;
    delete node;
    this->size--;
}

/**
 * @brief 查找节点
 * @param key 被查找节点的键
 * @param value 被查找节点保存的键值
 * @param update 当其不为nullptr时,用于保存每一层最接近被查询节点但小于被查询节点的节点作为返回值的一部分,用于插入或删除节点
 * @param rank 当其不为nullptr时,用于保存每一层最接近被查询节点但小于被查询节点的节点在最底层的排名作为返回值的一部分,用于插入新节点时更新span
 * @return Node* 若存在则返回节点,不存在则返回nullptr
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
typename SkipList<Key,Value,CompareKey,CompareValue>::Node* SkipList<Key,Value,CompareKey,CompareValue>::findNode(Key key,Value value,Node **update,int* rank){
    Node* x = this->header;
    for(int i = this->level;i>=0;i--){
        if(rank) rank[i] = i==this->level-1 ? 0 : rank[i+1];
        while(x->level[i].forward && 
            (this->compareKey(x->level[i].forward->key,key) ||
                (x->level[i].forward->key==key &&
                    this->compareValue(x->level[i].forward->value,value)))){
            if(rank) rank[i] += x->level[i].span;
            x = x->level[i].forward;
        }
        if(update) update[i] = x;
    }
    x = x->level[0].forward;
    return x && (x->key==key && x->value==value) ? x : nullptr;
}

/**
 * @brief 插入节点
 * @param key 新节点的key
 * @param value 新节点的value
 * @return int 插入结果,返回0表示插入成功,返回1表示已有节点的保存的值等于value,更新节点的键为key
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
int SkipList<Key,Value,CompareKey,CompareValue>::insert(Key key,Value value){
    if(this->mp.count(value)){
        this->update(key,value);
        return 1;
    }
    Node *update[this->MAXL],*node;
    // rank[i]表示第i层最接近node但小于node的节点排名
    int rank[this->MAXL];
    node = this->findNode(key,value,update,rank);
    assert(!node);
    int level = this->randomLevel();
    while(this->level < level){
        update[this->level] = this->header;
        this->header->level[this->level].span = this->size;
        rank[this->level] = 0;
        this->level++;
    }
    node = this->createNode(level,key,value);
    // 插入新节点并更新span
    for(int i=this->level-1;i>=0;i--){
        if(i>=level){
            update[i]->level[i].span++;
            continue;
        }
        // 插入新节点
        node->level[i].forward = update[i]->level[i].forward;
        update[i]->level[i].forward = node;
        // 更新span
        node->level[i].span = update[i]->level[i].span - (rank[0]-rank[i]);
        update[i]->level[i].span = (rank[0]-rank[i]) + 1;
    }
    // 更新backward
    node->backward = update[0] == this->header ? nullptr:update[0];
    if(node->level[0].forward){
        node->level[0].forward->backward = node;
    }else{
        this->tail = node;
    }
    return 0;
}

/**
 * @brief 根据value查找节点并更新key值
 * @param key 被更新节点的新key
 * @param value 被更新节点的value
 * @return int 插入结果,返回0表示插入成功
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
int SkipList<Key,Value,CompareKey,CompareValue>::update(Key key,Value value){
    assert(this->mp.count(value));
    Node *update[this->MAXL],*node;
    node = this->findNode(mp[value],value,update);
    assert(node);
    if((!node->backward || node->backward->key < key) &&
        (!node->level[0].forward || node->level[0].forward->key > key)){
        node->key = key;
        mp[value] = key;
        return 0;
    }
    this->deleteNode(node,update);
    this->insert(key,value);
    return 0;
}

/**
 * @brief 根据value查找节点并删除节点
 * @param value 被删除节点的value
 * @return int 删除结果,返回0表示删除成功,返回1表示节点不存在删除失败
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
int SkipList<Key,Value,CompareKey,CompareValue>::remove(Value value){
    if(!this->mp.count(value)) return 1;
    Node *update[this->MAXL], *node;
    node = this->findNode(mp[value],value,update);
    assert(node);
    this->deleteNode(node,update);
    return 0;
}

/**
 * @brief 跳表的遍历
 * @return void 
 */
template<typename Key,typename Value,typename CompareKey,typename CompareValue>
void SkipList<Key,Value,CompareKey,CompareValue>::traversal(){
    for(Node* node = this->header->level[0].forward;node;node=node->level[0].forward){
        std::cout << node->key << "|" << node->value << "  ";
    }
    std::cout << std::endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值