跳跃表:是基于链表来是实现的。
我们可以先看看单链表的数据的结构。如下图
在这样一个链表中,如果我们要查找某个数据,那么需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点为止(没找到)。也就是说,时间复杂度为O(n)。同样,当我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。假如我们每相邻两个节点增加一个指针,让指针指向下下个节点,如下图:
这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半(上图中是7, 19, 26)。现在当我们想查找数据的时候,可以先沿着这个新链表进行查找。当碰到比待查数据大的节点时,再回到原来的链表中进行查找。比如,我们想查找23,查找的路径是沿着下图中标红的指针所指向的方向进行的:
23首先和7比较,再和19比较,比它们都大,继续向后比较。
但23和26比较的时候,比26要小,因此回到下面的链表(原链表),与22比较。
23比22要大,沿下面的指针继续向后和26比较。23比26小,说明待查数据23在原链表中不存在,而且它的插入位置应该在22和26之间。
在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。
skiplist正是受这种多层链表的想法的启发而设计出来的。实际上,按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似于一个二分查找,使得查找的时间复杂度可以降低到O(log n)。但是,这种方法在插入数据的时候有很大的问题。新插入一个节点之后,就会打乱上下相邻两层链表上节点个数严格的2:1的对应关系。如果要维持这种对应关系,就必须把新插入的节点后面的所有节点(也包括新插入的节点)重新进行调整,这会让时间复杂度重新蜕化成O(n)。删除数据也有同样的问题。
skiplist为了避免这一问题,它不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是为每个节点随机出一个层数(level)。比如,一个节点随机出的层数是3,那么就把它链入到第1层到第3层这三层链表中。为了表达清楚,下图展示了如何通过一步步的插入操作从而形成一个skiplist的过程:
从上面skiplist的创建和插入过程可以看出,每一个节点的层数(level)是随机出来的,而且新插入一个节点不会影响其它节点的层数。因此,插入操作只需要修改插入节点前后的指针,而不需要对很多节点都进行调整。这就降低了插入操作的复杂度。实际上,这是skiplist的一个很重要的特性,这让它在插入性能上明显优于平衡树的方案。这在后面我们还会提到。
根据上图中的skiplist结构,我们很容易理解这种数据结构的名字的由来。skiplist,翻译成中文,可以翻译成“跳表”或“跳跃表”,指的就是除了最下面第1层链表之外,它会产生若干层稀疏的链表,这些链表里面的指针故意跳过了一些节点(而且越高层的链表跳过的节点越多)。这就使得我们在查找数据的时候能够先在高层的链表中进行查找,然后逐层降低,最终降到第1层链表来精确地确定数据位置。在这个过程中,我们跳过了一些节点,从而也就加快了查找速度。
下面是使用PHP实现的跳跃表的一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
| <?php
class Node {
public $nextNode;
public $score;
public $data;
public function __construct($score) { $this->score = $score; }
public function setData($val) { $this->data = $val; } }
class IndexNode extends Node {
public $downNode = null;
}
class SkipList {
public $level = 1;
public $head = null;
CONST SKIPLIST_P = 0.5;
const MAX_LEVEL = 16;
public function __construct() { $this->head = new IndexNode(null); }
public function randomLevel() { $level = 0; while (lcg_value() < self::SKIPLIST_P && $level < self::MAX_LEVEL) { $level++; } return $level; }
public function findEntry($score) {
$list = $this->head; while ($list) { while ($list->nextNode && $list->nextNode->score < $score) { $list = $list->nextNode; } if (isset($list->downNode) && !empty($list->downNode)) { $list = $list->downNode; } else { break; } } return $list; }
public function findEntry2($score) {
$list = $this->head; while ($list) { while ($list->nextNode && $list->nextNode->score < $score) { $list = $list->nextNode; echo '::'.$list->score.'::'.PHP_EOL; } if (isset($list->downNode) && !empty($list->downNode)) { $list = $list->downNode; } else { break; } } return $list;
}
public function get($score) { $node = $this->findEntry($score);
if ($node->nextNode && $node->nextNode->score == $score) { return $node->nextNode; } return null; }
public function addNode($score,$val) { $node = new Node($score); $node->setData($val);
$entryNode = $this->findEntry($score); if ($entryNode && $entryNode->score == $score && $entryNode->data == $val) { return; }
$node->nextNode = $entryNode->nextNode; $entryNode->nextNode = $node;
$head = $down = $this->head;
$newIndex = null; for ($i = $this->level; $i>=1;$i--) { while ($down->nextNode && $down->nextNode->score < $score) { $down = $down->nextNode; } if ($down->nextNode && $down->nextNode->score == $score) { if ($newIndex) { $newIndex->downNode = $down->nextNode; } continue; } else { $upNewIndex = $newIndex ? $newIndex : null; $newIndex = new IndexNode($score); $newIndex->nextNode = $down->nextNode; $down->nextNode = $newIndex; if ($upNewIndex) { $upNewIndex->downNode = $newIndex; } } $down = $head->downNode; $head = $head->downNode; }
$level = $this->randomLevel(); $up = $this->head; for ($i = $this->level+1;$i<= $level;$i++) { $downUp = $up ?? null; $up = new IndexNode(null); $newIndex = new IndexNode($score); $up->nextNode = $newIndex; if ($downUp) { $up->downNode = $downUp; } while ($downUp->nextNode && $downUp->nextNode->score < $score) { $downUp = $downUp->nextNode; } if ($down->nextNode->score == $score) { $newIndex->downNode = $down->nextNode; continue; } }
$this->head = $up; $this->level = $this->level < $level ? $level:$this->level; }
public function prinSkipList() { $down = $node = $this->head; while ($down) { $str = ''; while ($node) { $str .= '['.$node->score.']['.(isset($node->downNode) ? $node->downNode->score : null).']['.(isset($node->nextNode) ? $node->nextNode->score : null).']--'; $node = $node->nextNode; } echo $str.PHP_EOL; $down = $node = $down->downNode; } }
public function del($score) { $node = $this->get($score); if (empty($node)) { return true; }
$head = $down = $this->head; for ($i = $this->level;$i>=1;$i--) { $nodeNum = 1; $levelNode = $down; while ($down->nextNode && $down->nextNode->score < $score) { $nodeNum++; $down = $down->nextNode; } if ($down->nextNode && $down->nextNode->score == $score) { $down->nextNode = $down->nextNode->nextNode; if ($nodeNum <= 2 && !$down->nextNode) { $this->level = $this->level > 1 ? ($this->level - 1) : $this->level; $head = $head->downNode ? $head->downNode : $head; } } $down = $levelNode->downNode ? $levelNode->downNode : $levelNode; } $this->head = $head; }
}
$skipList = new SkipList(); $skipList->addNode(5,'5'); $skipList->addNode(8,'8'); $skipList->addNode(6,'6'); $skipList->addNode(7,'7'); $skipList->addNode(3,'3'); $skipList->addNode(2,'2'); $skipList->addNode(1,'1'); $skipList->addNode(0,'0'); $skipList->prinSkipList(); echo "-------------------------".PHP_EOL;
$skipList = new SkipList(); $head2 = new IndexNode(null); $head1 = new IndexNode(null);
$head2->downNode = $head1;
$data1 = new Node(1); $data1->setData('1'); $index1 = new IndexNode(1); $index1->downNode = $data1;
$index2 = new IndexNode(2); $data2 = new Node(2); $data2->setData('2');
$index2->downNode = $data2; $index1->nextNode = $index2;
$data1->nextNode = $data2;
$data3 = new Node(3); $data3->setData('3'); $data2->nextNode = $data3;
$data4 = new Node(4); $data4->setData('4'); $data3->nextNode = $data4;
$head1->nextNode = $data1; $head2->nextNode = $index1;
$head3 = new IndexNode(null); $head3->downNode = $head2; $head3->nextNode = new IndexNode(1); $head3->nextNode->downNode = $index1;
$skipList->head = $head3; $skipList->level = 3; $skipList->prinSkipList(); $skipList->del(1); echo "-------------------------".PHP_EOL; $skipList->prinSkipList(); echo "-------------------------".PHP_EOL; $skipList->addNode(6,'6'); $skipList->prinSkipList();
|