文章目录
跳跃表总结
解决了有序链表结构查找特定值困难的问题,查找特定值的时间复杂度为O(logn),他是一种可以代替平衡树的数据结构
问题
假如我们要用某种数据结构来维护一组有序的int型数据的集合,并且希望这个数据结构在插入、删除、查找等操作上能够尽可能着快速,那么,你会用什么样的数据结构呢?
数组:
采用二分法可以在0(logn)的时间里查找指定元素,但是插入和删除不友好:查找0(logn),插入和删除0(n)
链表:
查找0(n),但是插入和删除就是0(1)
优化思路
如果我们现在查找元素为9的节点,需要从头节点遍历8次。
我们增加一些路径加快查找速度
现在我们遍历5次就查找到了。
基于这种方法,对于具有n个元素的链表,我们采用logN+1层指针路径的方法,可以实现在0(logn)查找到元素
调表的搜索
例子:查找元素 117
(1) 比较 21, 比 21 大,往后面找
(2) 比较 37, 比 37大,比链表最大值小,从 37 的下面一层开始找
(3) 比较 71, 比 71 大,比链表最大值小,从 71 的下面一层开始找
(4) 比较 85, 比 85 大,从后面找
(5) 比较 117, 等于 117, 找到了节点。
我靠,java程序员表示真难懂!!!
//如果存在x,返回x所在的节点。否则返回x的后继节点
find(x){
p=top; //p指向头节点的最高层节点
while(1){ //不断循环
while(p->next->key<x) //横向查找满足key>=x的p
p=p->next;
if(p-dowm==null){ //如果
return p-next;
}
p=p->down;
}
}
满足redis标准的跳跃表数据结构
public class SkipList{
Node header,tail;
int level; //层数最大的节点
int length; //目前节点的个数
static class level{
Node next; //请进指针
int span; //跨度
}
static class Node implements Comparable { //带有不同层高的节点
Object data=-1;
level[] levels; //存储此节点锁数的层数。每次创建时都会随机生成一个32内随机大小的level数组
double score; //按各个节点的分支大小从小到大排列
Node pre;
public Node(value,level){
this.value=value;
this.level=leve;
}
@Override
public int compareTo(Object o) {
return this.value > ((Node )o).value ? 1 : -1;
}
}
}
调表的插入
先确定元素占据的层数k(采用丢硬币的方式)
然后在level数组中插入相关的元素
int random_level()
{
K = 1;
while (random(0,1)) //包括0,不包括1
K++;
return K;
}
跳跃表的性质
- 跳跃表由很多层组成,每一层都是一个有序列表
- 跳跃表最底层链表包含所有元素
- 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。
- 跳跃表是一种随机化数据结构,通过抛硬币决定层数
对比
- 对比二叉查找树
因为查找查找树的插入、删除、查找也是近似 O(logn) 的时间复杂度。
不过,二叉查找树是有可能出现一种极端的情况的,就是如果插入的数据刚好一直有序,那么所有节点会偏向某一边。 - 红黑树
红黑树可以说是二叉查找树的一种变形,红黑在查找,插入,删除也是近似O(logn)的时间复杂度
而且红黑树插入,删除结点时,是通过调整结构来保持红黑树的平衡,比起跳跃表直接通过一个随机数来决定跨越几层,在时间复杂度的花销上是要高于跳跃表的
自定义简单跳跃表的实现-区别于redis的有序列表
自定义数据结构
public class SkipList{
Node header=new Node(-1,16); //跳跃表的头节点
int levelMax=16; //允许最大层数
int length; //当前跳表节点的个数
int levelCount=1; //当前跳跃表的层数,初始化为1
static class level{ //层节点,相当于hashmap的entry
Node next; //指向下一个节点
int span; //跨越的层数,也就是levels数组的大小
public level(){};
public level(int span){
this.span=span;
this.next=new Node(span);
}
}
static class Node implements Comparable { //带有不同层高的节点
int value=-1; //数据初始化为-1,为了简便为int类型
level[] levels; //存储此节点的层节点。每次创建时都会随机生成一个32内随机大小的level数组,相当于hashmap的链表
public Node(value,span){
this.value=value;
this.span=span;
this.levels=new level(span);
}
@Override
public int compareTo(Object o) {
return this.value > ((Node )o).value ? 1 : -1;
}
}
跳跃表查询方法
待优化,找到值还要一致向下遍历,在levels[i]层找到,则在其下面各层肯定存在
public Node find(int value){
Node temp=header;
for(int levelCount-1;i>=0;i--){ //从最高层遍历到最底层-纵向遍历
while(temp.levels[i].next!=null && temp.levels[i].next.value<value){ //横向遍历,直到值不小于查找值 temp最终存放指定值的前驱
temp=temp.levels[i].next;
}
}
//遍历所有层后,判断是否找到 待定:应该上移判断
if(temp.levels[i].next!=null && temp.levels[i].next.value==value){
System. out.println( value+ " 查找成功");
return temp.levels[i].next.value;
}
return null;
}
向跳跃表插入值,不允许重复
public void insert(int value){
int level =getLevel();
Node newNode = newNode(value, level); //创建一个新节点
Node[] update= newNode[level]; //记录每一层插入位置的前驱节点
Node temp = head;
for(int i = level - 1; i >= 0; i--) { //找到每一层插入的前驱节点
while(temp.levels[i].next != null&& temp.levels[i].next.value< value) {
temp = temp.levels[i].next;
}
update[i] = temp;
}
//把插入节点的每一层连接起来
for( int i = 0; i < level; i++) {
newNode.levels[i].next = update[i].levels[i].next;
update[i].levels[i].next = newNode;
}
//判断是否需要更新跳跃表的层数
if(level > levelCount) {
levelCount = level;
}
size++;
System. out.println( value+ " 插入成功");
}
}
}
在跳跃表删除值
假设删除值一定存在
public void delete(int value){
int level; //存储删除节点得层数
Node temp = head;
Node[] update= newNode[levelCount]; //此处大小设置为levelCount!!
//1. 首先找到删除值所有层得前驱位置
for(int i = levelCount- 1; i >= 0; i--) { //找到每一层插入的前驱节点
while(temp.levels[i].next != null&& temp.levels[i].next. value< value) {
temp = temp.levels[i].next;
}
update[i] = temp;
}
//2. 更新跳跃表得节点数
if(temp.levels[i].next != null&& temp.levels[i].next. value== value) {
size--;
}
System. out.println( value+ " 删除成功");
//删除节点后,连接链表
for(int i=0;i< levelCount;i++) {
if(update[i].levels[i].next != null&& update[i].levels[i].next.value== value) {
update[i].levels[i].next = update[i].levels[i].next.levels[i].next;
}
}
模拟抛硬币
自己理解,和其他人优点区别
int getLevel() {
int level = (int)(Math.random() * (this.levelMax+1)); //随机产生[0,1)的数字
System.out.println( "当前的level = "+ level);
return level;
}
随机生成1-16的整数
方法1:使用Random类
Random r=new Random();
int a=r.nextInt(16)+1; //r.nextInt(16)会生成0-15
方法2:使用Math.random()
int num = (int) (Math.random() * 16 + 1); //Math.random()随机生成[0,1)*16=[1,17) int[1,17)=[1,16]
总结
学习数据结构,不能只学习原理,一定要手动实现一遍
待更新。。。