跳表是一种实现较为简单的数据结构理解起来并不复杂,而且增删改的效率要高于平衡二叉搜索树,不涉及到左右子树的旋转操作.
实现逻辑
跳表理解起来并不困难,我们先来看一张图.
每一列看作是一个节点,它有着相同的值,最后一个节点为nil或者NULL,起始节点为头结点.图中最左侧是头结点,最后一个节点nil.
数据结构是这样的:
struct {
int val;
struct node **next;
} node;
每一个节点包含一个指向node地址的next数组.这一块大小可以根据层数(高度)去分配对应的大小.每个next中的node地址总是和该node是下一个指向,可能不相邻.
节点是由从左到右为由大到小的顺序排列.
查询逻辑
查询时跳表通过高度差来跳过无关的节点来加速查找.时间复杂度为O(logn)
比后面节点大就在节点中往下走,如果比后面节点大往后走.相等则返回.
插入逻辑
找到要插入节点的前面节点信息,然后做插入.前面节点指向要插入节点,要插入节点的后面节点指向前面节点的后面节点.
删除逻辑
查到要删除的节点的前面节点,前面节点的后面节点指向要删除节点的后面节点.
跳表源码
<?php
class SkipListNode
{
public $forward = [];
public $key;
public function __construct($key, $level)
{
$this->key = $key;
$this->forward = array_fill(0, $level + 1, null);//0层为基本层是一个单链表串起了所有的节点
}
}
class SkipList
{
public $maxLevel;
public $p;
public $level = 0; //level从0起
public $head;
public function __construct($maxLevel, $p)
{
$this->maxLevel = $maxLevel;
$this->p = $p;
$this->head = new SkipListNode(-1, $maxLevel); //head 拥有最大层
}
/**
* 获取一个随机层数,p概率取(0~1)之间
* 越往上的层数可能性越小
* @return int
*/
public function getRandLevel()
{
$level = 0;
while ((mt_rand(0,mt_getrandmax()) / mt_getrandmax()) < $this->p && $level < $this->maxLevel) {
$level++;
}
return $level;
}
public function insert($val)
{
//后面节点定义为forward[$n]为n位置节点
//前面节点定义为$current->forward[$n] $current节点
$current = $this->head;//从头节点开始
$update = [];//保留新插入节点的前面节点.
for ($i = $this->level; $i >= 0; $i--) {//从当前层数开始遍历
while ($current->forward[$i] != null && $current->forward[$i]->key < $val) { //如果头节点开始遍历,如果第i层的下一个节点的值小于要插入的值
$current = $current->forward[$i]; //current向后移动,直到遇到null或者向后移动的值比当前值大 停止移动.
}
$update[$i] = $current;
}
$current = $current->forward[0]; //current节点变为要插入节点值的后面的一个节点,取forward[0]是因为0层肯定存在
if ($current != null && $current->key == $val) {//插入节点值的后面的节点值相等返回
return;
}
$newLevel = $this->getRandLevel();
if ($newLevel > $this->level) {
for ($i = $this->level + 1; $i < $newLevel + 1; $i++) {
$update[$i] = $this->head; //新产生的层数,要更新节点的值前面节点移必是head
}
$this->level = $newLevel;//更新当前的层数
}
$newNode = new SkipListNode($val, $newLevel); //构建新的node
for ($i = 0; $i <= $newLevel; $i++) { //从第0层到$newLevel层
$newNode->forward[$i] = $update[$i]->forward[$i]; //接管新插入节点的前面节点($update[$i])的(->)后面节点($forward[$i])
$update[$i]->forward[$i] = $newNode;//前面节点($update[$i])的(->)后面节点(forward[$i])为当前新插入的节点
}
}
public function search($val)
{
$current = $this->head;
$currentLevel = $this->level; //从当前层开始找,不用从最大层开始找,因为当前的最大层可能不存在后面节点
do {
while ($current->forward[$currentLevel] != null &&
$current->forward[$currentLevel]->key <= $val) { //第$currentLevel层后面节点不是null//当前节点后面节点为空或者后面节点的值小于等于要查找的值跳出
$current = $current->forward[$currentLevel]; //否则向同一层后面节点继续移动
}
//当前节点值为$val且不为头结点 说明找到
if ($current->key == $val && $current != $this->head) {
return $current;
}
} while (--$currentLevel >= 0);
return false;
}
public function display()
{
for ($i = 0; $i <= $this->maxLevel; $i++) {
$current = $this->head;
while ($current != null ) {
echo $current->key . ' ';
$current = $current->forward[$i];
}
echo PHP_EOL;
}
}
}
$sk = new SkipList(4, 0.5);
$sk->insert(1);
$sk->insert(2);
$sk->insert(3);
$sk->insert(4);
$sk->insert(5);