0
点赞
收藏
分享

微信扫一扫

跳跃表SkipList

少_游 2022-04-06 阅读 112
数据结构

文章目录

一、跳跃表的概念

跳跃表具有如下性质:

  1. 由很多层链表组成,每一层都是一个有序链表
  2. 最底层(level 1)的链表包含所有元素
  3. 如果一个元素出现在level i层的链表中,则它在level i之下的链表也都会出现
  4. 每个节点都包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素跳跃表的增加、删除、查询操作时间复杂度和红黑树一样,也是 O ( l o g 2 N ) O(log_2N) O(log2N)

相比于红黑树,它的优势是:

  1. 实现起来更加简单
  2. 跳跃表的增加、删除操作只会改动局部,不像红黑树的增加、删除操作,因为需要节点重新着色和旋转,可能整棵树都要进行调整,这就导致了并发环境下红黑树锁的粒度大。并发环境下,跳跃表只需要对插入元素影响的区间加锁即可,跳跃表加锁的粒度会更小一些,并发能力更强
  3. 因为跳跃表的每一层都是一个有序的链表,因此范围查找非常方便,优于红黑树的范围搜索的跳跃表相比于红黑树,是用空间换时间(level 2层开始每一层都有会存储重复的数据),因此占用的内存空间比红黑树大

跳跃表相对于红黑树而言,是在空间换时间,占用内存比较大。红黑树中,所有元素值只是存储一个节点的,但是在跳跃表中,所有的元素不仅仅在最下面那层出现,而且根据抛硬币选出来的元素在上面层进行存储,以此类推上去,模拟了一个二分查找搜索树的过程,简化了红黑树,达到O(logn)的搜索和删除的时间复杂度,而且实现起来比较简单

在实际应用场景中,跳跃表和红黑树在很多情况下是可以互换的

在这里插入图片描述
redis的有序集合也是用SkipList实现的,增、删、查都是 O ( l o g 2 N ) O(log_2N) O(log2N),只要能定义比较方式,就可以用SkipList存储

关于SkipList的层数,我们采用符合二项分布的函数确定是否将某个节点存储至上一层。既然是符合二项分布,当数据量较大时,一般来说对于相邻两层,上层节点数量会是下层节点数量的 1 / 2 1/2 1/2,这就很类似于二叉树了

在这里插入图片描述

二、跳跃表的find

我们查找117的过程如下:
在这里插入图片描述

  1. 先在最上层的链表找第一个大于117的数,从这个大于117节点的前一个节点往下寻找,于是从37往下查找
  2. 同理,从71往下找
  3. 在最下层节点找到117
bool find(int data) const{
    Node* pre = head_;
    Node* cur = pre->next_;
    while (true) {
        if (cur != nullptr) {
            if (cur->data_ < data) {
                pre = cur;
                cur = cur->next_;
            }
            else if (cur->data_ > data) {
                if (pre->down_ == nullptr) {
                    // 已经到了level 1
                    return false;
                }
                pre = pre->down_;
                cur = pre->next_;
            }
            else {
                return true;
            }
        }
        else {
            // cur == nullptr
            if (pre->down_ == nullptr) {
                // 已经到了level 1
                return false;
            }
            else {
                pre = pre->down_;
                cur = pre->next_;
            }
        }
    }
}

三、跳跃表的insert

在这里插入图片描述
如果我们要插入119这个数字,通过抛硬币,决定119要插几行

  • K=2:跳表本身是3行,插2行是没有问题的,在最后一行按序插入,因为要插2行,所以在第二行也插了一个119
  • K=4:跳表只有3层,但是最终我们也是要插4层的,也就是说,在最上层(第4层)就只有一个数字119
  • K=5:119这个数字要添加到5层,但是跳表只有3层,所以我们最终也是只插4层就可以了

在这里插入图片描述
编写代码顺序:

  • 查找data,重复则不插入
  • 获取level,若level过大,调整level使得skiplist只增长一层
  • 用数组存储要插入的level个节点,并纵向连接
  • 找到最先插入节点的第level层链表的头节点,往后遍历找到合适的位置插入数组中的节点,然后再到下层链表找到合适的位置插入,直到在底层链表插入
void insert(int data) {
    if (find(data)) {
        // 不插入重复数据
        return;
    }
    int level = get_level();
    if (level > head_->level_) {
        // level过大,只增长一层
        level = head_->level_ + 1;
        // 需要增长,则构造新的头节点
        HeadNode* new_head = new HeadNode(level);
        new_head->down_ = head_;
        head_ = new_head;
    }
    // 先创建一个指针数组,用于存放data节点的地址
    Node** new_nodes = new Node * [level];
    for (int i = level - 1; i >= 0; i--) {
        new_nodes[i] = new Node(data);
        if (i != level - 1) {
            new_nodes[i]->down_ = new_nodes[i + 1];
        }
    }

    // 若获取的level本身很小,需要将insert_head指向首次插入的层
    Node* insert_head = head_;
    for (int i = head_->level_; i > level; i--) {
        insert_head = insert_head->down_;
    }

    Node* pre = insert_head;
    Node* cur = pre->next_;
    for (int i = 0; i < level; i++) {
        while (cur != nullptr && cur->data_ < data) {
            // 找第一个大于data的节点
            pre = cur;
            cur = cur->next_;
        }
        // 找到第一个大于data的节点后,将数组内存放的data节点插入pre和cur之间
        new_nodes[i]->next_ = cur; 
        pre->next_ = new_nodes[i];
        pre = pre->down_;
        if (pre != nullptr) {
            cur = pre->next_;
        }
        
    }
    delete[] new_nodes;
    new_nodes = nullptr;
}

四、跳跃表的remove

在这里插入图片描述

  • 删除71:先查找到level 2的71,修改37节点的指针域,然后把71这一整列的节点全部delete
  • 删除37:在level 3中找到37,修改37前面节点的指针域,并把37这一整列的节点全部delete

需要注意:如果此时链表的最高层只有一个数据节点119,我们在删除操作中把最高层的数据节点119删除了,那么此时最高层链表就只剩一个头节点了,而这个头节点也是要删除的
在这里插入图片描述

编写代码顺序:

  • 查找第一个data的数据,现在上层链表找第一个大于data的节点,找到则删除,并且pre向下走;找不到则pre直接向下走
  • 在同一层链表查找data时,一直向后遍历,直到找到data或cur为空
void remove(int data) {
    // 头节点不可能为空
    Node* pre = head_;
    Node* cur = pre->next_;
    while (true) {
        if (cur != nullptr) {
            if (cur->data_ < data) {
                pre = cur;
                cur = cur->next_;
                continue;
            }
            else if (cur->data_ == data) {
                pre->next_ = cur->next_;
                delete cur;
                if (head_->next_ == nullptr) {
			        // 如果只剩头节点了,说明pre肯定也指向头节点
			        delete head_;
			        head_ = static_cast<HeadNode*>(pre);
			    }
            }
        }
		// 这里 (cur==nullptr) || (删除节点了pre准备向下走) || (找到了第一个大于data的节点,pre准备向下走)
        if (pre->down_ == nullptr) {
            // 已经到了level 1,且cur==nullptr,说明已经遍历到最右下角的节点了
            return;
        }
        pre = pre->down_;
        cur = pre->next_;
    }
}

五、完整代码

class SkipList {
public:
    SkipList() {
        head_ = new HeadNode(1);  // 跳跃表初始化为第一层
    }
    
    ~SkipList() {
        Node* next_head = head_;
        int level = head_->level_;
        for (int i = level; i > 0; i--) {
            Node* pre = next_head;
            Node* cur = pre->next_;
            next_head = next_head->down_;
            while (cur != nullptr) {
                delete pre;
                pre = cur;
                cur = cur->next_;
            }
        }

    }

    bool find(int data) const{
        Node* pre = head_;
        Node* cur = pre->next_;
        while (true) {
            if (cur != nullptr) {
                if (cur->data_ < data) {
                    pre = cur;
                    cur = cur->next_;
                }
                else if (cur->data_ > data) {
                    if (pre->down_ == nullptr) {
                        // 已经到了level 1
                        return false;
                    }
                    pre = pre->down_;
                    cur = pre->next_;
                }
                else {
                    return true;
                }
            }
            else {
                // cur == nullptr
                if (pre->down_ == nullptr) {
                    // 已经到了level 1
                    return false;
                }
                else {
                    pre = pre->down_;
                    cur = pre->next_;
                }
            }
        }
    }

    void insert(int data) {
        if (find(data)) {
            // 不插入重复数据
            return;
        }
        int level = get_level();
        if (level > head_->level_) {
            // level过大,只增长一层
            level = head_->level_ + 1;
            // 需要增长,则构造新的头节点
            HeadNode* new_head = new HeadNode(level);
            new_head->down_ = head_;
            head_ = new_head;
        }
        // 先创建一个指针数组,用于存放data节点的地址
        Node** new_nodes = new Node * [level];
        for (int i = level - 1; i >= 0; i--) {
            new_nodes[i] = new Node(data);
            if (i != level - 1) {
                new_nodes[i]->down_ = new_nodes[i + 1];
            }
        }

        // 若获取的level本身很小,需要将insert_head指向首次插入的层
        Node* insert_head = head_;
        for (int i = head_->level_; i > level; i--) {
            insert_head = insert_head->down_;
        }

        Node* pre = insert_head;
        Node* cur = pre->next_;
        for (int i = 0; i < level; i++) {
            while (cur != nullptr && cur->data_ < data) {
                // 找第一个大于data的节点
                pre = cur;
                cur = cur->next_;
            }
            // 找到第一个大于data的节点后,将数组内存放的data节点插入pre和cur之间
            new_nodes[i]->next_ = cur; 
            pre->next_ = new_nodes[i];
            pre = pre->down_;
            if (pre != nullptr) {
                cur = pre->next_;
            }
            
        }
        delete[] new_nodes;
        new_nodes = nullptr;
    }

    void remove(int data) {
        // 头节点不可能为空
        Node* pre = head_;
        Node* cur = pre->next_;
        while (true) {
            if (cur != nullptr) {
                if (cur->data_ < data) {
                    pre = cur;
                    cur = cur->next_;
                    continue;
                }
                else if (cur->data_ == data) {
                    pre->next_ = cur->next_;
                    delete cur;
                    if (head_->next_ == nullptr) {
                        // 如果只剩头节点了,说明pre肯定也指向头节点
                        delete head_;
                        head_ = static_cast<HeadNode*>(pre);
                    }
                }
            }

            if (pre->down_ == nullptr) {
                // 已经到了level 1,且cur==nulptr,说明已经遍历到最右下角的节点了
                return;
            }
            pre = pre->down_;
            
            cur = pre->next_;
            
        }
    }

    void show() const {
        Node* head = head_;
        while (head != nullptr) {
            Node* cur = head->next_;
            while (cur != nullptr) {
                cout << cur->data_ << " ";
                cur = cur->next_;
            }
            cout << endl;
            head = head->down_;
        }
        cout << endl;
    }

private:
    int get_level() {
        int n = 1;
        while (rand() % 2 == 1) {
            n++;
        }
        return n;
    }

private:
    // 普通节点类型
    struct Node {
        Node(int data = 0)
            : data_(data)
            , next_(nullptr)
            ,down_(nullptr)
        {}
        int data_;
        Node* next_;
        Node* down_;
    };
    
    // 头节点类型
    struct HeadNode : public Node {
        HeadNode(int level)
            : level_(level)
        {}
        int level_;  // 记录跳跃表的层数
    };

private:
    HeadNode* head_;
};

举报

相关推荐

0 条评论