IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 2021-07-10 -> 正文阅读

[数据结构与算法]2021-07-10

LeetCode1206题 设计跳表

图示

在这里插入图片描述
在跳表中,第0层为原始的链表。第1层为第1级索引,第2层为第2级索引…层数越高,该层的节点越少。

在跳表中进行查找时,从最高层开始查找。比如如果我们需要查找10这个元素,就会经过以下路线:第二层的1->第一层的1->第一层的4->第一层的7->第一层的9->原始链表的9->原始链表的10。

由于从最高层(节点最少)的层开始查找,因此当数据较多时,相比普通的线性查找,跳表的查找可以快速缩小查找范围(有点类似于二分,但不完全是)。查找得时间复杂度是O(logn)。

代码实现

上图是一个单链表,为了便于删除,下面的代码是用双链表实现的。注释写得比较详细了。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 跳表
 * 跳表有0,1,2,3......29层,一共有30层
 * 第0层为原始的双链表
 * 第1层为第1级索引
 * 第2层为第2级索引
 * ......
 * 层数越往上,该层的节点越少
 */
public class SkipListMultiSet {

    /**
     * 跳表的节点。forward[i]代表节点在第i层的下一个节点是什么
     * prevs[i]代表节点在第i层的上一个节点是什么
     */
    static class Node {
        Integer num;
        int cnt;
        Node[] forwards = new Node[TIER_NUM];
        Node[] prevs = new Node[TIER_NUM];

        public Node(Integer num, int cnt) {
            this.num = num;
            this.cnt = cnt;
        }

        @Override
        public String toString() {
            return "{" +
                    "num=" + num +
                    ", cnt=" + cnt +
                    '}';
        }
    }

    //写死跳表三十层
    private static final int TIER_NUM = 30;

    //跳表的哑头节点。
    private final Node head = new Node(null, 0);

    //跳表的哑尾节点
    private final Node tail = new Node(null, 0);


    public SkipListMultiSet() {
        //哑头节点和哑尾节点互相指向
        for (int i = 0; i < head.forwards.length; i++) {
            head.forwards[i] = tail;
            tail.prevs[i] = head;
        }
    }

    /**
     * 查找target的数是否在跳表中
     *
     * @param target 要查找的数
     * @return 如果存在,返回true,否则返回False
     */
    public boolean search(int target) {
        Node targetNode = this.findNode(target);
        return targetNode != null && targetNode.cnt > 0;
    }

    /**
     * 将num添加到跳表
     *
     * @param num 要添加的元素
     */
    public void add(int num) {

        //先看看num的节点是否在链表中,如果是的话直接把cnt加一
        Node targetNode = findNode(num);
        if (targetNode != null) {
            targetNode.cnt++;
            return;
        }

        //每一层插入在哪个节点后面?
        Node[] nodeListToInsertAfter = getNodeListToInsertAfter(num);

        Node newNode = new Node(num, 1);

        //在底层插入
        insertAfter(newNode, nodeListToInsertAfter[0], 0);

        //逐层向上插入。
        for (int i = 1; i < nodeListToInsertAfter.length; i++) {

            //从第1层不停的向上插入。每一次的概率都是0.5,如果有一次决定不插入,就停止整个过程
            double d = Math.random();
            if (d < 0.5) {
                if (nodeListToInsertAfter[i].forwards[i] == newNode) {//由于这里nodeListToInsertAfter就是向下查找时的路径,因此可能有垂直向下的路径,要避免重复插入
                    continue;
                }
                insertAfter(newNode, nodeListToInsertAfter[i], i);
            } else {
                break;
            }
        }


    }

    /**
     * 把newNode插在anchor的后面
     *
     * @param newNode 要插入的新节点
     * @param anchor 在anchor节点后面插入
     */
    public void insertAfter(Node newNode, Node anchor, int tier) {
        Node originalAnchorNext = anchor.forwards[tier];
        anchor.forwards[tier] = newNode;
        newNode.forwards[tier] = originalAnchorNext;
        originalAnchorNext.prevs[tier] = newNode;
        newNode.prevs[tier] = anchor;
    }

    /**
     * 【调用前提】跳表的数据结构中不含num这个节点
     * 要在跳表的数据结构中插入num这个节点,这个函数可以返回一个数组返回数组nodeListToInsertAfter,表示在每一层上,在哪个节点后插入。
     *
     * @param num 要插入的数字
     * @return 返回数组nodeListToInsertAfter[i]表示在第i层的这个节点后面插入。比如在第5层上,就在nodeListToInsertAfter[i]后面插入
     */
    private Node[] getNodeListToInsertAfter(int num) {
        Node[] nodeToInsertAfter = new Node[TIER_NUM];

        //从顶层向下遍历
        int tier = TIER_NUM - 1;
        Node ptr = head;

        //遍历的过程和findNode类似。
        while (tier >= 0) {
            Node next = ptr.forwards[tier];
            if (next == tail && tier == 0) {//如果到了第0层的末尾,则第0层应该在ptr后面插入,并break
                nodeToInsertAfter[tier] = ptr;
                break;
            } else if (next == tail) {//遍历到了非0层的末尾,应该在ptr后面插入,并下降一层
                nodeToInsertAfter[tier] = ptr;
                tier--;
            } else if (next.num < num) {//如果下一个节点比要插入的数字小,则要插入的位置应该在这一层的更后面
                ptr = next;
            } else if (next.num > num) {//如果下一个节点比要插入的数字大,则在这一层上,应该在ptr后面插入。同时下降一层
                nodeToInsertAfter[tier] = ptr;
                tier--;
            } else if (next.num == num) {
                nodeToInsertAfter[tier] = next;
                tier--;
            }
        }
        return nodeToInsertAfter;
    }

    /**
     * 从跳表中删除num
     *
     * @param num 要删除的数字
     * @return 如果跳表原来存在num,删除后返回true。如果跳表中不存在num,就返回false。
     */
    public boolean erase(int num) {
        //寻找是否存在num的节点。
        Node targetNode = findNode(num);
        if (targetNode == null) {
            return false;
        } else {
            if (targetNode.cnt > 0) {//先将cnt减1,如果cnt变为0,就从数据结构中移除掉这个节点
                targetNode.cnt--;
                if (targetNode.cnt == 0) {
                    removeThisNode(targetNode);
                }
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * 将anchor的节点在所有层的索引当中删除
     *
     * @param anchor 要删除的节点
     */
    private void removeThisNode(Node anchor) {
        for (int tier = 0; tier < TIER_NUM; tier++) {
            Node preAnchor = anchor.prevs[tier];
            Node postAnchor = anchor.forwards[tier];
            if (preAnchor != null && postAnchor != null) {
                preAnchor.forwards[tier] = postAnchor;
                postAnchor.prevs[tier] = preAnchor;
            }
        }

    }

    /**
     * 找到内部值为num的节点。如果找不到,返回null
     *
     * @param num 要查找的数
     * @return 如果存在值为num的节点,就返回它,否则就返回null
     */
    private Node findNode(int num) {

        //从最高层,也就是节点最少的层开始找
        int tier = TIER_NUM - 1;
        Node ptr = head;
        while (tier >= 0) {
            Node next = ptr.forwards[tier];
            if (next == tail && tier == 0) {//如果已经到了底层的最尾,说明找不到
                return null;
            } else if (next == tail) {//如果到了最尾部但不是底层,就向下一层
                tier--;
            } else if (next.num < num) {//如果下一个节点小于num,说明要找的节点应该还在这一层的后面
                ptr = next;
            } else if (next.num > num) {//如果下一个节点大于num,说明要找的节点夹在ptr和next之间,但不在这一层。于是向下一层找
                tier--;
            } else if (next.num == num) {//找到了!
                return next;
            }
        }

        return null;
    }

    /**
     * 按层输出跳表
     *
     * @return 按层输出跳表的表示
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        int tier = TIER_NUM - 1;
        while (tier >= 0) {
            List<Node> li = new ArrayList<>();
            Node ptr = head;
            while (ptr != null) {
                li.add(ptr);
                ptr = ptr.forwards[tier];
            }
            String line = li.stream().map(Object::toString).collect(Collectors.joining("<->"));

            builder.append(line);
            builder.append("\n");
            tier--;
        }
        return builder.toString();
    }
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-07-11 16:51:09  更:2021-07-11 16:51:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/6 11:57:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码