Principle and implementation of efficient data type - jump table Java version

This article focuses on the implementation. The first contact with the data structure of jump table was in Redis. At that time, I only learned the theoretical knowledge of jump table, which is difficult to support by theory alone. This can be realized when I was asked about jump table in the process of byte interview

Jump table is a very high-school data structure, which was invented by American scientist William Pugh. In a paper, he introduced the jump table data structure, insert, delete and other operations in great detail

basic thought

First of all, the skip list is an improvement of the ordered linked list. For the ordinary linked list, whether it is an ordinary linked list or an ordered linked list, the search operation of a node needs to be compared one by one from the head. The ordered nature of the ordered linked list is not available here. Is there any way to use the ordered nature of the ordered linked list to reduce the search time complexity of the ordered linked list?

Jump table is such a data structure. Based on the ordered linked list, jump table introduces the concept of layering. Each layer is an ordered linked list. Except for the lowest layer, other layers are indexes. Searching based on this index will be very fast, for example

For the structure in the figure above, if you want to find data 5, first start from the first layer. There is only one element in the first layer. Look down, 5 < 7 for the second time, and then look down. When you reach the third layer 4 < 5 < 7, look down from node 4. For the fourth layer, find data 5

At this point, we understand that this is actually the idea of dichotomy, so that the data can quickly approach the data to be found. This is the basic idea of jump table. Next, we will understand how to realize jump table, how to establish hierarchy, and how to insert and delete from the specific implementation

Data structure definition

First, you must define a node:

/**
 * Implementation of hop table Node
 * @param <T>
 */
class SkipNode<T>{
    int key;
    T value;
    SkipNode right, down;       //Represents the right and down pointers, respectively
    public SkipNode(int key, T value) {
        this.key = key;
        this.value = value;
    }

    /**
     * Initialization node is used to initialize the header node
     * @param key
     */
    public SkipNode(int key){
        this.key = key;
        this.value = null;
    }
}

Then define a class

public class SkipList<T> {
    SkipNode headNode;          //Head node
    int highLevel;              //Level height
    Random random;              //The random value is used to judge whether to add a level later
    final int MAX_LEVEL = 12;   //Maximum layer series

    /**
     * Constructor initialization jump table
     */
    public SkipList() {
        this.random = new Random();
        this.headNode = new SkipNode(Integer.MAX_VALUE);
        highLevel = 0;
    }
}

It should be noted that in order to make the head node the same as other nodes, we define a virtual head node here, and the element in it is integer MAX_ Value, there is also a random value of random, which will be used when inserting later. highLevel represents the level height of the current hop table, MAX_LEVEL represents the maximum number of layers

Search method

The process for finding methods is as follows:

1) Use temp as the header node, which will be used for traversal

2) If temp Key = = key, then the search is successful and the result is returned

3) If temp Right = = null, then the current level does not have this key, and must be down temp = temp down

4) If temp right != null && temp. Key < key, then continue to find temp = temp to the right right

5) If temp right != null && temp. Key > key, then the current level does not have this key, and you must look down for temp = temp down

/**
     * Search method: four cases
     * 1. If temp Key = = key, then the search is successful and the result is returned
     * 2. If temp Right = = null, then the current level does not have this key, and must be down temp = temp down
     * 3. If temp right !=  null && temp. Key < key, then continue to find temp = temp to the right right
     * 4. If temp right !=  null && temp. Key > key, then the current level does not have this key, and you must look down for temp = temp down
     *
     * @param key
     * @return
     */
public SkipNode search(int key) {
  SkipNode temp = this.headNode;
  while (temp != null) {
    if (temp.key == key) {
      return temp;
    } else if (temp.right == null) {
      temp = temp.down;
    } else if (temp.key < key) {
      temp = temp.right;
    } else {
      temp = temp.down;
    }
  }
  return null;
}

Delete method

The process of deleting the method is:

1) If temp Right = = null, then temp = temp down

2) If temp right != null && temp.right. Key = key, then delete temp Right node, and then delete the next level node temp = temp down

3) If temp right != null && temp. right. Key < key, then temp = temp right

4) If temp right != null && temp. right. Key > key, then the current level does not have this key, temp = temp down

    /**
     * Delete: find the previous node to delete, and then delete it. There are four cases:
     * 1. If temp Right = = null, then temp = temp down
     * 2. If temp right !=  null && temp.right. Key = key, then delete temp Right node, and then delete the next level node temp = temp down
     * 3. If temp right !=  null && temp. right. Key < key, then temp = temp right
     * 4. If temp right !=  null && temp. right. Key > key, then the current level does not have this key, temp = temp down
     *
     * @param key
     */
    public void delete(int key) {
        SkipNode temp = this.headNode;
        while (temp != null) {
            if (temp.right == null) {
                temp = temp.down;
            } else if (temp.right.key == key) {
                temp.right = temp.right.right;      //Delete node
                temp = temp.down;                   //Then look down
            } else if (temp.right.key < key) {
                temp = temp.right;
            } else {
                temp = temp.down;
            }
        }
    }

Insertion method

    /**
     * There are three issues to consider when inserting:
     * 1. This node must be inserted at the bottom layer, but does the upper layer need to be inserted? The random value method is used to determine whether to insert this node to the upper layer
     * 2. If the highest level needs to create a new higher level, what should I do? You need to create a new Node as the new head Node and point to the old head Node at the same time
     * 3. How to find the nodes to be inserted in the upper layer? If there is a two-way pointer, it is simple, but how does a one-way pointer operate? With the help of the stack, the current node is stored in the stack every time it goes down, and can be taken out the next time it needs to be inserted
     *
     * @param node
     */
    public void add(SkipNode<T> node) {
        int key = node.key;
        //Determine whether the node exists
        SkipNode findNode = this.search(node.key);
        if (findNode != null) {
            findNode.value = node.value;
            return;
        }
        Stack<SkipNode> stack = new Stack<>();
        SkipNode temp = this.headNode;
        while (temp != null) {
            if (temp.right == null || temp.right.key > key) {
                stack.add(temp);
                temp = temp.down;
            } else {
                temp = temp.right;
            }
        }
        int level = 1;      //Represents the current number of layers
        SkipNode downNode = null;   //Vertical precursor node
        while (!stack.isEmpty()) {
            temp = stack.pop();     //Points to be inserted backward
            SkipNode nodeTemp = new SkipNode(node.key, node.value);
            nodeTemp.down = downNode;
            downNode = nodeTemp;    //Process horizontal direction
            //null description on the right is inserted at the end
            if (temp.right == null) {
                temp.right = nodeTemp;
            } else {
                nodeTemp = temp.right;
                temp.right = nodeTemp;
            }

            //Consider whether you need to insert up
            if (level > this.MAX_LEVEL) break;
            if (this.random.nextDouble() > 0.5) break;   //Luck doesn't need to be inserted
            level++;
            if (level > this.highLevel) {
                highLevel = level;
                //Create a new header node
                SkipNode headNew = new SkipNode(Integer.MAX_VALUE);
                headNew.down = this.headNode;
                this.headNode = headNew;
                stack.add(headNew);
            }
        }

    }

Printing method

 public void print() {
        SkipNode teamNode = headNode;
        int index = 1;
        SkipNode last = teamNode;
        while (last.down != null) {
            last = last.down;
        }
        while (teamNode != null) {
            SkipNode enumNode = teamNode.right;
            SkipNode enumLast = last.right;
            System.out.printf("%-8s", "head->");
            while (enumLast != null && enumNode != null) {
                if (enumLast.key == enumNode.key) {
                    System.out.printf("%-5s", enumLast.key + "->");
                    enumLast = enumLast.right;
                    enumNode = enumNode.right;
                } else {
                    enumLast = enumLast.right;
                    System.out.printf("%-5s", "");
                }

            }
            teamNode = teamNode.down;
            index++;
            System.out.println();
        }

    }

main

    public static void main(String[] args) {
        SkipList<Integer> list = new SkipList<Integer>();
        for (int i = 1; i < 20; i++) {
            list.add(new SkipNode(i, 666));
        }
        System.out.println("Before deletion");
        list.print();
        list.delete(4);
        list.delete(8);
        System.out.println("After deletion");
        list.print();
    }

Complete code

package cn.noteblogs.skipList;

import com.sun.jmx.snmp.SnmpOid;

import java.awt.*;
import java.util.Random;
import java.util.Stack;

/**
 * Implementation of hop table Node
 * @param <T>
 */
class SkipNode<T>{
    int key;
    T value;
    SkipNode right, down;       //Represents the right and down pointers, respectively
    public SkipNode(int key, T value) {
        this.key = key;
        this.value = value;
    }

    /**
     * Initialization node is used to initialize the header node
     * @param key
     */
    public SkipNode(int key){
        this.key = key;
        this.value = null;
    }
}


/**
 * Skip table
 */
public class SkipList<T> {
    SkipNode headNode;          //Head node
    int highLevel;              //Level height
    Random random;              //The random value is used to judge whether to add a level later
    final int MAX_LEVEL = 12;   //Maximum layer series

    /**
     * Constructor initialization jump table
     */
    public SkipList() {
        this.random = new Random();
        this.headNode = new SkipNode(Integer.MAX_VALUE);
        highLevel = 0;
    }

    /**
     * Search method: four cases
     * 1. If temp Key = = key, then the search is successful and the result is returned
     * 2. If temp Right = = null, then the current level does not have this key, and must be down temp = temp down
     * 3. If temp right !=  null && temp. Key < key, then continue to find temp = temp to the right right
     * 4. If temp right !=  null && temp. Key > key, then the current level does not have this key, and you must look down for temp = temp down
     *
     * @param key
     * @return
     */
    public SkipNode search(int key) {
        SkipNode temp = this.headNode;
        while (temp != null) {
            if (temp.key == key) {
                return temp;
            } else if (temp.right == null) {
                temp = temp.down;
            } else if (temp.key < key) {
                temp = temp.right;
            } else {
                temp = temp.down;
            }
        }
        return null;
    }

    /**
     * Delete: find the previous node to delete, and then delete it. There are four cases:
     * 1. If temp Right = = null, then temp = temp down
     * 2. If temp right !=  null && temp.right. Key = key, then delete temp Right node, and then delete the next level node temp = temp down
     * 3. If temp right !=  null && temp. right. Key < key, then temp = temp right
     * 4. If temp right !=  null && temp. right. Key > key, then the current level does not have this key, temp = temp down
     *
     * @param key
     */
    public void delete(int key) {
        SkipNode temp = this.headNode;
        while (temp != null) {
            if (temp.right == null) {
                temp = temp.down;
            } else if (temp.right.key == key) {
                temp.right = temp.right.right;      //Delete node
                temp = temp.down;                   //Then look down
            } else if (temp.right.key < key) {
                temp = temp.right;
            } else {
                temp = temp.down;
            }
        }
    }

    /**
     * There are three issues to consider when inserting:
     * 1. This node must be inserted at the bottom layer, but does the upper layer need to be inserted? The random value method is used to determine whether to insert this node to the upper layer
     * 2. If the highest level needs to create a new higher level, what should I do? You need to create a new Node as the new head Node and point to the old head Node at the same time
     * 3. How to find the nodes to be inserted in the upper layer? If there is a two-way pointer, it is simple, but how does a one-way pointer operate? With the help of the stack, the current node is stored in the stack every time it goes down, and can be taken out the next time it needs to be inserted
     *
     * @param node
     */
    public void add(SkipNode<T> node) {
        int key = node.key;
        //Determine whether the node exists
        SkipNode findNode = this.search(node.key);
        if (findNode != null) {
            findNode.value = node.value;
            return;
        }
        Stack<SkipNode> stack = new Stack<>();
        SkipNode temp = this.headNode;
        while (temp != null) {
            if (temp.right == null || temp.right.key > key) {
                stack.add(temp);
                temp = temp.down;
            } else {
                temp = temp.right;
            }
        }
        int level = 1;      //Represents the current number of layers
        SkipNode downNode = null;   //Vertical precursor node
        while (!stack.isEmpty()) {
            temp = stack.pop();     //Points to be inserted backward
            SkipNode nodeTemp = new SkipNode(node.key, node.value);
            nodeTemp.down = downNode;
            downNode = nodeTemp;    //Process horizontal direction
            //null description on the right is inserted at the end
            if (temp.right == null) {
                temp.right = nodeTemp;
            } else {
                nodeTemp = temp.right;
                temp.right = nodeTemp;
            }

            //Consider whether you need to insert up
            if (level > this.MAX_LEVEL) break;
            if (this.random.nextDouble() > 0.5) break;   //Luck doesn't need to be inserted
            level++;
            if (level > this.highLevel) {
                highLevel = level;
                //Create a new header node
                SkipNode headNew = new SkipNode(Integer.MAX_VALUE);
                headNew.down = this.headNode;
                this.headNode = headNew;
                stack.add(headNew);
            }
        }

    }

    public void print() {
        SkipNode teamNode = headNode;
        int index = 1;
        SkipNode last = teamNode;
        while (last.down != null) {
            last = last.down;
        }
        while (teamNode != null) {
            SkipNode enumNode = teamNode.right;
            SkipNode enumLast = last.right;
            System.out.printf("%-8s", "head->");
            while (enumLast != null && enumNode != null) {
                if (enumLast.key == enumNode.key) {
                    System.out.printf("%-5s", enumLast.key + "->");
                    enumLast = enumLast.right;
                    enumNode = enumNode.right;
                } else {
                    enumLast = enumLast.right;
                    System.out.printf("%-5s", "");
                }

            }
            teamNode = teamNode.down;
            index++;
            System.out.println();
        }

    }

    public static void main(String[] args) {
        SkipList<Integer> list = new SkipList<Integer>();
        for (int i = 1; i < 20; i++) {
            list.add(new SkipNode(i, 666));
        }
        System.out.println("Before deletion");
        list.print();
        list.delete(4);
        list.delete(8);
        System.out.println("After deletion");
        list.print();
    }
}

Keywords: Java Algorithm data structure

Added by valen53 on Mon, 03 Jan 2022 20:02:39 +0200