算法:用Java實現跳錶(SkipList),讓查找的時間複雜度媲美紅黑樹

今天略微有些晚了,我就暫時不講解什麼是跳錶了,先上我實現跳錶的Java代碼以及測試結果,空了再補上

我自己用Java實現的跳錶類:SkipList.java

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: LiYang
 * @Date: 2020/3/6 23:57
 * @Description: 跳錶的實現(增加、刪除和查找,查找是O(logN)的時間複雜度)
 */
public class SkipList {

    /**
     * 跳錶的結點類
     */
    private static class Node {
    
        //跳錶結點的數據
        private Integer data;

        //跳錶結點的左結點
        private Node left;

        //跳錶結點的右結點
        private Node right;

        //跳錶結點的下級結點
        private Node down;

        /**
         * 無參構造函數
         */
        public Node() {

        }

        /**
         * 全參數構造函數
         * @param data 結點值
         * @param left 左結點
         * @param right 右結點
         * @param down 下級結點
         */
        public Node(Integer data, Node left, Node right, Node down) {
            this.data = data;
            this.left = left;
            this.right = right;
            this.down = down;
        }

        /**
         * 重寫toString爲展示數據
         * @return
         */
        @Override
        public String toString() {
            //如果有數據
            if (this.data != null) {

                //就展示數據
                return String.valueOf(this.data);

            //如果沒有數據
            } else {

                //就直接返回null表示
                return "null";
            }
        }

        /**
         * 打印跳錶結點自身和右邊的鏈表數據
         */
        public void printRightList() {
            //把本結點賦值爲當前結點
            Node current = this;

            //遍歷當前結點的右結點
            while (current.getRight() != null){

                //打印結點數據,並用 - 隔開
                System.out.print(current.getRight().getData() + " - ");

                //獲取下一個右結點
                current = current.getRight();
            }
        }

        /**
         * 下面是 Getter 和 Setter 方法
         */
        public Integer getData() {
            return data;
        }

        public void setData(Integer data) {
            this.data = data;
        }

        public Node getLeft() {
            return left;
        }

        public void setLeft(Node left) {
            this.left = left;
        }

        public Node getRight() {
            return right;
        }

        public void setRight(Node right) {
            this.right = right;
        }

        public Node getDown() {
            return down;
        }

        public void setDown(Node down) {
            this.down = down;
        }

    }

    /**
     * 跳錶的最大層數
     * 如果想要完全發揮Integer的logN查詢,可以
     * 將這個值設置爲32
     */
    private static int SKIP_LIST_MAX_LEVEL = 16;

    /**
     * 跳錶每一層的頭結點列表
     */
    private List<Node> headList;

    //當SkipList類實例創建的時候,初始化headList
    {
        //將頭結點列表,聲明爲固定容量(跳錶最大層數)的列表
        headList = new ArrayList<>(SKIP_LIST_MAX_LEVEL);

        //每一層都加入空數據的頭結點
        for (int i = 0; i < SKIP_LIST_MAX_LEVEL; i++) {
            headList.add(new Node());
        }

        //上一層將下一層設置爲下結點
        for (int i = 1; i < SKIP_LIST_MAX_LEVEL; i++) {
            headList.get(i).setDown(headList.get(i - 1));
        }
    }

    /**
     * 獲得新加節點的層數
     * @return 隨機層數
     */
    private int getLevel() {
        //最底層下標爲0,這一層包含所有數據
        int level = 0;

        //每當中標二分之一的概率
        while (Math.random() < 0.5) {

            //層數累加(看能連續中多少個二分之一的概率,也就是多少層)
            level ++;
        }

        //返回當前數據的最高層數,如果超過了最大層,就返回最大層
        return level >= SKIP_LIST_MAX_LEVEL? SKIP_LIST_MAX_LEVEL - 1 : level;
    }

    /**
     * 跳錶的元素添加操作
     * @param data 需要添加的數據
     */
    public boolean add(int data) {
        //先獲取需要插入的層數
        int level = getLevel();

        //獲取最頂層的頭結點
        Node topLevelHead = headList.get(level);

        //新結點(位於上部)
        Node upNode = null;

        //新結點(位於下部)
        Node downNode = null;

        //從頂層的頭結點,一直往右下角方向找
        while (true) {

            //插入數據的新實例結點
            Node newNode = new Node();

            //設置結點數據爲插入的數據
            newNode.setData(data);

            //標記爲下部新結點
            downNode = newNode;

            //從本層頭結點一直往右找,直到找到空,或者右邊結點數據比插入數據大的
            while (topLevelHead.getRight() != null && topLevelHead.getRight().getData() < data) {

                //獲得那個比插入數據剛剛小一點的節點
                topLevelHead = topLevelHead.getRight();
            }

            //剛剛小一點的節點的右邊的數據(剛剛大於或等於插入數據)
            Node right = topLevelHead.getRight();

            //如果右邊的數據存在,且是等於插入數據
            if (right != null && right.getData() == data) {

                //表示數據已存在,不重複加入,返回操作失敗
                return false;
            }

            //如果topLevelHead剛好比插入數據小,且
            //right剛好比插入數據大

            //剛剛小的,設置插入結點爲右結點
            topLevelHead.setRight(newNode);

            //插入結點,設置剛剛小的爲左節點
            newNode.setLeft(topLevelHead);

            //如果有剛剛大的右結點
            if (right != null) {

                //插入節點,設置剛剛大的爲右結點
                newNode.setRight(right);

                //剛剛大的,設置插入節點爲左節點
                right.setLeft(newNode);
            }

            //如果標記的上部結點存在
            if (upNode != null) {

                //將新結點,也就是下部結點,設置爲上部結點的下結點
                upNode.setDown(downNode);
            }

            //如果還有下一層
            if (topLevelHead.getDown() != null) {

                //將剛剛小的結點,用下一層的等值結點替代
                //然後重複這個過程,往右找到合適的地方插入新結點
                topLevelHead = topLevelHead.getDown();

                //更新位於上部的標記結點
                upNode = newNode;

            //如果已經是最底層了(最底層的結點,
            //都沒有下結點 ),結束
            } else {

                //跳出循環
                break;
            }
        }

        //如果沒有找到等值的結點,就會插入
        //並走到這裏,返回插入成功
        return true;
    }

    /**
     * 跳錶的元素刪除操作
     * @param data 待刪除的元素
     */
    public boolean delete(int data) {
        //先找到最頂的相同結點
        Node topNode = find(data);

        //如果遍歷完鏈表所有層都沒找到
        if (topNode == null) {

            //返回刪除失敗
            return false;
        }

        //定義當前頭結點,初始化爲最頂層找到的待刪除數據的等值結點
        Node currentTopNode = topNode;

        //當還沒找到最底層
        while (currentTopNode != null) {

            //如果找到當前層的刪除數據等值結點無右節點
            if (currentTopNode.getRight() == null) {

                //獲取等值結點的左節點
                Node left = currentTopNode.getLeft();

                //等值結點左節點設置右節點爲空
                //也就相當於刪除了這個等值結點
                left.setRight(null);

            //如果找到當前層的刪除數據等值結點有右節點
            } else {

                //找到等值結點的左節點
                Node left = currentTopNode.getLeft();

                //找到等值結點的右節點
                Node right = currentTopNode.getRight();

                //然後讓等值結點兩邊的結點牽手,跨過等值結點牽手
                //中間的等值結點就被刪除了

                //左節點設置右節點爲右節點
                left.setRight(right);

                //右節點設置左節點爲左節點
                right.setLeft(left);

            }

            //繼續往下一層找等值結點,重複上述刪除的過程
            currentTopNode = currentTopNode.getDown();
        }

        //如果走到這裏,就證明找到了刪除元素,並刪除成功了
        return true;
    }

    /**
     * 跳錶元素的查找
     * @param data 要查找的數據
     * @return
     */
    public Node find(int data) {
        //找到頂層的頭結點
        Node topLevelHead = headList.get(SKIP_LIST_MAX_LEVEL - 1);

        //當前節點初始化爲頂層頭結點
        Node current = topLevelHead;

        //頂部節點
        Node topNode = null;

        //尋找最頂部相同元素
        while (true) {

            //如果到了最底層都沒找到
            if (current == null) {

                //那就不存在,返回空
                return null;
            }

            //一直找比查詢值剛剛小的節點
            while (current.getRight() != null && current.getRight().getData() < data) {

                //將剛剛小的節點,作爲當前節點
                current = current.getRight();
            }

            //如果是頭結點,或者找到了末尾
            if (current.getRight() == null) {

                //當前節點垂直到下一層,接着往右找
                current = current.getDown();

            //如果剛剛小的結點的右節點的值不等於查詢的值
            } else if (current.getRight().getData() != data) {

                //當前節點垂直到下一層,接着往右找
                current = current.getDown();

            //最後就是等於
            } else {

                //也就是找到了最頂的等價結點
                topNode = current.getRight();

                //結束查找
                break;
            }
        }

        //返回最頂的結點(所有層結點都是一樣的)
        return topNode;
    }

    /**
     * 打印跳錶
     */
    public void printSkipList() {
        //從底層到頂層打印
        for (int i = 0; i < SKIP_LIST_MAX_LEVEL; i++) {
            System.out.print("第" + i + "層:");
            headList.get(i).printRightList();
            System.out.println();
        }
    }


    /**
     * 跳錶的測試方法
     * @param args
     */
    public static void main(String[] args) {
        //新建跳錶的實例
        SkipList skipList = new SkipList();

        //亂序加入一些數據
        skipList.add(3);
        skipList.add(7);
        skipList.add(5);
        skipList.add(4);
        skipList.add(2);
        skipList.add(6);
        skipList.add(8);
        skipList.add(9);
        skipList.add(0);
        skipList.add(1);

        //打印加入數據後的跳錶
        skipList.printSkipList();

        System.out.println("==================================");

        //刪除一些存在的值,和不存在的值
        System.out.println("刪除3:" + skipList.delete(3));
        System.out.println("刪除5:" + skipList.delete(5));
        System.out.println("刪除7:" + skipList.delete(7));
        System.out.println("刪除15:" + skipList.delete(15));

        System.out.println("==================================");

        //打印刪除後的跳錶
        skipList.printSkipList();

        System.out.println("==================================");

        //查找一些存在的值,和不存在的值
        System.out.println("尋找6:" + skipList.find(6));
        System.out.println("尋找8:" + skipList.find(8));
        System.out.println("尋找3:" + skipList.find(3));
    }

}

運行SkipList類的main方法,觀察控制檯的輸出,測試通過:

0層:0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 -1層:2 - 3 - 6 - 7 - 8 - 9 -2層:3 - 6 - 7 - 9 -3層:3 - 6 - 7 -4層:
第5層:
第6層:
第7層:
第8層:
第9層:
第10層:
第11層:
第12層:
第13層:
第14層:
第15層:
==================================
刪除3true
刪除5true
刪除7true
刪除15false
==================================0層:0 - 1 - 2 - 4 - 6 - 8 - 9 -1層:2 - 6 - 8 - 9 -2層:6 - 9 -3層:6 -4層:
第5層:
第6層:
第7層:
第8層:
第9層:
第10層:
第11層:
第12層:
第13層:
第14層:
第15層:
==================================
尋找66
尋找88
尋找3:null
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章