RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

跳躍表數(shù)據(jù)結(jié)構(gòu)與算法分析

OSC開(kāi)源社區(qū) ? 來(lái)源:OSCHINA 社區(qū) ? 2023-01-31 13:35 ? 次閱讀

作者 | 京東云開(kāi)發(fā)者-京東物流 紀(jì)卓志

目前市面上充斥著大量關(guān)于跳躍表結(jié)構(gòu)與 Redis 的源碼解析,但是經(jīng)過(guò)長(zhǎng)期觀察后發(fā)現(xiàn)大都只是在停留在代碼的表面,而沒(méi)有系統(tǒng)性地介紹跳躍表的由來(lái)以及各種常量的由來(lái)。作為一種概率數(shù)據(jù)結(jié)構(gòu),理解各種常量的由來(lái)可以更好地進(jìn)行變化并應(yīng)用到高性能功能開(kāi)發(fā)中。本文沒(méi)有重復(fù)地以對(duì)現(xiàn)有優(yōu)秀實(shí)現(xiàn)進(jìn)行代碼分析,而是通過(guò)對(duì)跳躍表進(jìn)行了系統(tǒng)性地介紹與形式化分析,并給出了在特定場(chǎng)景下的跳躍表擴(kuò)展方式,方便讀者更好地理解跳躍表數(shù)據(jù)結(jié)構(gòu)。

跳躍表 [1,2,3] 是一種用于在大多數(shù)應(yīng)用程序中取代平衡樹(shù)的概率數(shù)據(jù)結(jié)構(gòu)。跳躍表?yè)碛信c平衡樹(shù)相同的期望時(shí)間上界,并且更簡(jiǎn)單、更快、是用更少的空間。在查找與列表的線性操作上,比平衡樹(shù)更快,并且更簡(jiǎn)單。

概率平衡也可以被用在基于樹(shù)的數(shù)據(jù)結(jié)構(gòu) [4] 上,例如樹(shù)堆(Treap)。與平衡二叉樹(shù)相同,跳躍表也實(shí)現(xiàn)了以下兩種操作

通過(guò)搜索引用 [5],可以保證從任意元素開(kāi)始,搜索到在列表中間隔為 k 的元素的任意期望時(shí)間是 O (logk)

實(shí)現(xiàn)線性表的常規(guī)操作(例如_將元素插入到列表第 k 個(gè)元素后面_)

這幾種操作在平衡樹(shù)中也可以實(shí)現(xiàn),但是在跳躍表中實(shí)現(xiàn)起來(lái)更簡(jiǎn)單而且非常的快,并且通常情況下很難在平衡樹(shù)中直接實(shí)現(xiàn)(樹(shù)的線索化可以實(shí)現(xiàn)與鏈表相同的效果,但是這使得實(shí)現(xiàn)變得更加復(fù)雜 [6])

預(yù)覽

最簡(jiǎn)單的支持查找的數(shù)據(jù)結(jié)構(gòu)可能就是鏈表。Figure.1 是一個(gè)簡(jiǎn)單的鏈表。在鏈表中執(zhí)行一次查找的時(shí)間正比于必須考查的節(jié)點(diǎn)個(gè)數(shù),這個(gè)個(gè)數(shù)最多是 N。

ec4d8c8a-a11e-11ed-bfe3-dac502259ad0.png

Figure.1 Linked List

Figure.2 表示一個(gè)鏈表,在該鏈表中,每個(gè)一個(gè)節(jié)點(diǎn)就有一個(gè)附加的指針指向它在表中的前兩個(gè)位置上的節(jié)點(diǎn)。正因?yàn)檫@個(gè)前向指針,在最壞情況下最多考查?N/2?+1 個(gè)節(jié)點(diǎn)。

ec63137a-a11e-11ed-bfe3-dac502259ad0.png

Figure.2 Linked List with fingers to the 2nd forward elements

Figure.3 將這種想法擴(kuò)展,每個(gè)序數(shù)是 4 的倍數(shù)的節(jié)點(diǎn)都有一個(gè)指針指向下一個(gè)序數(shù)為 4 的倍數(shù)的節(jié)點(diǎn)。只有?N/4?+2 個(gè)節(jié)點(diǎn)被考查。

ec74bc9c-a11e-11ed-bfe3-dac502259ad0.png

Figure.3 Linked List with fingers to the 4th forward elements

這種跳躍幅度的一般情況如 Figure.4 所示。每個(gè) 2i 節(jié)點(diǎn)就有一個(gè)指針指向下一個(gè) 2i 節(jié)點(diǎn),前向指針的間隔最大為 N/2。可以證明總的指針最大不會(huì)超過(guò) 2N(見(jiàn)空間復(fù)雜度分析),但現(xiàn)在在一次查找中最多考查?logN?個(gè)節(jié)點(diǎn)。這意味著一次查找中總的時(shí)間消耗為 O (logN),也就是說(shuō)在這種數(shù)據(jù)結(jié)構(gòu)中的查找基本等同于二分查找(binary search)。

ec876fa4-a11e-11ed-bfe3-dac502259ad0.png

Figure.4 Linked List with fingers to the 2ith forward elements

在這種數(shù)據(jù)結(jié)構(gòu)中,每個(gè)元素都由一個(gè)節(jié)點(diǎn)表示。每個(gè)節(jié)點(diǎn)都有一個(gè)高度(height)或級(jí)別(level),表示節(jié)點(diǎn)所擁有的前向指針數(shù)量。每個(gè)節(jié)點(diǎn)的第 i 個(gè)前向指針指向下一個(gè)級(jí)別為 i 或更高的節(jié)點(diǎn)。

在前面描述的數(shù)據(jù)結(jié)構(gòu)中,每個(gè)節(jié)點(diǎn)的級(jí)別都是與元素?cái)?shù)量有關(guān)的,當(dāng)插入或刪除時(shí)需要對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行調(diào)整來(lái)滿足這樣的約束,這是很呆板且低效的。為此,可以_將每個(gè) 2i 節(jié)點(diǎn)就有一個(gè)指針指向下一個(gè) 2i 節(jié)點(diǎn)_的限制去掉,當(dāng)新元素插入時(shí)為每個(gè)新節(jié)點(diǎn)分配一個(gè)隨機(jī)的級(jí)別而不用考慮數(shù)據(jù)結(jié)構(gòu)的元素?cái)?shù)量。

ec9f070e-a11e-11ed-bfe3-dac502259ad0.png

ecb9fb2c-a11e-11ed-bfe3-dac502259ad0.png

Figure.5 Skip List

數(shù)據(jù)結(jié)構(gòu)

到此為止,已經(jīng)得到了所有讓鏈表支持快速查找的充要條件,而這種形式的數(shù)據(jù)結(jié)構(gòu)就是跳躍表。接下來(lái)將會(huì)使用更正規(guī)的方式來(lái)定義跳躍表

所有元素在跳躍表中都是由一個(gè)節(jié)點(diǎn)表示。

每個(gè)節(jié)點(diǎn)都有一個(gè)高度或級(jí)別,有時(shí)候也可以稱之為階(step),節(jié)點(diǎn)的級(jí)別是一個(gè)與元素總數(shù)無(wú)關(guān)的隨機(jī)數(shù)。規(guī)定 NULL 的級(jí)別是∞。

每個(gè)級(jí)別為 k 的節(jié)點(diǎn)都有 k 個(gè)前向指針,且第 i 個(gè)前向指針指向下一個(gè)級(jí)別為 i 或更高的節(jié)點(diǎn)。

每個(gè)節(jié)點(diǎn)的級(jí)別都不會(huì)超過(guò)一個(gè)明確的常量 MaxLevel。整個(gè)跳躍表的級(jí)別是所有節(jié)點(diǎn)的級(jí)別的最高值。如果跳躍表是空的,那么跳躍表的級(jí)別就是 1。

存在一個(gè)頭節(jié)點(diǎn) head,它的級(jí)別是 MaxLevel,所有高于跳躍表的級(jí)別的前向指針都指向 NULL。

稍后將會(huì)提到,節(jié)點(diǎn)的查找過(guò)程是在頭節(jié)點(diǎn)從最高級(jí)別的指針開(kāi)始,沿著這個(gè)級(jí)別一直走,直到找到大于正在尋找的節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)(或者是 NULL),在此過(guò)程中除了頭節(jié)點(diǎn)外并沒(méi)有使用到每個(gè)節(jié)點(diǎn)的級(jí)別,因此每個(gè)節(jié)點(diǎn)無(wú)需存儲(chǔ)節(jié)點(diǎn)的級(jí)別。

在跳躍表中,級(jí)別為 1 的前向指針與原始的鏈表結(jié)構(gòu)中 next 指針的作用完全相同,因此跳躍表支持所有鏈表支持的算法

對(duì)應(yīng)到高級(jí)語(yǔ)言中的結(jié)構(gòu)定義如下所示(后續(xù)所有代碼示例都將使用 C 語(yǔ)言描述)

#define SKIP_LIST_KEY_TYPE     int
#define SKIP_LIST_VALUE_TYPE   int
#define SKIP_LIST_MAX_LEVEL    32
#define SKIP_LIST_P            0.5

struct Node {
  SKIP_LIST_KEY_TYPE    key;
  SKIP_LIST_VALUE_TYPE  value;
  struct Node          *forwards[]; // flexible array member
};

struct SkipList {
  struct Node *head;
  int          level;
};

struct Node *CreateNode(int level) {
  struct Node *node;
  assert(level > 0);
  node = malloc(sizeof(struct Node) + sizeof(struct Node *) * level);
  return node;
}

struct SkipList *CreateSkipList() {
  struct SkipList *list;
  struct Node     *head;
  int              i;

  list = malloc(sizeof(struct SkipList));
  head = CreateNode(SKIP_LIST_MAX_LEVEL);
  for (i = 0; i < SKIP_LIST_MAX_LEVEL; i++) {
    head->forwards[i] = NULL;
  }
  list->head = head;
  list->level = 1;

  return list;
}

從前面的預(yù)覽章節(jié)中,不難看出 MaxLevel 的選值影響著跳躍表的查詢性能,關(guān)于 MaxLevel 的選值將會(huì)在后續(xù)章節(jié)中進(jìn)行介紹。在此先將 MaxLevel 定義為 32,這對(duì)于 232 個(gè)元素的跳躍表是足夠的。延續(xù)預(yù)覽章節(jié)中的描述,跳躍表的概率被定義為 0.5,關(guān)于這個(gè)值的選取問(wèn)題將會(huì)在后續(xù)章節(jié)中進(jìn)行詳細(xì)介紹。

算法

搜索

在跳躍表中進(jìn)行搜索的過(guò)程,是通過(guò) Z 字形遍歷所有沒(méi)有超過(guò)要尋找的目標(biāo)元素的前向指針來(lái)完成的。在當(dāng)前級(jí)別沒(méi)有可以移動(dòng)的前向指針時(shí),將會(huì)移動(dòng)到下一級(jí)別進(jìn)行搜索。直到在級(jí)別為 1 的時(shí)候且沒(méi)有可以移動(dòng)的前向指針時(shí)停止搜索,此時(shí)直接指向的節(jié)點(diǎn)(級(jí)別為 1 的前向指針)就是包含目標(biāo)元素的節(jié)點(diǎn)(如果目標(biāo)元素在列表中的話)。在 Figure.6 中展示了在跳躍表中搜索元素 17 的過(guò)程。
ecce1972-a11e-11ed-bfe3-dac502259ad0.png

Figure.6 A search path to find element 17 in Skip List 整個(gè)過(guò)程的示例代碼如下所示,因?yàn)楦呒?jí)語(yǔ)言中的數(shù)組下標(biāo)從 0 開(kāi)始,因此 forwards [0] 表示節(jié)點(diǎn)的級(jí)別為 1 的前向指針,依此類(lèi)推

struct Node *SkipListSearch(struct SkipList *list, SKIP_LIST_KEY_TYPE target) {
  struct Node *current;
  int          i;

  current = list->head;
  for (i = list->level - 1; i >= 0; i--) {
    while (current->forwards[i] && current->forwards[i]->key < target) {
      current = current->forwards[i];
    }
  }

  current = current->forwards[0];
  if (current->key == target) {
    return current;
  } else {
    return NULL;
  }
}

插入和刪除

在插入和刪除節(jié)點(diǎn)的過(guò)程中,需要執(zhí)行和搜索相同的邏輯。在搜索的基礎(chǔ)上,需要維護(hù)一個(gè)名為 update 的向量,它維護(hù)的是搜索過(guò)程中跳躍表每個(gè)級(jí)別上遍歷到的最右側(cè)的值,表示插入或刪除的節(jié)點(diǎn)的左側(cè)直接直接指向它的節(jié)點(diǎn),用于在插入或刪除后調(diào)整節(jié)點(diǎn)所在所有級(jí)別的前向指針(與樸素的鏈表節(jié)點(diǎn)插入或刪除的過(guò)程相同)。 當(dāng)新插入節(jié)點(diǎn)的級(jí)別超過(guò)當(dāng)前跳躍表的級(jí)別時(shí),需要增加跳躍表的級(jí)別并將 update 向量中對(duì)應(yīng)級(jí)別的節(jié)點(diǎn)修改為 head 節(jié)點(diǎn)。 Figure.7 和 Figure.8 展示了在跳躍表中插入元素 16 的過(guò)程。首先,在 Figure.7 中執(zhí)行與搜索相同的查詢過(guò)程,在每個(gè)級(jí)別遍歷到的最后一個(gè)元素在對(duì)應(yīng)層級(jí)的前向指針被標(biāo)記為灰色,表示稍后將會(huì)對(duì)齊進(jìn)行調(diào)整。接下來(lái)在 Figure.8 中,在元素為 13 的節(jié)點(diǎn)后插入元素 16,元素 16 對(duì)應(yīng)的節(jié)點(diǎn)的級(jí)別是 5,這比跳躍表當(dāng)前級(jí)別要高,因此需要增加跳躍表的級(jí)別到 5,并將 head 節(jié)點(diǎn)對(duì)應(yīng)級(jí)別的前向指針標(biāo)記為灰色。Figure.8 中所有虛線部分都表示調(diào)整后的效果。 ece08ec2-a11e-11ed-bfe3-dac502259ad0.png

Figure.7 Search path for inserting element 16

ecfd688a-a11e-11ed-bfe3-dac502259ad0.png

Figure.8 Insert element 16 and adjust forward pointers

struct Node *SkipListInsert(struct SkipList *list, SKIP_LIST_KEY_TYPE key, SKIP_LIST_VALUE_TYPE value) {
  struct Node *update[SKIP_LIST_MAX_LEVEL];
  struct Node *current;
  int          i;
  int          level;

  current = list->head;
  for (i = list->level - 1; i >= 0; i--) {
    while (current->forwards[i] && current->forwards[i]->key < target) {
      current = current->forwards[i];
    }
    update[i] = current;
  }

  current = current->forwards[0];
  if (current->key == target) {
    current->value = value;
    return current;
  }

  level = SkipListRandomLevel();
  if (level > list->level) {
    for (i = list->level; i < level; i++) {
      update[i] = list->header;
    }
  }

  current = CreateNode(level);
  current->key = key;
  current->value = value;

  for (i = 0; i < level; i++) {
    current->forwards[i] = update[i]->forwards[i];
    update[i]->forwards[i] = current;
  }

  return current;
}

在刪除節(jié)點(diǎn)后,如果刪除的節(jié)點(diǎn)是跳躍表中級(jí)別最大的節(jié)點(diǎn),那么需要降低跳躍表的級(jí)別。 Figure.9 和 Figure.10 展示了在跳躍表中刪除元素 19 的過(guò)程。首先,在 Figure.9 中執(zhí)行與搜索相同的查詢過(guò)程,在每個(gè)級(jí)別遍歷到的最后一個(gè)元素在對(duì)應(yīng)層級(jí)的前向指針被標(biāo)記為灰色,表示稍后將會(huì)對(duì)齊進(jìn)行調(diào)整。接下來(lái)在 Figure.10 中,首先通過(guò)調(diào)整前向指針將元素 19 對(duì)應(yīng)的節(jié)點(diǎn)從跳躍表中卸載,因?yàn)樵?19 對(duì)應(yīng)的節(jié)點(diǎn)是級(jí)別最高的節(jié)點(diǎn),因此將其從跳躍表中移除后需要調(diào)整跳躍表的級(jí)別。Figure.10 中所有虛線部分都表示調(diào)整后的效果。 ed1c9b9c-a11e-11ed-bfe3-dac502259ad0.png Figure.9 Search path for deleting element 19 ed374d98-a11e-11ed-bfe3-dac502259ad0.png Figure.10 Delete element 19 and adjust forward pointers
struct Node *SkipListDelete(struct SkipList *list, SKIP_LIST_KEY_TYPE key) {
  struct Node *update[SKIP_LIST_MAX_LEVEL];
  struct Node *current;
  int          i;

  current = list->head;
  for (i = list->level - 1; i >= 0; i--) {
    while (current->forwards[i] && current->forwards[i]->key < key) {
      current = current->forwards[i];
    }
    update[i] = current;
  }

  current = current->forwards[0];
  if (current && current->key == key) {
    for (i = 0; i < list->level; i++) {
      if (update[i]->forwards[i] == current) {
        update[i]->forwards[i] = current->forwards[i];
      } else {
        break;
      }
    }

    while (list->level > 1 && list->head->forwards[list->level - 1] == NULL) {
      list->level--;
    }
  }

  return current;
}

生成隨機(jī)級(jí)別

ed487cd0-a11e-11ed-bfe3-dac502259ad0.png

int SkipListRandomLevel() {
  int level;
  level = 1;
  while (random() < RAND_MAX * SKIP_LIST_P && level <= SKIP_LIST_MAX_LEVEL) {
    level++;
  }
  return level;
}

以下表格為按上面算法執(zhí)行 232 次后,所生成的隨機(jī)級(jí)別的分布情況。

1 2 3 4 5 6 7 8
2147540777 1073690199 536842769 268443025 134218607 67116853 33563644 16774262
9 10 11 12 13 14 15 16
8387857 4193114 2098160 1049903 523316 262056 131455 65943
17 18 19 20 21 22 23 24
32611 16396 8227 4053 2046 1036 492 249
25 26 27 28 29 30 31 32
121 55 34 16 7 9 2 1

MaxLevel 的選擇

在 Figure.4 中曾給出過(guò)對(duì)于 10 個(gè)元素的跳躍表最理想的分布情況,其中 5 個(gè)節(jié)點(diǎn)的級(jí)別是 1,3 個(gè)節(jié)點(diǎn)的級(jí)別是 2,1 個(gè)節(jié)點(diǎn)的級(jí)別是 3,1 個(gè)節(jié)點(diǎn)的級(jí)別是 4。 這引申出一個(gè)問(wèn)題:既然相同元素?cái)?shù)量下,跳躍表的級(jí)別不同會(huì)有不同的性能,那么跳躍表的級(jí)別為多少才合適? ed627d92-a11e-11ed-bfe3-dac502259ad0.png

分析

空間復(fù)雜度分析

前面提到過(guò),分?jǐn)?shù) p 代表節(jié)點(diǎn)同時(shí)帶有第 i 層前向指針和第 i+1 層前向指針的概率,而每個(gè)節(jié)點(diǎn)的級(jí)別最少是 1,因此級(jí)別為 i 的前向指針數(shù)為 npi?1。定義 S (n) 表示所有前向指針的總量,由等比數(shù)列求和公式可得 ed78b62a-a11e-11ed-bfe3-dac502259ad0.png 對(duì) S (n) 求極限,有 ed91725a-a11e-11ed-bfe3-dac502259ad0.png 易證,這是一個(gè)關(guān)于 p 的單調(diào)遞增函數(shù),因此 p 的值越大,跳躍表中前向指針的總數(shù)越多。因?yàn)?1?p 是已知的常數(shù),所以說(shuō)跳躍表的空間復(fù)雜度是 O (n)。

時(shí)間復(fù)雜度分析

非形式化分析

eda0f95a-a11e-11ed-bfe3-dac502259ad0.png

形式化分析

edba9efa-a11e-11ed-bfe3-dac502259ad0.png

延續(xù)_分?jǐn)?shù) p 代表節(jié)點(diǎn)同時(shí)帶有第 i 層前向指針和第 i+1 層前向指針的概率_的定義,考慮反方向分析搜索路徑。 搜索的路徑總是小于必須執(zhí)行的比較的次數(shù)的。首先需要考查從級(jí)別 1(在搜索到元素前遍歷的最后一個(gè)節(jié)點(diǎn))爬升到級(jí)別 L (n) 所需要反向跟蹤的指針數(shù)量。雖然在搜索時(shí)可以確定每個(gè)節(jié)點(diǎn)的級(jí)別都是已知且確定的,在這里仍認(rèn)為只有當(dāng)整個(gè)搜索路徑都被反向追蹤后才能被確定,并且在爬升到級(jí)別 L (n) 之前都不會(huì)接觸到 head 節(jié)點(diǎn)。 在爬升過(guò)程中任何特定的點(diǎn),都認(rèn)為是在元素 x 的第 i 個(gè)前向指針,并且不知道元素 x 左側(cè)所有元素的級(jí)別或元素 x 的級(jí)別,但是可以知道元素 x 的級(jí)別至少是 i。元素 x 的級(jí)別大于 i 的概率是 p,可以通過(guò)考慮視認(rèn)為這個(gè)反向爬升的過(guò)程是一系列由成功的爬升到更高級(jí)別或失敗地向左移動(dòng)的隨機(jī)獨(dú)立實(shí)驗(yàn)。 在爬升到級(jí)別 L (n) 過(guò)程中,向左移動(dòng)的次數(shù)等于在連續(xù)隨機(jī)試驗(yàn)中第 L (n)?1 次成功前的失敗的次數(shù),這符合負(fù)二項(xiàng)分布。期望的向上移動(dòng)次數(shù)一定是 L (n)?1。因此可以得到無(wú)限列表中爬升到 L (n) 的代價(jià) C C=prob (L (n)?1)+NB (L (n)?1,p) 列表長(zhǎng)度無(wú)限大是一個(gè)悲觀的假設(shè)。當(dāng)反向爬升的過(guò)程中接觸到 head 節(jié)點(diǎn)時(shí),可以直接向上爬升而不需要任何向左移動(dòng)。因此可以得到 n 個(gè)元素列表中爬升到 L (n) 的代價(jià) C (n) C(n)≤probC=prob(L(n)?1)+NB(L(n)?1,p) 因此 n 個(gè)元素列表中爬升到 L (n) 的代價(jià) C (n) 的數(shù)學(xué)期望和方差為 edd028d8-a11e-11ed-bfe3-dac502259ad0.png

因?yàn)?1/p 是已知常數(shù),因此跳躍表的搜索、插入和刪除的時(shí)間復(fù)雜度都是 O (logn)。

P 的選擇


edee4af2-a11e-11ed-bfe3-dac502259ad0.png
ee0dcb0c-a11e-11ed-bfe3-dac502259ad0.jpg

Figure.11 Normalized search times

ee1fa14c-a11e-11ed-bfe3-dac502259ad0.png

擴(kuò)展

快速隨機(jī)訪問(wèn)

ee335cbe-a11e-11ed-bfe3-dac502259ad0.png

#define SKIP_LIST_KEY_TYPE     int
#define SKIP_LIST_VALUE_TYPE   int
#define SKIP_LIST_MAX_LEVEL    32
#define SKIP_LIST_P            0.5

struct Node; // forward definition

struct Forward {
  struct Node *forward;
  int          span;
}

struct Node {
  SKIP_LIST_KEY_TYPE    key;
  SKIP_LIST_VALUE_TYPE  value;
  struct Forward        forwards[]; // flexible array member
};

struct SkipList {
  struct Node *head;
  int          level;
  int          length;
};

struct Node *CreateNode(int level) {
  struct Node *node;
  assert(level > 0);
  node = malloc(sizeof(struct Node) + sizeof(struct Forward) * level);
  return node;
}

struct SkipList *CreateSkipList() {
  struct SkipList *list;
  struct Node     *head;
  int              i;

  list = malloc(sizeof(struct SkipList));
  head = CreateNode(SKIP_LIST_MAX_LEVEL);
  for (i = 0; i < SKIP_LIST_MAX_LEVEL; i++) {
    head->forwards[i].forward = NULL;
    head->forwards[i].span = 0;
  }

  list->head = head;
  list->level = 1;

  return list;
}

接下來(lái)需要修改插入和刪除操作,來(lái)保證在跳躍表修改后跨度的數(shù)據(jù)完整性。

需要注意的是,在插入過(guò)程中需要使用 indices 記錄在每個(gè)層級(jí)遍歷到的最后一個(gè)元素的位置,這樣通過(guò)做簡(jiǎn)單的減法操作就可以知道每個(gè)層級(jí)遍歷到的最后一個(gè)元素到新插入節(jié)點(diǎn)的跨度。

struct Node *SkipListInsert(struct SkipList *list, SKIP_LIST_KEY_TYPE key, SKIP_LIST_VALUE_TYPE value) {
  struct Node *update[SKIP_LIST_MAX_LEVEL];
  struct Node *current;
  int          indices[SKIP_LIST_MAX_LEVEL];
  int          i;
  int          level;

  current = list->head;
  for (i = list->level - 1; i >= 0; i--) {
    if (i == list->level - 1) {
      indices[i] = 0;
    } else {
      indices[i] = indices[i + 1];
    }
    
    while (current->forwards[i].forward && current->forwards[i].forward->key < target) {
      indices[i] += current->forwards[i].span;
      current = current->forwards[i].forward;
    }
    update[i] = current;
  }

  current = current->forwards[0].forward;
  if (current->key == target) {
    current->value = value;
    return current;
  }

  level = SkipListRandomLevel();
  if (level > list->level) {
    for (i = list->level; i < level; i++) {
      indices[i] = 0;
      update[i] = list->header;
      update[i]->forwards[i].span = list->length;
    }
  }

  current = CreateNode(level);
  current->key = key;
  current->value = value;

  for (i = 0; i < level; i++) {
    current->forwards[i].forward = update[i]->forwards[i].forward;
    update[i]->forwards[i].forward = current;
    current->forwards[i].span = update[i]->forwards[i].span - (indices[0] - indices[i]);
    update[i]->forwards[i].span = (indices[0] - indices[i]) + 1;
  }

  list.length++;

  return current;
}

struct Node *SkipListDelete(struct SkipList *list, SKIP_LIST_KEY_TYPE key) {
  struct Node *update[SKIP_LIST_MAX_LEVEL];
  struct Node *current;
  int          i;

  current = list->head;
  for (i = list->level - 1; i >= 0; i--) {
    while (current->forwards[i].forward && current->forwards[i].forward->key < key) {
      current = current->forwards[i].forward;
    }
    update[i] = current;
  }

  current = current->forwards[0].forward;
  if (current && current->key == key) {
    for (i = 0; i < list->level; i++) {
      if (update[i]->forwards[i].forward == current) {
        update[i]->forwards[i].forward = current->forwards[i];
        update[i]->forwards[i].span += current->forwards[i].span - 1;
      } else {
        break;
      }
    }

    while (list->level > 1 && list->head->forwards[list->level - 1] == NULL) {
      list->level--;
    }
  }

  return current;
}

當(dāng)實(shí)現(xiàn)了快速隨機(jī)訪問(wèn)之后,通過(guò)簡(jiǎn)單的指針操作即可實(shí)現(xiàn)區(qū)間查詢功能。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 算法
    +關(guān)注

    關(guān)注

    23

    文章

    4607

    瀏覽量

    92826
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    639

    瀏覽量

    29185
  • 數(shù)據(jù)結(jié)構(gòu)

    關(guān)注

    3

    文章

    573

    瀏覽量

    40121
  • 鏈表
    +關(guān)注

    關(guān)注

    0

    文章

    80

    瀏覽量

    10558
  • Redis
    +關(guān)注

    關(guān)注

    0

    文章

    374

    瀏覽量

    10871

原文標(biāo)題:跳躍表數(shù)據(jù)結(jié)構(gòu)與算法分析

文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    數(shù)據(jù)結(jié)構(gòu)算法分析(Java版)(pdf)

    數(shù)據(jù)結(jié)構(gòu)算法分析(Java版)(pdf)http://www.ibeifeng.com/read.php?tid=4812&u=73481【中文】Java數(shù)據(jù)結(jié)構(gòu)
    發(fā)表于 12-20 21:22

    數(shù)據(jù)結(jié)構(gòu)概述及線性

    第一講 數(shù)據(jù)結(jié)構(gòu)概述及線性 1 數(shù)據(jù)結(jié)構(gòu)概述1.1 概述    60年代初期,還沒(méi)有獨(dú)立的“數(shù)據(jù)結(jié)構(gòu)”課程,有關(guān)內(nèi)容散見(jiàn)于操作系統(tǒng)、編譯
    發(fā)表于 12-05 21:20

    數(shù)據(jù)結(jié)構(gòu)算法分析

    數(shù)據(jù)結(jié)構(gòu)算法分析
    發(fā)表于 06-05 10:46

    數(shù)據(jù)結(jié)構(gòu)算法分析:C語(yǔ)音第二版

    數(shù)據(jù)結(jié)構(gòu)算法分析:C語(yǔ)音第二版,經(jīng)典資料與你分析
    發(fā)表于 12-10 10:57

    C#數(shù)據(jù)結(jié)構(gòu)算法分析_ 魏寶剛

    數(shù)據(jù)結(jié)構(gòu)算法分析》描述了各種類(lèi)型的數(shù)據(jù)結(jié)構(gòu),包括線性、樹(shù)、堆、圖,以及查找、排序等算法。自
    發(fā)表于 12-15 16:46 ?0次下載
    C#<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>和<b class='flag-5'>算法</b><b class='flag-5'>分析</b>_ 魏寶剛

    數(shù)據(jù)結(jié)構(gòu)算法分析(C語(yǔ)言版)

    電子發(fā)燒友網(wǎng)站提供《數(shù)據(jù)結(jié)構(gòu)算法分析(C語(yǔ)言版).txt》資料免費(fèi)下載
    發(fā)表于 11-28 11:05 ?0次下載

    數(shù)據(jù)結(jié)構(gòu)算法分析C++描述(第3版)

    電子發(fā)燒友網(wǎng)站提供《數(shù)據(jù)結(jié)構(gòu)算法分析C++描述(第3版).txt》資料免費(fèi)下載
    發(fā)表于 07-23 14:15 ?0次下載

    數(shù)據(jù)結(jié)構(gòu)算法習(xí)題

    數(shù)據(jù)結(jié)構(gòu)算法習(xí)題,ACM專用,刷題初期按照這個(gè)地方刷很好
    發(fā)表于 03-03 18:25 ?0次下載

    數(shù)據(jù)結(jié)構(gòu)算法

    全國(guó)C語(yǔ)言考試公共基礎(chǔ)知識(shí)點(diǎn)——數(shù)據(jù)結(jié)構(gòu)算法,該資料包含了有關(guān)數(shù)據(jù)結(jié)構(gòu)算法的全部知識(shí)點(diǎn)。
    發(fā)表于 03-30 14:27 ?0次下載

    數(shù)據(jù)結(jié)構(gòu)算法分析

    一部淺顯易懂的介紹數(shù)據(jù)結(jié)構(gòu)算法的書(shū)籍。
    發(fā)表于 07-14 17:12 ?0次下載

    算法數(shù)據(jù)結(jié)構(gòu)——哈希

    周立功教授數(shù)年之心血之作《程序設(shè)計(jì)與數(shù)據(jù)結(jié)構(gòu)》以及《面向第三章為算法數(shù)據(jù)結(jié)構(gòu),本文為3.5 哈希。
    的頭像 發(fā)表于 09-25 11:37 ?5545次閱讀
    <b class='flag-5'>算法</b>與<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>——哈希<b class='flag-5'>表</b>

    什么是數(shù)據(jù)結(jié)構(gòu)?為什么要學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)?數(shù)據(jù)結(jié)構(gòu)的應(yīng)用實(shí)例分析

    本文檔的主要內(nèi)容詳細(xì)介紹的是什么是數(shù)據(jù)結(jié)構(gòu)?為什么要學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)?數(shù)據(jù)結(jié)構(gòu)的應(yīng)用實(shí)例分析包括了:數(shù)據(jù)結(jié)構(gòu)在串口通信當(dāng)中的應(yīng)用,
    發(fā)表于 09-26 15:45 ?14次下載
    什么是<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>?為什么要學(xué)習(xí)<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>?<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>的應(yīng)用實(shí)例<b class='flag-5'>分析</b>

    大牛分享平時(shí)如何學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)算法

    數(shù)據(jù)結(jié)構(gòu)算法的地位對(duì)于一個(gè)程序員來(lái)說(shuō)不言而喻。今天這篇文章不是來(lái)勸你們學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)算法的,也不是來(lái)和你們說(shuō)數(shù)據(jù)結(jié)構(gòu)
    的頭像 發(fā)表于 11-02 11:25 ?2973次閱讀

    數(shù)據(jù)結(jié)構(gòu)算法分析—C語(yǔ)言描述

    數(shù)據(jù)結(jié)構(gòu)算法分析:C語(yǔ)言描述》曾被評(píng)為20世紀(jì)頂尖的30部計(jì)算機(jī)著作之一,作者在數(shù)據(jù)結(jié)構(gòu)算法分析
    發(fā)表于 10-14 08:00 ?17次下載
    <b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>與<b class='flag-5'>算法</b><b class='flag-5'>分析</b>—C語(yǔ)言描述

    數(shù)據(jù)結(jié)構(gòu)算法分析——Java語(yǔ)言描述

    數(shù)據(jù)結(jié)構(gòu)算法分析——Java語(yǔ)言描述說(shuō)明。
    發(fā)表于 05-31 14:25 ?22次下載
    RM新时代网站-首页