跳表
定义 优化 实现基本框架
实现基本操作
跳表与平衡搜索树和哈希表的对比
定义
每相邻两个节点升高一层,增加一个指针,让指针指向下下个节点;上面每一层链表的节点个数,是下面一层的节点个数的一半,这样在查找的时候就类似于二分查找。
优化
如上图所示,插入或删除的时候会破坏跳表中这种2:1的对应关系 解决方法:不再要求严格的比例关系,把结点插入到相应位置之后,再给其随机出一个层数,保证每个结点的插入和删除操作和其他结点没关系,都是独立的,不需要调整其他结点的关系; 一般跳表会设计一个最大层数maxLevel的限制和一个多增加一层的概率p; 当p=1/2时,每个节点所包含的平均指针数目为2; 当p=1/4时,每个节点所包含的平均指针数目为1.33。
实现基本框架
定义跳表结点
template < class T >
struct SkiplistNode {
T _data;
vector< SkiplistNode* > _nextv;
SkiplistNode ( T data, int level)
: _data ( data) ,
_nextv ( level, nullptr ) {
}
} ;
实现基础结构
template < class T >
class Skiplist {
public :
typedef SkiplistNode< T> Node;
private :
Node* _head;
size_t _maxlevel = 10 ;
double _p = 0.25 ;
} ;
构造函数
跳表初始状态下,仅有头结点,其层数为1,值为-1;
Skiplist ( ) {
srand ( time ( 0 ) ) ;
_head = new Node ( - 1 , 1 ) ;
}
实现基本操作
查找操作
从头结点开始,按从上层到下层的数据进行查找 target大于当前层下一个不为空的结点,向右走 target小于当前层下一个结点或下一个结点为空,向下走
bool search ( int target) {
size_t level = _head-> _nextv. size ( ) - 1 ;
Node* cur = _head;
while ( level >= 0 ) {
if ( cur-> _nextv[ level] != nullptr && target > cur-> _nextv[ level] -> _data) {
cur = cur-> _nextv[ level] ;
}
else if ( cur-> _nextv[ level] == nullptr || target < cur-> _nextv[ level] -> _data) {
level-- ;
}
else
return true ;
}
return false ;
}
插入数据
先找待插入数据的位置,并且保留待插入位置的每一层的前一个结点 前一个结点:若需要向下查找,则当前结点为待插入结点当前层的前一个结点
vector< Node* > findPrevV ( int num) {
int level = _head-> _nextv. size ( ) - 1 ;
Node* cur = _head;
vector< Node* > prevV ( level + 1 , _head) ;
while ( level >= 0 ) {
if ( cur-> _nextv[ level] != nullptr && num > cur-> _nextv[ level] -> _data) {
cur = cur-> _nextv[ level] ;
}
else if ( cur-> _nextv[ level] == nullptr || num <= cur-> _nextv[ level] -> _data) {
prevV[ level] = cur;
-- level;
}
}
return prevV;
}
void add ( int num) {
vector< Node* > prevV = findPrevV ( num) ;
int n = randomLevel ( ) ;
Node* newnode = new Node ( num, n) ;
if ( n > _head-> _nextv. size ( ) ) {
_head-> _nextv. resize ( n, nullptr ) ;
prevV. resize ( n, _head) ;
}
for ( size_t i = 0 ; i < n; i++ ) {
newnode-> _nextv[ i] = prevV[ i] -> _nextv[ i] ;
prevV[ i] -> _nextv[ i] = newnode;
}
}
删除某结点
先找待删除结点的位置,并且保存待删除结点的每一层的前一个结点 若待删除结点的第0层的前一个结点的下一个结点为空或其值不为num,说明num结点不在跳表中 删除num结点,自底向上对各层进行连接
bool erase ( int num) {
vector< Node* > prevV = findPrevV ( num) ;
if ( prevV[ 0 ] -> _nextv[ 0 ] == nullptr || prevV[ 0 ] -> _nextv[ 0 ] -> _data != num) {
return false ;
}
else {
Node* delnode = prevV[ 0 ] -> _nextv[ 0 ] ;
for ( int i = 0 ; i < delnode-> _nextv. size ( ) ; i++ ) {
prevV[ i] -> _nextv[ i] = delnode-> _nextv[ i] ;
}
delete delnode;
return true ;
}
}
打印跳表
void print ( ) {
int level = _head-> _nextv. size ( ) - 1 ;
for ( int i = level; i >= 0 ; i-- ) {
Node* cur = _head-> _nextv[ i] ;
while ( cur) {
cout << cur-> _data << " " ;
cur = cur-> _nextv[ i] ;
}
cout << endl;
}
}
跳表与平衡搜索树和哈希表的对比
skiplist相比平衡搜索树(AVL树和红黑树)的优势: a、skiplist实现简单,容易控制 ; b、skiplist的额外空间消耗更低 :平衡树节点存储每个值有三叉链,平衡因子/颜色等消耗;skiplist中p=1/2时,每个节点所包含的平均指针数目为2;skiplist中p=1/4时,每个节点所包含的平均指针数目为1.33。 skiplist相比哈希表而言: 缺点:哈希表查找的平均时间复杂度是O(1),比skiplist快 优点:a、遍历数据有序; b、消耗略小一点,哈希表存在链接指针和表空间消耗; c、哈希表扩容有性能损耗; d、哈希表在极端场景下哈希冲突高,效率下降厉害,需要红黑树补足接力。