前提
有时候会被问到链表如果做到二分搜索,可能会有部分的人会去把链表中的值保存到数组来进行二分,但是如果知道跳跃表的话,那么这个数据结构就可以解决这个困惑,它允许快速查询一个有序连续元素的数据链表,它的效率可以做到和二分相同,都是O(logn)的平均时间复杂度,其空间复杂度为O(n)。
跳跃列表是在很多应用中有可能替代平衡树而作为实现方法的一种数据结构。跳跃列表的算法有同平衡树一样的渐进的预期时间边界,并且更简单、更快速和使用更少的空间。----by 发明者像是redis中有序集合就使用到了跳跃表。
性质
首先,应该要了解跳跃表的性质;
- 由很多层结构组成;
- 每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点;
- 最底层的链表包含了所有的元素;
- 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集);
- 链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;
可以看到,这里一共有4层,最上面就是最高层(Level 3),最下面的层就是最底层(Level 0),然后每一列中的链表节点中的值都是相同的,用指针来连接着。跳跃表的层数跟结构中最高节点的高度相同。理想情况下,跳跃表结构中第一层中存在所有的节点,第二层只有一半的节点,而且是均匀间隔,第三层则存在1/4的节点,并且是均匀间隔的,以此类推,这样理想的层数就是logN。
搜索
其基本原理就是从最高层的链表节点开始,如果比当前节点要大和比当前层的下一个节点要小,那么则往下找,也就是和当前层的下一层的节点的下一个节点进行比较,以此类推,一直找到最底层的最后一个节点,如果找到则返回,反之则返回空。
代码实现大致为:
find(x)
{
p = top;
while (1) {
while (p->next->key < x)
p = p->next;
if (p->down == NULL)
return p->next;
p = p->down;
}
}
插入
既然要插入,首先需要确定插入的层数,这里有不一样的方法。1. 看到一些博客写的是抛硬币,只要是正面就累加,直到遇见反面才停止,最后记录正面的次数并将其作为要添加新元素的层;2. 还有就是一些博客里面写的统计概率,先给定一个概率p,产生一个0到1之间的随机数,如果这个随机数小于p,则将高度加1,直到产生的随机数大于概率p才停止,根据给出的结论,当概率为1/2或者是1/4的时候,整体的性能会比较好(其实当p为1/2的时候,也就是抛硬币的方法)。
当确定好要插入的层数以后,则需要将元素都插入到从最底层到第k层。
删除
在各个层中找到包含指定值的节点,然后将节点从链表中删除即可,如果删除以后只剩下头尾两个节点,则删除这一层。
php代码
<?php
/**
* 数据链表节点
*/
class skipListNode
{
public $d = null;
public $mem = null;
public $pre = null;
public $level = 0;
public $levelNode = array();
}
/**
* 层链表节点
*/
class skipListLevelNode
{
public $node = null;
public $next = null;
public $cur_level = 0;
public $next_step = 0;
}
/**
* 跳跃表
*/
class skipList
{
private $max_level = 1;
private $head = null;
private $hash_table = array();
public function __construct($max_level = 10)
{
$this->max_level = (int) $max_level;
$skipListNode = new skipListNode();
$skipListNode->level = $max_level;
for ($i = 0; $i < $this->max_level; $i++) {
$skipListLevelNode = new skipListLevelNode();
$skipListLevelNode->cur_level = $i;
$skipListLevelNode->node = $skipListNode;
$skipListNode->levelNode[] = $skipListLevelNode;
}
$this->head = $skipListNode;
}
public function add(string $mem, $d)
{
$mem = strval($mem);
$d = floatval($d);
if (!$this->update($mem, $d)) {
# 新加
$listNode = $this->getNodePos($d);
$nextNode = isset($listNode->levelNode[0]->next) ? $listNode->levelNode[0]->next->node : null;
$skipListNode = new skipListNode();
$skipListNode->pre = $listNode;
$skipListNode->mem = $mem;
$skipListNode->d = $d;
$skipListNode->level = $this->node_level();
if ($nextNode) {
$nextNode->pre = $skipListNode;
}
$pre_next_step = $next_step = 1;
for ($i = 0; $i < $this->max_level; $i++) {
while ($i >= $listNode->level && $listNode->pre !== null) {
$listNode = $listNode->pre;
$pre_next_step++;
}
if (empty($listNode)) {
break;
}
if (!empty($nextNode) && $i >= $nextNode->level) {
$nextNode = isset($nextNode->levelNode[0]->next) ? $nextNode->levelNode[0]->next->node : null;
if ($nextNode) {
$next_step++;
}
}
if ($i < $skipListNode->level) {
$skipListLevelNode = new skipListLevelNode();
$skipListLevelNode->node = $skipListNode;
$skipListLevelNode->cur_level = $i;
$skipListLevelNode->next = null;
$skipListLevelNode->next_step = $next_step;
if ($nextNode && isset($nextNode->levelNode[$i]) && !empty($nextNode->levelNode[$i])) {
$skipListLevelNode->next = $nextNode->levelNode[$i];
}
$skipListNode->levelNode[] = $skipListLevelNode;
$listNode->levelNode[$i]->next = $skipListLevelNode;
$listNode->levelNode[$i]->next_step = $pre_next_step;
} else {
$listNode->levelNode[$i]->next_step++;
}
}
$this->hash_table[$mem] = $skipListNode;
}
return true;
}
public function update(string $mem, $d)
{
$mem = strval($mem);
$d = floatval($d);
$node = $this->getNodePosByMem($mem);
if ($node) {
if ($node->d === $d) {
return true;
}
if ($this->del($mem)) {
$this->add($mem, $d);
return true;
}
return false;
}
return false;
}
public function del(string $mem)
{
$mem = strval($mem);
$node = $this->getNodePosByMem($mem);
if ($node) {
$preNode = $node->pre;
if (isset($node->levelNode[0]->next->node)) {
$node->levelNode[0]->next->node->pre = $preNode;
}
for ($i = 0; $i < $this->max_level; $i++) {
while ($i >= $preNode->level && $preNode->pre !== null) {
$preNode = $preNode->pre;
}
if ($preNode->levelNode[$i]->next === null) {
continue;
}
if ($i < $node->level) {
$preNode->levelNode[$i]->next = $node->levelNode[$i]->next;
$preNode->levelNode[$i]->next_step += $node->levelNode[$i]->next_step;
$node->levelNode[$i] = null;
}
$preNode->levelNode[$i]->next_step--;
}
$node = null;
$this->delNodePosByMem($mem);
return true;
}
return false;
}
public function getByScore($s)
{
$s = intval($s);
$node = $this->head;
for ($i = $this->max_level - 1; $i >= 0 && $s > 0; $i--) {
if (!isset($node->levelNode[$i]) || empty($node->levelNode[$i]->next) || $node->levelNode[$i]->next_step > $s) {
continue;
}
do {
if ($node->levelNode[$i]->next_step === $s) {
$s = 0;
} else {
$s -= $node->levelNode[$i]->next_step;
}
$node = $node->levelNode[$i]->next->node;
if ($s <= 0) {
break;
}
} while (isset($node->levelNode[$i]->next->node) && $node->levelNode[$i]->next->node && $node->levelNode[$i]->next_step <= $s);
}
return isset($node->mem) && $s <= 0 ? $node->mem : null;
}
public function getRank($s, $e)
{
$s = intval($s);
$e = intval($e);
$ret = array();
if ($s > $e || $s < 1) {
return $ret;
}
$node = $this->head;
$step = 0;
for ($i = $this->max_level; $i >= 0; $i--) {
while (isset($node->levelNode[$i]->next) && $node->levelNode[$i]->next && ($step + $node->levelNode[$i]->next_step) <= $s) {
$step += $node->levelNode[$i]->next_step;
$node = $node->levelNode[$i]->next->node;
}
if ($step === $s) {
break;
}
}
if ($step === $s) {
do {
if ($step > $e || empty($node)) {
break;
}
$ret[] = $node->mem;
$step += $node->levelNode[0]->next_step;
$node = isset($node->levelNode[0]->next->node) ? $node->levelNode[0]->next->node : null;
} while (true);
}
return $ret;
}
public function getScore($mem)
{
$mem = strval($mem);
$node = $this->getNodePosByMem($mem);
if ($node) {
$step = 0;
$preNode = $node;
do {
$preNode = $preNode->pre;
if ($preNode === null) {
break;
}
if ($preNode->level < $node->level) {
continue;
}
$step += $preNode->levelNode[$node->level - 1]->next_step;
} while (true);
return $step;
}
return null;
}
private function getNodePosByMem($mem)
{
return isset($this->hash_table[$mem]) ? $this->hash_table[$mem] : null;
}
private function delNodePosByMem($mem)
{
unset($this->hash_table[$mem]);
}
private function getNodePos($d)
{
$listNode = $this->head;
$i = $listNode->level - 1;
do {
$node = isset($listNode->levelNode[$i]->next->node) ? $listNode->levelNode[$i]->next->node : null;
if ($i > 0) {
if (empty($node)) {
$i--;
continue;
}
} else {
if (empty($node)) {
break;
}
}
if ($node->d > $d) {
if ($i === 0) {
break;
} else {
while ($i > 0 && $node->levelNode[$i]->next_step === $node->levelNode[--$i]->next_step) {};
}
} elseif ($node->d === $d) {
break;
} else {
$listNode = $node;
continue;
}
} while ($i >= 0);
return $listNode;
}
private function node_level()
{
$level = 1;
do {
if (mt_rand(0, 10000) > 1999) {
break;
}
} while (++$level && $level < $this->max_level);
if ($level === $this->max_level) {
$skipListLevelNode = new skipListLevelNode();
$skipListLevelNode->cur_level = $level - 1;
$skipListLevelNode->node = $this->head;
$this->head->levelNode[] = $skipListLevelNode;
$this->max_level++;
}
return $level;
}
public function print_list()
{
$node = $this->head;
do {
if (!isset($node->levelNode[0]->next) || empty($node->levelNode[0]->next)) {
break;
}
$node = $node->levelNode[0]->next->node;
var_dump($node->mem . " = " . $node->d, $node->level, isset($node->pre) ? $node->pre->mem : 'null');
for ($i = 0; $i < $node->level; $i++) {
echo "+++ {$i} +++";
var_dump($node->levelNode[$i]->next_step, isset($node->levelNode[$i]->next));
}
echo "-----------------------<br/>";
} while (1);
echo "-----------head------------<br/>";
foreach ($this->head->levelNode as $key => $n) {
if (isset($n->next)) {
$node = $n->next->node;
var_dump($node->level . ":" . $node->mem . " = " . $node->d, $n->next_step);
}
}
}
}