前言
最近在看Redis资料的时候,看见了跳表这种数据结构,非常好奇跳表是什么样的一种数据结构,它的原理是怎么样的,有什么优点值得Redis使用这种数据结构来开发功能?所以,就查了一些资料,看了不同的实现版本,总算是明白跳表的原理了,这里总结一下,分享给大家。一、跳表是什么?
跳表是一个随机化的数据结构,可以被看做二叉树的一个变种,它在性能上和红黑树,AVL树不相上下,但是跳表的原理非常简单,目前在Redis和LeveIDB中都有用到。它采用随机技术决定链表中哪些节点应增加向前指针以及在该节点中应增加多少个指针。跳表结构的头节点需有足够的指针域,以满足可能构造最大级数的需要,而尾节点不需要指针域。
采用这种随机技术,跳表中的搜索、插入、删除操作的时间均为O(logn),然而,最坏情况下时间复杂性却变成O(n)。相比之下,在一个有序数组或链表中进行插入/删除操作的时间为O(n),最坏情况下为O(n)。
二、跳表的由来
1.数组的优缺点
我们知道数组的优点是可以随机访问,时间复杂度都是O(1),缺点是数组是连续的内存空间,非尾部元素的插入和删除操作效率很低,而且其长度也是固定的,不利于扩展,实现预留空余空间给新元素,比较浪费空间;2.列表的优缺点
而列表这种数据结构,刚好解决了数组的缺点,元素的插入删除非常方便高效,且不需要连续内存,可以方便的扩展元素数目,而不会造成内存空间浪费。但是列表的查找效率非常低,时间复杂度O(n)。3.跳表的原理
跳表这种数据结构的思想就是,既想要数组这样的查找效率,有想要列表的增删高效。跳表首先是一个列表,它要达到数组的随机访问效率是很难的,所以,退而求其次,跳表借用二分查找的思想来提高查找的效率。跳表的原理非常简单,跳表其实就是一种可以进行二分查找的有序链表。跳表的数据结构模型如图(来自百度百科):
可以看到,跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。首先在最高级索引上查找最后一个小于当前查找元素的位置,然后再跳到次高级索引继续查找,直到跳到最底层为止,这时候以及十分接近要查找的元素的位置了(如果查找元素存在的话)。由于根据索引可以一次跳过多个元素,所以跳查找的查找速度也就变快了。
三、跳表的节点定义及查找实现
跳表属于有序列表,有序这个意思并不一定值的是存储的值有序。常规列表,我们一般是对节点的值进行操作,进行查找、删除或插入。跳表除了这样实现外,还可以定义节点的key,用key来表示列表的有序状态,而其中可以存储节点的值,这个值不参与节点的排序。这样,我们就可以使用跳表来存储数据,使用key作为条件,进行数据的增删改查,这样节点数据的值的改变,并不会影响列表的有序状态,没破坏跳表的结构。从某种意义上来说,这个key倒很像数组的索引,我们可以通过这个索引来取节点的值、修改节点的值、删除节点等。1.跳表节点定义
template<typename K,typename V>
typedef struct tag_skip_node_t<K,V> {
K m_key; //跳表节点的key,用于列表排序
V m_value; //跳表节点存储的值
struct tag_skip_node_t<K,V>* m_next; //当前层列表的下一个节点
struct tag_skip_node_t<K,V>* m_pre; //当前层列表的上一个节点
struct tag_skip_node_t<K,V>* m_down; //当前层列表的下一级列表的节点
} skip_node_t;
2.跳表节查找
template<typename K,typename V> bool skip_list_t<K,V>::find(K key, V &v) {
skip_node_t *current_head = m_head;
skip_node_t *current_node = m_head == NULL ? NULL : m_head->m_next;
return find_next_level(current_head, current_node, current_node, key, v);
}
template<typename K,typename V> bool skip_list_t<K,V>::find_next_level(skip_node_t *current_head, skip_node_t *left, skip_node_t *right, K key, V &v) {
if (current_head == NULL || left == NULL || right == NULL) return false;
skip_node_t *current_node = left;
bool is_overlap = left == right;
if (current_node->m_key == key) {
v = current_node->m_value;
return true;
}
if (current_node->m_key > key) {
if (current_head->m_down != NULL) {
if (is_overlap) {
return find_next_level(current_head->m_down, current_head->m_down->m_next, left->m_down, key, v);
} else {
return find_next_level(current_head->m_down, left->m_down, right->m_down, key, v);
}
} else {
return false;
}
} else {
while (current_node->m_next != NULL && current_node->m_next->m_key < key) {
current_node = current_node->m_next;
if (current_node->m_key == key) {
v = current_node->m_value;
return true;
}
left = current_node;
right = is_overlap ? current_node : right;
}
return find_next_level(current_head->m_down, left->m_down, right->m_down, key, v);
}
return false;
}