跳表.

1.跳表的意义

我们都知道 对于一个有序(指的是元素值是升序或者降序的)的数组而言 二分法查询的最坏时间复杂度是O(logn)
我解释一下logn的由来:
比如现在我们有一个长度为8的数组 最坏情况就是分到最后长度为1的时候查询到元素 其中的过程为8 4 2 1 需要执行3次操作 即为log28 = 3 那么简化成复杂度就是O(logn)
然后对于一个有序的链表来说 我们还是需要从头到尾一个一个查询 那么最坏复杂度还是O(n) 但是跳表的出现优化了链表的查询操作

2.跳表的特点

1.一个跳表由若干层链表组成
2.每一层链表都是有序的
3.跳表最下面一层的链表包含了所有的数据
4.如果一个元素出现在了某一层级 那么该层级下面的所以层级都会包含该元素
5.当前层级的元素和指向下一层级的元素始终相同的
6.跳表最上面一层的头元素为head所指向

3.节点类的实现

private class Node<E>{
    // 私有成员
    private E val;// 节点值
    private Node<E> next;// 水平方向上的下一节点
    private Node<E> down;// 竖直方向上的下一节点
    // 构造方法
    public Node(E val, Node<E> next){
        this.val = val;
        this.next = next;
    }
}

4.层级索引的随机生成

此方法致力于随机生成一个待插入的层级数
保证层级为1的概率为0.5 层级为2的概率为0.25(0.5 x 0.5) 层级为3的概率为0.125(0.5 x 0.5 x 0.5)……每一层级的概率是上一层级生成概率的一半

public int randLevel(){
    int level = 1;
    while(Math.random() < 0.5f && level < MAX_LEVEL)
        level++;
    return level;
}

5.插入方法

跳表插入的总体思路为:第一步 检查一下有无未创建的层级 如若有 那么就先创建 第二步 层级创建完毕以后 跳过无需获取前置节点的层级从待插入层级开始从上往下依次获取待插入节点的前置节点 存放入数组中的相应位置 第三步 从待插入层级开始 从上往下依次执行插入操作 最后需要更新记录总层级数的变量

public void add(E val){
    // 首先随机生成一个待插入的层级数
    int level = randLevel();
    // 接着创建一个数组 用于存放前置节点
    Node[] preNodes = new Node[level];
    if(level > curLevelCounts){
        // 保存一下旧的头结点
        Node<E> beforeHead = head;
        // 更新头结点
        head = new Node<>(null, null);
        // 接着定义一个遍历节点 初始化为头结点
        Node<E> curHead = head;
        for(int i = curLevelCounts; i < level - 1; ++i){
            // 为每一个新层创建头结点
            Node<E> node = new Node<>(null, null);
            // 设置down属性
            curHead.down = node;
            curHead = node;
        }
        // 最后还要连接一下curHead和beforeHead之间的线
        curHead.down = beforeHead;
    }
    // 定义一个遍历节点 用于遍历前置节点
    Node<E> preNode = head;
    // 过滤层级
    for(int i = curLevelCounts; i > level; --i){
        preNode = preNode.down;
    }
    // 获取必要层级的前置节点
    for(int i = level - 1; i >= 0; --i){
        while(preNode.next != null && preNode.next.val.compareTo(val) < 0){
            preNode = preNode.next;
        }
        preNodes[i] = preNode;
        preNode = preNode.down;
    }
    Node<E> topNode = null;
    for(int i = level - 1; i >= 0; --i){
        Node<E> node = new Node<>(val, preNodes[i].next);
        preNodes[i].next = node;
        if(topNode != null){
            topNode.down = node;
        }
        topNode = node;
    }
    if(level > curLevelCounts)
        curLevelCounts = level;
}

6.查询方法

在这里插入图片描述

该方法旨在查找是否存在指定值
总体思路:从跳表的最上层链表开始查询 先在当前层级从前往后查找 如果下一节点为空或者下一节点大于指定值的话 那么就前往下一层级寻找 否则就继续在当前层级查找 如果找到了 就返回true 没找到就返回false
然后确实有可能存在当前节点的节点值为空 然后执行if语句 那么将无法执行if语句中的代码 所以需要或上一个node.val == null才行

public boolean search(int target){
    // 从头结点开始查找
    Node<E> node = head;
    while(node != null){
        if(node.val == null || node.val.compareTo(target) < 0){
            if(node.next == null){
                node = node.down;
            }else{
                node = node.next.val > target ? node.down : node.next;
            }
        }else if(node.val.equals(target)){
            return true;
        }else{
            return false;
        }
    }
    return false;
}

7.删除方法

在这里插入图片描述

总体思路:首先找到待删除节点所处的最高层级 然后从最高层级开始往下一层一层执行待删除节点的删除操作 全部删除完毕以后 需要检查一下哪些是空链表 如若存在空链表 就需要更新总层级数和头结点
删除方法中的删除操作是从topIndex开始删除的 topIndex储存的是层级数-1
node.next.val.equals(val)必须写在node.next != null后面 如果反之就报错了

public boolean remove(E val){
    // 定义一个遍历节点 初始化为头结点
    Node<E> node = head;
    int topIndex = -1;
    for(int i = curLevelCounts - 1; i >= ){
        while(node.next != null && node.next.val.compareTo(val) < 0)
            node = node.next;
        if(node.next != null && node.next.val.equals(val) && topIndex == -1){
            topIndex = i;
            break;
        }
        if(node.down == null) 
            return false;
        node = node.down;   
    }
    if(topIndex == -1)
        return false;
    for(int i = topIndex; i >= 0; --i){
        if(node.next != null)
            node.next = node.next.next;
        // 更新遍历节点为下一层级的前置节点
        node = node.down;
        if(node != null){
            while(node.next != null && !node.next.val.equals(val)){
                node = node.next;
            }
        }
    }
    // 删除那些已被清空的链表
    while(curLevelCounts > 1 && head.next == null){
        curLevelCounts--;
        head = head.down;
    }
    // 执行到此 才算真正的删除成功
    return true;
}

8.整体代码

由于待会需要进行节点值的大小比较 所以说需要让泛型继承自Comparable类才行 这样才能调用该类中的compareTo方法进行泛型的大小比较

public class SkipLinkedList<E extends Comparable<E>>{
    // 私有成员
    private Node<E> head;// 头节点
    private int MAX_LEVEL = 16;// 最大的待插入层级
    private int curLevelCounts;// 当前的总层级数
    // 构造方法
    public SkipLinkedList(){
        // 用于初始化头节点
        head = new Node<>(null, null);
    }



    // 节点类
    private class Node<E>{
        // 私有成员
        private E val;// 节点值
        private Node<E> next;// 水平方向上的下一个节点
        private Node<E> down;// 竖直方向上的下一个节点
        // 构造方法
        public Node(E val, Node<E> next){
            this.val = val;
            this.next = next;
        }
    }
    // 随机获取层级数的方法
    private int randLevel(){
        // 首先设置初始值
        int level = 1;
        // 然后设置每一层的概率是上一层的一半 比如获取层级2的概率是获取层级1概率的一半 获取层级3的概率是获取层级2概率的一半 以此类推
        while(Math.random() < 0.5f && level < MAX_LEVEL){
            level++;
        }
        return level;
    }
    // 用于往跳表增加元素的方法
    public void add(E val){
        // 分成三步 第一步.如果在插入之前有些层级还为创建的话 那么就优先创建这些待创建的层级 第二步.待创建完毕所有的层级以后 从待插入的层级开始至上而下依次获取待插入节点的前置节点 第三步.就是从待插入层级开始自上而下依次将待插入节点插入到指定位置处
        // 首先先获取待插入的层级
        int level = randLevel();
        // 接着创建一个数组 用于存放前置节点
        Node[] preNodes = new Node[level];
        if(level > curLevelCounts){
            Node<E> beforeHead = head;
            head = new Node<>(null, null);
            Node<E> curHead = head;// 这个变量旨在遍历新创建层级的头节点
            for(int i = curLevelCounts; i < level - 1; i++){
                Node<E> node = new Node<>(null, null);
                curHead.down = node;
                curHead = node;
            }
            // 最后设置curHead.down为beforeHead
            curHead.down = beforeHead;
        }
        // 定义一个遍历节点 初始化为头节点
        Node<E> preNode = head;
        // 在执行第二步之前 还需要将那些无需执行前置节点获取的层级跳过才行
        for(int i = curLevelCounts - 1; i >= level; i--){
            preNode = preNode.down;
        }
        for(int i = level - 1; i >= 0; i--){
            while(preNode.next != null && preNode.next.val.compareTo(val) < 0){
                preNode = preNode.next;
            }
            preNodes[i] = preNode;
            preNode = preNode.down;
        }
        // 最后一步就是将待插入节点从待插入层级自上而下逐层进行插入操作 插入其实就是连接三条线 一条是前置节点指向待插入节点的 一条是待插入节点指向后置节点的 一条是上置节点指向待插入节点的
        Node<E> topNode = null;
        for(int i = level - 1; i >= 0; i--){
            Node<E> node = new Node<>(val, preNodes[i].next);
            preNodes[i].next = node;
            if(topNode != null){
                topNode.down = preNodes[i];
            }
            topNode = node;
        }
        // 最后 如果有必要的话 还需要更新一下curLevelCounts的值
        if(level > curLevelCounts)
            curLevelCounts = level;
    }
    // 用于判断是否存在指定值 如果存在的话 那么就返回true 反之则返回false
    public boolean search(E target){
        // 首先定义一个遍历节点 初始化为头节点
        Node<E> node = head;
        while(node != null){
            // 如果当前节点的值小于指定值的话
            if(node.val == null || node.val.compareTo(target) < 0){
                // 如果下一节点的值为空的话 那么就直接更新为down节点
                if(node.next == null){
                    node = node.down;
                }else{
                    node = (node.next.val.compareTo(target) > 0) ? node.down : node.next;
                }
            }else if(node.val.equals(target)){
                return true;
            }else{
                return false;
            }
        }
        // 如果找寻完所有的节点以后 依然没有返回值的话 那么就说明指定值没有找到 所以直接返回false
        return false;
    }
    // 用于删除指定的节点 并且判断是否删除成功
    public boolean remove(E val){
        // 首先找到指定值所处的最高层级
        Node<E> node = head;
        int topIndex = -1;// topIndex记录的是层级数-1
        for(int i = curLevelCounts - 1; i >= 0; i--){
            // 首先一路向右找
            while(node.next != null && node.next.val.compareTo(val) < 0){
                node = node.next;
            }
            // 如果找到的话 那么就更新topIndex 并且退出循环
            if(topIndex == -1  && node.next != null && node.next.val.equals(val)){
                topIndex = i;
                break;
            }
            // 如果当前层没有找到的话 那么就往下一层继续找 先判断一下是否存在下一层 如果不存在的话 说明找不到指定值 直接返回false
            if(node.down == null)
                return false;
            node = node.down;
        }
        // 那既然指定值的最高层级已经找打的话 那么现在就开始执行删除操作
        // 如果找完以后topIndex==-1的话 那么说明没有找到指定值
        if(topIndex == -1)
            return false;
        // 从topIndex层开始往下进行删除操作
        for(int i = topIndex; i >= 0; i--){
            if(node.next != null){
                node.next = node.next.next;
            }
            // 接下来更新node为下一层待删除节点的前置节点
            // 先将node更新为down节点
            node = node.down;
            // 因为这是在移动到down元素以后进行的更新next操作 所以说我们需要对node进行判空操作 然后找到下一层的待删除节点的前置节点
            if(node != null) {
                while (node.next != null && !node.next.val.equals(val)) {
                    node = node.next;
                }
            }
        }
        // 我们要将已经被清空的链表彻底清空 彻底清空靠的是更新head节点和curLevelCounts两个变量 但是需要保证的是最后一层的节点不要被清空就行了
        while(curLevelCounts > 1 && head.next == null){
            head = head.down;
            curLevelCounts--;
        }
        // 执行到最后肯定是说明该删除的元素已经被我们成功删除了
        return true;
    }
    // 用于打印跳表
    @Override
    public String toString(){
        // 用于打印跳表
        StringBuilder sb = new StringBuilder();

        // 首先设置一个遍历节点
        Node<E> cur = head;
        while(cur != null){
            // 接着设置一个tmp节点
            Node<E> tmp = cur.next;
            while(tmp != null){

                sb.append(tmp.val).append(", ");


                tmp = tmp.next;
            }

            cur = cur.down;
            if(cur != null)
                sb.append("\n");
        }

        return sb.toString();
    }
}

9.测试

public static void main(String[] args) {
    SkipLinkedList<Integer> sll = new SkipLinkedList<>();
    sll.add(1);
    sll.add(2);
    sll.add(3);
    sll.add(4);
    sll.add(5);
    System.out.println(sll);
    sll.remove(3);
    System.out.println(sll);
    System.out.println(sll.search(6));
}
在 PostgreSQL 数据库中,并没有内置的跳表Skip List)索引实现。PostgreSQL 提供了多种索引类型,如B树索引、哈希索引、GiST索引和GIN索引等,但没有直接支持跳表的索引类型。 B树索引是 PostgreSQL 中最常用的索引类型之一。它适用于范围查询和等值查询,并且可以保持数据有序性。B树索引在处理数据块的平衡性和查询效率方面具有很好的性能。 除了B树索引之外,PostgreSQL 还提供了其他类型的索引用于特定的场景。例如,哈希索引适用于等值查询,可以提供快速的哈希查找;GiST 索引(通用搜索树)和 GIN 索引(通用倒排索引)适用于全文搜索和复杂的匹配查询。 虽然 PostgreSQL 不提供内置的跳表索引实现,但是你可以使用扩展或自定义索引实现跳表的功能。通过编写自定义插件或使用第三方扩展,你可以在 PostgreSQL 中实现跳表索引。这需要一定的开发工作,并且需要充分测试和评估性能。 需要注意的是,自定义实现的跳表索引可能会受到 PostgreSQL 内核版本更新的影响,并且可能无法享受到 PostgreSQL 内置索引的一些优化和支持。 总之,PostgreSQL 并没有内置的跳表索引实现,但提供了其他类型的索引,如B树索引、哈希索引、GiST索引和GIN索引等,用于满足不同的查询需求。如果需要使用跳表索引,你可以考虑自定义实现或使用第三方扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axihaihai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值