跳表(Skip List)是一种概率性数据结构,它通过在普通有序链表的基础上增加多级索引层来实现快速的查找、插入和删除操作。跳表的效率可以与平衡树相媲美,其操作的时间复杂度也是O(log n),但跳表的结构更简单,更易于实现。
forward指针数组的长度等于跳表的最大层数。它在所有层级上都指向该层的第一个实际节点(如果存在)。跳表通过简单的随机化过程来避免复杂的重平衡操作,使得它成为一种既高效又易于实现的数据结构选项。
- import java.util.Random;
-
- class SkipListNode {
- int value;
- SkipListNode[] forward; // 指向不同层的指针数组
-
- public SkipListNode(int value, int level) {
- this.value = value;
- this.forward = new SkipListNode[level + 1];
- }
- }
-
- public class SkipList {
- private static final float P = 0.5f;
- private static final int MAX_LEVEL = 16;
- private SkipListNode head;
- private int level;
- private Random random;
-
- public SkipList() {
- level = 0;
- head = new SkipListNode(0, MAX_LEVEL);
- random = new Random();
- }
-
- // 随机生成节点的层数
- private int randomLevel() {
- int lvl = 1;
- while (random.nextFloat() < P && lvl < MAX_LEVEL) {
- lvl++;
- }
- return lvl;
- }
-
- // 插入节点
- public void insert(int value) {
- int lvl = randomLevel();
- SkipListNode newNode = new SkipListNode(value, lvl);
-
- SkipListNode current = head;
- SkipListNode[] update = new SkipListNode[MAX_LEVEL + 1];
-
- for (int i = level; i >= 0; i--) {
- while (current.forward[i] != null && current.forward[i].value < value) {
- current = current.forward[i];
- }
- update[i] = current;
- }
-
- for (int i = 0; i <= lvl; i++) {
- newNode.forward[i] = update[i].forward[i];
- update[i].forward[i] = newNode;
- }
-
- if (lvl > level) {
- level = lvl;
- }
- }
-
- // 查找节点
- public boolean search(int value) {
- SkipListNode current = head;
- for (int i = level; i >= 0; i--) {
- while (current.forward[i] != null && current.forward[i].value < value) {
- current = current.forward[i];
- }
- }
- current = current.forward[0];
- return current != null && current.value == value;
- }
-
- // 删除节点
- public void delete(int value) {
- SkipListNode[] update = new SkipListNode[MAX_LEVEL + 1];
- SkipListNode current = head;
- for (int i = level; i >= 0; i--) {
- while (current.forward[i] != null && current.forward[i].value < value) {
- current = current.forward[i];
- }
- update[i] = current;
- }
-
- current = current.forward[0];
- if (current.value == value) {
- for (int i = 0; i <= level; i++) {
- if (update[i].forward[i] != current) break;
- update[i].forward[i] = current.forward[i];
- }
- while (level > 0 && head.forward[level] == null) {
- level--;
- }
- }
- }
-
- // 打印跳表的内容
- public void display() {
- System.out.println("SkipList: ");
- for (int i = 0; i <= level; i++) {
- SkipListNode node = head.forward[i];
- System.out.print("Level " + i + ": ");
- while (node != null) {
- System.out.print(node.value + " ");
- node = node.forward[i];
- }
- System.out.println();
- }
- }
- }
-
- // 使用示例
- public class Main {
- public static void main(String[] args) {
- SkipList list = new SkipList();
- list.insert(3);
- list.insert(6);
- list.insert(7);
- list.insert(9);
- list.insert(12);
- list.insert(19);
- list.insert(17);
-
- list.display();
-
- System.out.println("Searching 6: " + list.search(6));
- System.out.println("Searching 15: " + list.search(15));
-
- list.delete(6);
- System.out.println("After deleting 6: ");
- list.display();
- }
- }
这段代码首先定义了SkipListNode类,它是跳表节点的结构,包括节点值和一个数组forward,数组中每个元素是对应层级的下一个节点的引用。SkipList类实现了跳表,包括初始化、插入、查找、删除和打印跳表的方法。
insert方法用于插入新的节点。search方法用于查找一个值,如果找到,则返回true。delete方法用于删除一个值。display方法用于打印跳表的所有层级和节点。假设我们有一个跳表,它当前的状态如下,其中每一行代表一个层级(层级0是最底层,包含所有元素):
- 层级3:1 --------------------------------> 9
- 层级2:1 ------------> 5 ------------> 9
- 层级1:1 ----> 3 ----> 5 ----> 7 ----> 9
- 层级0:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 9
现在,我们想要插入一个新的节点,值为8,并假设通过随机过程,决定新节点8将出现在层级0、1和2上(不出现在层级3上)。下面是插入过程的步骤:
从跳表的最高层(在这个例子中是层级3)开始寻找,直到找到比插入值小的最大节点。因为8不会被插入到层级3,我们直接从层级2开始:
1开始,遍历到5,因为9大于8,所以5是层级2的插入位置。1开始,遍历到5,然后到7,因为9大于8,所以7是层级1的插入位置。1开始,按顺序遍历,直到7,因为9大于8,所以7是层级0的插入位置。5和9之间插入8,更新5的下一个指针为8,8的下一个指针为9。7和9之间插入8,更新7的下一个指针为8,8的下一个指针为9。7和9之间插入8,更新7的下一个指针为8,8的下一个指针为9。插入8后,跳表变为:
- 层级3:1 --------------------------------> 9
- 层级2:1 ------------> 5 -------> 8 ----> 9
- 层级1:1 ----> 3 ----> 5 ----> 7 -> 8 -> 9
- 层级0:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9
在这个例子中,新插入的节点8并没有增加跳表的总层数,因此不需要调整。
通过这个例子,你可以看到插入过程如何在每一层找到正确的插入位置,并更新指针来维护跳表的结构。这个过程确保了跳表的搜索效率,使得搜索、插入和删除操作的时间复杂度都为O(log n)。