Java实现跳跃表

目录

概要

本文提供可复用的跳跃表(下称 SkipList)实现,所用语言为 Java,使用场景为单机单线程

跳跃表的特点为:数据有序、不可重复。查询的时间复杂度为 O(logN)

读者可以从 源码 直接复制使用,或者从 gitee 下载使用。

返回目录


运行性能

由下表可知,SkipList 的插入性能是 TreeSet56%~70%

TreeSet 是 Java Collection 集合中的一员,该数据结构的特点是:数据有序、不可重复,查询的时间复杂度为 O(logN)。

为了测试 SkipList 的运行性能,将其与 TreeSet 进行比较,统计了两者连续插入随机数,所耗费的时间。读者可以从 性能测试 获取测试代码。

数据量分别为 10万条、100万条、1000万条,数据提前准备,不计入时间。

每个数据量下,重复测试 10 次,得到 10 个运行时间,去除最大值和最小值后,取平均值。

数据量/万条101001000
SkipList 平均运行时间/毫秒58.71389.021726.5
TreeSet 平均运行时间/毫秒34.0783.315336.5
运行时间比 SkipList / TreeSet1.731.771.42
性能比(运行时间比值的倒数)58%56%70%

返回目录


源码

跳跃表抽象类

声明需要实现的方法。

import java.util.Collection;

public abstract class AbstractSkipList<E> {

    public abstract int size();

    public abstract boolean isEmpty();

    public abstract boolean contains(E e);

    public abstract boolean add(E e);

    public abstract boolean remove(E e);

    public abstract void clear();

    public abstract boolean addAll(Collection<? extends E> c);

    public abstract E first();

    public abstract E last();

}

跳跃表实现类

实现跳跃表。

import java.util.Collection;
import java.util.Comparator;
import java.util.Random;

public class SkipList<E> extends AbstractSkipList<E> {

    private int topLevel = 0;

    private int size = 0;

    private Node<E> head;

    private Random random;

    private final Comparator<? super E> comparator;

    public SkipList() {
        this(null);
    }

    public SkipList(Comparator<? super E> c) {
        head = new Node<>(null, 3);
        this.comparator = c;
    }

    @Override
    public int size() {
        return this.size;
    }

    @Override
    public boolean isEmpty() {
        return this.size == 0;
    }

    @Override
    public boolean contains(E e) {
        return find(e) != null;
    }

    @Override
    public boolean add(E e) {
        if (e == null) {
            throw new NullPointerException();
        }

        int level = randomLevel();
        Node<E> newNode;
        if (level > topLevel) {
            topLevel++;
            resize();
            newNode = new Node<>(e, topLevel);
        } else {
            newNode = new Node<>(e, level);
        }
        Node<E>[] preNodes = getPreNodes(e, newNode.getLevel());
        for (int i = 0; i < preNodes.length; i++) {
            Node<E> pre = preNodes[i];
            newNode.setNextNode(i, pre.getNextNode(i));
            pre.setNextNode(i, newNode);
        }
        size++;
        return true;
    }

    @Override
    public boolean remove(E e) {
        Node<E> del = find(e);
        if (del == null) {
            return false;
        }
        Node<E>[] preNodes = getPreNodes(e, del.getLevel());
        for (int i = 0; i < preNodes.length; i++) {
            Node<E> pre = preNodes[i];
            pre.setNextNode(i, del.getNextNode(i));
            del.setNextNode(i, null);
        }
        while (topLevel > 0) {
            if (head.getNextNode(topLevel) == null) {
                topLevel--;
            } else {
                break;
            }
        }
        size--;
        return true;
    }

    @Override
    public void clear() {
        for (int i = 0; i <= topLevel; i++) {
            head.setNextNode(i, null);
        }
        topLevel = 0;
        size = 0;
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c) {
            if (add(e)) modified = true;
        }
        return modified;
    }

    @Override
    public E first() {
        if (head.getNextNode(0) != null) {
            return head.getNextNode(0).getValue();
        }
        return null;
    }

    @Override
    public E last() {
        Node<E> temp = head;
        for (int i = topLevel; i >= 0; i--) {
            while (temp.getNextNode(i) != null) {
                temp = temp.getNextNode(i);
            }
        }
        return temp.getValue();
    }

    @Override
    public String toString() {
        if (isEmpty()) return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');

        Node temp = head;
        while (temp.getNextNode(0) != null) {
            b.append(temp.getNextNode(0).getValue());
            temp = temp.getNextNode(0);
            if (temp.getNextNode(0) == null) {
                b.append(']');
                break;
            }
            b.append(", ");
        }
        return b.toString();
    }

    private int randomLevel() {
        if (random == null) {
            random = new Random();
        }
        int level = 0;
        int times = topLevel + 1;
        while (times-- > 0) {
            if (!random.nextBoolean()) {
                break;
            }
            level++;
        }
        return level;
    }

    private Node<E>[] getPreNodes(E e, int level) {
        if (e == null) {
            return null;
        }
        Node<E>[] preNodes = new Node[level + 1];
        Node<E> temp = head;
        Comparator<? super E> cpr = comparator;
        Node<E> next;
        if (cpr != null) {
            for (int i = topLevel; i >= 0; i--) {
                while ((next = temp.getNextNode(i)) != null && cpr.compare(next.getValue(), e) < 0) {
                    temp = next;
                }
                if (i <= level) {
                    preNodes[i] = temp;
                }
            }
        } else {
            for (int i = topLevel; i >= 0; i--) {
                while ((next = temp.getNextNode(i)) != null && ((Comparable<? super E>) next.getValue()).compareTo(e) < 0) {
                    temp = next;
                }
                if (i <= level) {
                    preNodes[i] = temp;
                }
            }
        }
        return preNodes;
    }

    private void resize() {
        if (topLevel > head.getLevel()) {
            Node<E> newHead = new Node(null, topLevel);
            for (int i = 0; i <= head.getLevel(); i++) {
                newHead.setNextNode(i, head.getNextNode(i));
                head.setNextNode(i, null);
            }
            head = newHead;
        }
    }

    private Node<E> find(E e) {
        if (e == null) {
            throw new NullPointerException();
        }
        Node<E> temp = head;
        for (int i = topLevel; i >= 0; i--) {
            if (comparator != null) {
                while (temp.getNextNode(i) != null) {
                    int c = comparator.compare(temp.getNextNode(i).getValue(), e);
                    if (c < 0) {
                        temp = temp.getNextNode(i);
                    } else if (c == 0) {
                        return temp.getNextNode(i);
                    } else {
                        break;
                    }
                }
            } else {
                while (temp.getNextNode(i) != null) {
                    Comparable<? super E> v = (Comparable<? super E>) temp.getNextNode(i).getValue();
                    int c = v.compareTo(e);
                    if (c < 0) {
                        temp = temp.getNextNode(i);
                    } else if (c == 0) {
                        return temp.getNextNode(i);
                    } else {
                        break;
                    }
                }
            }
        }
        return null;
    }

    static final class Node<E> {
        E value;
        int level;
        Node<E>[] nextNode;

        public Node(E value, int level) {
            this.value = value;
            this.level = level;
            this.nextNode = new Node[level + 1];
        }

        public E getValue() {
            return value;
        }

        public void setValue(E value) {
            this.value = value;
        }

        public int getLevel() {
            return level;
        }

        public void setLevel(int level) {
            this.level = level;
        }

        public Node<E> getNextNode(int level) {
            return this.nextNode[level];
        }

        public void setNextNode(int level, Node<E> node) {
            this.nextNode[level] = node;
        }

    }

}
性能测试
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.TreeSet;

public class performanceTest {

    public Random random = new Random();
    public final int size = 1000_0000;
    public final int time = 10;

    @Test
    public void SkipListTest() {
        long[] res = new long[time];
        long begin, end;
        for (int i = 0; i < time; i++) {
            int[] nums = getRandomArray(size);
            SkipList<Integer> skipList = new SkipList<>();
            begin = System.currentTimeMillis();
            for (Integer num : nums) {
                skipList.add(num);
            }
            end = System.currentTimeMillis();
            res[i] = end - begin;
        }
        Arrays.sort(res);
        long sum = 0;
        for (int i = 1; i < time - 1; i++) {
            sum += res[i];
        }
        double average = sum / 6.0;
        System.out.println("max: " + res[res.length -1]);
        System.out.println("min: " + res[0]);
        System.out.println("average: " + average);
        System.out.println(Arrays.toString(res));
    }

    @Test
    public void TreeSetTest() {
        long[] res = new long[time];
        long begin, end;
        for (int i = 0; i < time; i++) {
            int[] nums = getRandomArray(size);
            TreeSet<Integer> treeSet = new TreeSet<>();
            begin = System.currentTimeMillis();
            for (Integer num : nums) {
                treeSet.add(num);
            }
            end = System.currentTimeMillis();
            res[i] = end - begin;
        }
        Arrays.sort(res);
        long sum = 0;
        for (int i = 1; i < time - 1; i++) {
            sum += res[i];
        }
        double average = sum / 6.0;
        System.out.println("max: " + res[res.length -1]);
        System.out.println("min: " + res[0]);
        System.out.println("average: " + average);
        System.out.println(Arrays.toString(res));
    }

    public int[] getRandomArray(int size) {
        int[] nums = new int[size];
        for (int i = 0; i < size; i++) {
            nums[i] = random.nextInt();
        }
        return nums;
    }

    public List<Integer> getRandomList(int size) {
        List<Integer> list = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            list.add(random.nextInt());
        }
        return list;
    }
}

返回目录

单元测试

import org.junit.Assert;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.TreeSet;

public class SkipListImplTest{

    public final Random random = new Random();

    @Test
    public void sizeTest() {
        int size = 10000;
        List<Integer> nums = getRandomList(size);

        SkipList<Integer> skipList = new SkipList<>();
        TreeSet<Integer> treeSet = new TreeSet<>();
        skipList.addAll(nums);
        treeSet.addAll(nums);
        Assert.assertEquals(treeSet.size(), skipList.size());
    }

    @Test
    public void isEmptyTest() {
        SkipList<Integer> skipList = new SkipList<>();
        Assert.assertTrue(skipList.isEmpty());
        skipList.add(1);
        Assert.assertFalse(skipList.isEmpty());
        skipList.remove(1);
        Assert.assertTrue(skipList.isEmpty());
    }

    @Test
    public void containsTest() {
        SkipList<Integer> skipList = new SkipList<>();
        int[] nums = new int[]{-99, -56, 1, 78, 265, Integer.MAX_VALUE};
        for (Integer num : nums) {
            skipList.add(num);
        }
        Assert.assertFalse(skipList.contains(Integer.MIN_VALUE));
        Assert.assertFalse(skipList.contains(-987867));
        Assert.assertFalse(skipList.contains(-98));
        Assert.assertFalse(skipList.contains(0));
        Assert.assertFalse(skipList.contains(264));
        Assert.assertFalse(skipList.contains(578676786));

        Assert.assertTrue(skipList.contains(-99));
        Assert.assertTrue(skipList.contains(-56));
        Assert.assertTrue(skipList.contains(1));
        Assert.assertTrue(skipList.contains(78));
        Assert.assertTrue(skipList.contains(265));
        Assert.assertTrue(skipList.contains(Integer.MAX_VALUE));
    }

    @Test
    public void addTest() {
        int size = 10000;
        int[] nums = getRandomArray(size);
        SkipList<Integer> skipList = new SkipList<>();
        TreeSet<Integer> set = new TreeSet<>();
        for (int num : nums) {
            skipList.add(num);
            set.add(num);
        }
        Assert.assertEquals(set.toString(), skipList.toString());
    }

    @Test
    public void removeTest() {
        int size = 10000;
        List<Integer> nums = getRandomList(size);
        SkipList<Integer> skipList = new SkipList<>();
        skipList.addAll(nums);
        for (Integer num : nums) {
            skipList.remove(num);
        }
        Assert.assertTrue(skipList.isEmpty());
    }

    @Test
    public void clearTest() {
        int size = 10000;
        List<Integer> nums = getRandomList(size);
        SkipList<Integer> skipList = new SkipList<>();
        skipList.addAll(nums);
        Assert.assertFalse(skipList.isEmpty());
        skipList.clear();
        Assert.assertTrue(skipList.isEmpty());
    }

    @Test
    public void addAllTest() {
        int size = 10000;
        List<Integer> nums = getRandomList(size);
        SkipList<Integer> skipList = new SkipList<>();
        TreeSet<Integer> set = new TreeSet<>();
        skipList.addAll(nums);
        set.addAll(nums);
        Assert.assertEquals(set.toString(), skipList.toString());
    }

    @Test
    public void firstTest() {
        int size = 1000;
        List<Integer> nums = getRandomList(size);
        SkipList<Integer> skipList = new SkipList<>();
        TreeSet<Integer> set = new TreeSet<>();
        skipList.addAll(nums);
        set.addAll(nums);
        Assert.assertEquals(set.first(), skipList.first());
    }

    @Test
    public void lastTest() {
        int size = 1000;
        List<Integer> nums = getRandomList(size);
        SkipList<Integer> skipList = new SkipList<>();
        TreeSet<Integer> set = new TreeSet<>();
        skipList.addAll(nums);
        set.addAll(nums);
        Assert.assertEquals(set.last(), skipList.last());
    }

    public int[] getRandomArray(int size) {
        int[] nums = new int[size];
        for (int i = 0; i < size; i++) {
            nums[i] = random.nextInt();
        }
        return nums;
    }

    public List<Integer> getRandomList(int size) {
        List<Integer> list = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            list.add(random.nextInt());
        }
        return list;
    }
}

返回目录


致谢

感谢 ExRoc 的指导与反馈。

返回目录


参考

《C++ 实现BigInteger 类》

《以后有面试官问你「跳跃表」,你就把这篇文章扔给他》

返回目录


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值