holyya.com
2025-09-04 15:12:03 Thursday
登录
文章检索 我的文章 写文章
C++ 实现跳表
2023-06-30 01:30:05 深夜i     --     --
C++ 跳表 数据结构 算法 实现

跳表是一种基于有序链表的数据结构,它可以提供快速的查找,插入和删除操作。它的优势在于它的时间复杂度比纯链表快,但它的实现比红黑树和 AVL 树要简单。

下面我们将介绍 C++ 实现跳表的过程。我们首先需要定义节点类和跳表类。

节点类表示跳表的每个节点,包含了节点值,以及每一层的前向和后向指针。相比于纯链表,跳表节点还新增了上下两个指针,用于跨越多层节点。


class skipnode {

public:

  skipnode() {}

  skipnode(int _value) : value(_value) {}

  int value;

  skipnode* left = nullptr;

  skipnode* right = nullptr;

  skipnode* up = nullptr;

  skipnode* down = nullptr;

};

随后我们需要定义跳表类。跳表类包含了跳表的头节点和尾节点,以及每一层的头节点和尾节点。


class skiplist {

public:

  skiplist() {}

  int size() const;

  void insert(const int value);

  void erase(const int value);

  bool find(const int value) const;

private:

  skipnode* head = nullptr;

  skipnode* tail = nullptr;

  std::vector<skipnode*> heads = {};

  std::vector<skipnode*> tails = {};

  const float p = 0.25;

  const int max_level = 16;

};

其中,私有成员中的 `p` 表示每一层节点被升高到上一层的概率,一般都是设为 1/4; `max_level` 表示跳表的最大层数; `heads` 和 `tails` 分别表示每一层的头节点和尾节点,我们可以将它们放在一个 vector 中,方便遍历和查找。

下面是构造函数的实现:


skiplist::skiplist() {

  head = new skipnode();

  tail = new skipnode(INT_MAX);

  head->right = tail;

  tail->left = head;

  heads.resize(max_level);

  tails.resize(max_level);

  heads[max_level - 1] = head;

  tails[max_level - 1] = tail;

  for (int i = max_level - 2; i >= 0; --i) {

    heads[i] = new skipnode();

    tails[i] = new skipnode(INT_MAX);

    heads[i]->down = heads[i + 1];

    heads[i]->right = tails[i];

    tails[i]->down = tails[i + 1];

    tails[i]->left = heads[i];

    heads[i + 1]->up = heads[i];

    tails[i + 1]->up = tails[i];

  }

}

在上面的构造函数中,我们首先创建了两个哨兵节点: `head` 和 `tail`。它们分别作为跳表的头节点和尾节点,不包含实际数据, `head->right` 和 `tail->left` 用于遍历节点;

我们然后根据最大层数 `max_level`,和每层节点的前向和后向指针,创建多级节点。我们将最高层的头节点 `heads[max_level - 1]` 设为 `head`,尾节点 `tails[max_level - 1]` 设为 `tail`。对于其余层,我们在每一层的头和尾节点之间插入哨兵节点。

接下来是查找、插入、删除和打印跳表的代码实现:


int skiplist::size() const {

  int count = 0;

  skipnode* iter = head;

  while (iter->down)

    iter = iter->down;

  

  iter = iter->right;

  while (iter) {

    if (iter == tail)

      return count;

    

    iter = iter->right;

    ++count;

  }

  return count;

}

bool skiplist::find(const int value) const {

  skipnode* iter = head;

  while (iter) {

    if (iter == tail || iter->value > value)

      iter = iter->down;

    

    else if (iter->value == value)

      return true;

    

    else

      iter = iter->right;

    

  }

  return false;

}

void skiplist::insert(const int value) {

  std::vector<skipnode*> predecessors(max_level);

  skipnode* iter = head;

  for (int i = max_level - 1; i >= 0; --i) {

    while (iter->right->value < value)

      iter = iter->right;

    

    predecessors[i] = iter;

    iter = iter->down;

  }

  if (predecessors[0]->right->value == value)

    return;

  

  skipnode* newNode = new skipnode(value);

  for (int i = 0; i < max_level; ++i) {

    newNode->left = predecessors[i]->right;

    newNode->right = predecessors[i]->right->right;

    predecessors[i]->right->left = newNode;

    predecessors[i]->right = newNode;

    if (i > 0) {

      if (rand() / (float)RAND_MAX < p) {

        newNode->up = predecessors[i - 1]->right;

        predecessors[i - 1]->right->down = newNode;

        newNode = newNode->up;

      }

      else

        break;

      

    }

  }

}

void skiplist::erase(const int value) {

  skipnode* iter = head;

  for (int i = max_level - 1; i >= 0; --i) {

    while (iter->right->value < value)

      iter = iter->right;

    

    if (iter->right->value == value) {

      skipnode* toBeDeleted = iter->right;

      iter->right = toBeDeleted->right;

      toBeDeleted->right->left = iter;

      delete toBeDeleted;

    }

    iter = iter->down;

  }

}

void skiplist::print() const {

  for (int i = max_level - 1; i >= 0; --i) {

    skipnode* iter = heads[i];

    while (iter)

      std::cout << iter->value << " ";

      iter = iter->right;

    

    std::cout << std::endl;

  }

}

在查找节点时,我们从跳表的最高层开始查找,如果当前节点指向的节点值大于目标值,往下一层走;如果当前节点指向的节点值等于目标值,返回 true;如果当前节点指向的节点值小于目标值,向右边走。

在插入节点时,我们从跳表的最高层和头节点分别开始查找,将每一层插入的位置(前驱节点和后继节点之间)存储在存储器向量中。此后按层遍历,插入节点,同时在插入后返回一个随机数,如果这个随机数小于 `p`,则将新节点提升到上一层,并在上一层的前驱节点和后继节点之间插入节点。

在删除节点时,我们也是从跳表的最高层和头节点分别开始查找,将每一层需要删除的节点删除即可。

打印跳表时,我们从最高层遍历至最底层,按从左到右的顺序输出每一个节点。

总结起来,我们使用 C++ 实现了一种高效的数据结构————跳表,它能够提供快速的查找、插入和删除操作,并且实现也比红黑树和 AVL 树要简单。

  
  
下一篇: C++代码全集

评论区

{{item['qq_nickname']}}
()
回复
回复