最近学习redis的zset时候,又看到跳表的思想,突然对跳表的设置有了新的思考
这是19年设计的跳表,在leetcode的执行时间是200+ms
现在我对跳表有了新的想法
1、跳表的设计,类似二分查找,但是不是二分查找,比较像之前遇到的一个面试题,使用有限个数鸡蛋,确定鸡蛋易损程度
2、跳表无法再设计的时候,就达到完美状态,而是在操作过程中一直维护完美状态
基于以上想法,我开始重新进行跳表的设计,在leetCode执行时间为14ms
设计思路如下:
0、设计节点,节点有next和pre两个指针,且因为多层结构,所以是数组表达
1、设计多层数据结构,多层均为有序链表,其中第0层包含所有数值
2、初始时,只有一层结构,先设计为10层结构
3、新增数据时,如果发现步数(即执行next次数)过长(大于3倍层高),就进行抬升节点高度行为,即节点high值增加
最初代码如下:
Node类
class Node {
Node[] next = new Node[10];
Node[] pre = new Node[10];
//节点高度
int high = 0;
//节点值
int value;
//最近一次走到这个节点的步数
int step = 0;
//这个仅是为了后续show方法使用
int k = 0;
}
基础参数及构造器
//头节点
Node head;
int maxHigh = 0;
//步数
int step = 0;
public Skiplist() {
}
查询操作,不直接查是否有,而是查floor值后,与tagert进行比较,查floor作用是,复用
public boolean search(int target) {
if (head == null) {
return false;
}
if (head.value > target) {
return false;
}
//查询Floor
return searchFloor(head, maxHigh, target).value == target;
}
private Node searchFloor(Node node, int high, int target) {
//查到了
if (node.value == target) {
return node;
}
//已经最下层了
if (high == -1) {
return node;
}
//如果next值小于tagert,就进行next操作
while (node.next[high] != null &&
node.next[high].value <= target) {
//步数增加
step++;
node = node.next[high];
node.step = step;
}
//向下找
return searchFloor(node, high - 1, target);
}
新增节点
public void add(int num) {
if (head == null) {
head = new Node();
head.value = num;
//没有head,好处理
return;
}
if (num < head.value) {
Node newHead = new Node();
newHead.value = num;
//比head还小,加上之后,充当新head
setNewHead(newHead, head);
return;
}
step = 0;
Node newNode = new Node();
newNode.value = num;
//找到floor,就加在floor后面
Node node = searchFloor(head, maxHigh, num);
setNext(node, newNode);
if (step > 3 * maxHigh) {
//需要抬高高度了,这个方法很重要,类似hashmap的扩容
resize(newNode);
}
}
先把几个简单的方法展示出来
private void setNext(Node pre, Node node) {
int high = node.high;
if (pre.next[high] == null) {
pre.next[high] = node;
node.pre[high] = pre;
} else {
Node next = pre.next[high];
pre.next[high] = node;
node.pre[high] = pre;
node.next[high] = next;
next.pre[high] = node;
}
}
private void setNewHead(Node newHead, Node head) {
newHead.high = head.high;
for (int i = 0; i <= newHead.high; i++) {
newHead.next[i] = head;
head.pre[i] = newHead;
}
this.head = newHead;
}
重点在resize
private void resize(Node node) {
if (node.high == maxHigh) {
//如果当前高度已经是最高高度了,将maxHigh增高
maxHigh++;
if (maxHigh == 10) {
show();
}
node.high = maxHigh;
head.high = maxHigh;
head.next[maxHigh] = node;
node.pre[maxHigh] = head;
return;
}
//找前者
Node pre = getMoreHighPre(node);
//抬高高度
node.high++;
//加入节点(比如,开始加在0层,这时就记在1层)
setNext(pre, node);
//更新步数值
node.step = pre.step + 1;
//步数还大,继续增高
if (node.step > 3 * (maxHigh + 1)) {
resize(node);
}
}
private Node getMoreHighPre(Node node) {
int high = node.high;
Node pre = node.pre[high];
//找到高一层级的上一个节点
while (pre.high == high) {
pre = pre.pre[high];
}
return pre;
}
删除操作
public boolean erase(int num) {
if (head == null) {
return false;
}
if (head.value == num) {
if (head.next[0] != null && head.next[0].value == num) {
//能不删head尽量不删head
removeNode(head.next[0]);
} else {
//只能删除head
removeHead();
}
return true;
}
//一样,找到对应节点
Node node = searchFloor(head, maxHigh, num);
if (node.value == num) {
//移除
removeNode(node);
return true;
}
return false;
}
private void removeNode(Node node) {
for (int i = 0; i <= node.high; i++) {
//每一层,都要删除
Node pre = node.pre[i];
Node next = node.next[i];
if (next == null) {
//注意可能没有next
pre.next[i] = null;
} else {
pre.next[i] = next;
next.pre[i] = pre;
}
}
}
private void removeHead() {
//删除头节点,就是把老二当老大用
if (head.next[0] == null) {
head = null;
}
Node node = head.next[0];
node.high = head.high;
for (int i = 1; i <= maxHigh; i++) {
if (head.next[i] != null && head.next[i] != node) {
node.next[i] = head.next[i];
head.next[i].pre[i] = node;
}
}
head = node;
}
以上代码执行后,leetCode执行时长为19ms,已经远快于19年的代码
但是,我发现了问题所在
因为数组高度有限,设置的高度为10,如果高度不够,就会出现数组越界,我尝试进行测试,写了show方法
private void show() {
System.out.println("总数"+i+"为出现越界");
System.out.println("0级,并设置k");
head.k = 0;
int k = 0;
Node node = head;
while (node != null) {
node.k = k++;
node = node.next[0];
}
for (int i = 1; i < 10; i++) {
System.out.println(i + "级间隔:");
node = head;
Node next = node.next[i];
while (next != null) {
System.out.print(next.k - node.k + ",");
node = next;
next = node.next[i];
}
System.out.println();
}
}
结果如下
居然一万多数值之后就越界了,思考原因所在
应该是,抬高的node,不应该是插入的那个node,应该是node所在层次的中间node,调整接口如下
通过middleNode,找到需要抬高的node
private void resize(Node node) {
if (node.high == maxHigh) {
//最高层,这个可以接受
maxHigh++;
if (maxHigh == max) {
show();
}
node.high = maxHigh;
head.high = maxHigh;
head.next[maxHigh] = node;
node.pre[maxHigh] = head;
return;
}
//找前人
Node pre = getMoreHighPre(node);
//不应该直接用node升级,应该用node区间的中间值
node = middleNode(pre, node);
node.high++;
//加入节点
setNext(pre, node);
node.step = pre.step + 1;
if (node.step > 3 * (maxHigh + 1)) {
resize(node);
}
}
寻找middleNode的代码如下
private Node middleNode(Node pre, Node node) {
int high = node.high;
if (pre.next[high + 1] == null) {
return getLast(node);
}
Node next = pre.next[high + 1];
int left = getLen(pre, node, node.high);
int right = getLen(node, next, node.high);
if (left == right) {
return node;
}
if (left > right) {
return left(node, (left - right) / 2);
} else {
return right(node, (right - left) / 2);
}
}
private int getLen(Node left, Node right, int high) {
int step = 0;
while (left != right) {
left = left.next[high];
step++;
}
return step;
}
private Node left(Node node, int step) {
if (step == 0) {
return node;
}
return left(node.pre[node.high], step - 1);
}
private Node right(Node node, int step) {
if (step == 0) {
return node;
}
return right(node.next[node.high], step - 1);
}
private Node getLast(Node node) {
int high = node.high;
while (node.next[high] != null) {
node = node.next[high];
}
return node;
}
同时发现,最左侧有一些数字极低值,优化setNewHead方法
private void setNewHead(Node newHead, Node head) {
newHead.high = head.high;
newHead.next[0] = head;
head.pre[0] = newHead;
head.high = 0;
for (int i = 1; i <= newHead.high; i++) {
Node next = head.next[i];
if(next==null){
break;
}
newHead.next[i] = next;
next.pre[i] = newHead;
}
this.head = newHead;
}
执行leetCode,14ms
使用show方法
结果如下:
数据超过我的想象,百万级了
当然,这不是完美的跳表,比如我只在新增时,判断是否需要抬高(resize),查询时没有,可能出现某些节点运气不好,查询就是很慢
完整代码包括test在下方
public class TiaoBIaoNewTest {
static int i =0;
public static void main(String[] args) {
Skiplist skiplist = new Skiplist();
Random random = new Random();
for (i = 0; i < 1000000000; i++) {
skiplist.add(random.nextInt());
}
System.out.println();
}
static class Skiplist {
static int max = 10;
class Node {
Node[] next = new Node[max];
Node[] pre = new Node[max];
int high = 0;
int value;
//最近一次走到这个节点的步数
int step = 0;
int k = 0;
}
Node head;
int maxHigh = 0;
//1)先分割0级
public Skiplist() {
}
public boolean search(int target) {
if (head == null) {
return false;
}
if (head.value > target) {
return false;
}
Node node = searchFloor(head, maxHigh, target);
return node.value == target;
}
int step = 0;
private Node searchFloor(Node node, int high, int target) {
if (node.value == target) {
return node;
}
if (high == -1) {
return node;
}
while (node.next[high] != null &&
node.next[high].value <= target) {
step++;
node = node.next[high];
node.step = step;
}
return searchFloor(node, high - 1, target);
}
public void add(int num) {
if (head == null) {
head = new Node();
head.value = num;
return;
}
if (num < head.value) {
Node newHead = new Node();
newHead.value = num;
setNewHead(newHead, head);
return;
}
step = 0;
Node newNode = new Node();
newNode.value = num;
Node node = searchFloor(head, maxHigh, num);
setNext(node, newNode);
if (step > 3 * maxHigh) {
resize(newNode);
}
}
private void setNewHead(Node newHead, Node head) {
newHead.high = head.high;
newHead.next[0] = head;
head.pre[0] = newHead;
head.high = 0;
for (int i = 1; i <= newHead.high; i++) {
Node next = head.next[i];
if(next==null){
break;
}
newHead.next[i] = next;
next.pre[i] = newHead;
}
this.head = newHead;
}
public boolean erase(int num) {
if (head == null) {
return false;
}
if (head.value == num) {
if (head.next[0] != null && head.next[0].value == num) {
removeNode(head.next[0]);
} else {
removeHead();
}
return true;
}
Node node = searchFloor(head, maxHigh, num);
if (node.value == num) {
removeNode(node);
return true;
}
return false;
}
private void removeNode(Node node) {
for (int i = 0; i <= node.high; i++) {
Node pre = node.pre[i];
Node next = node.next[i];
if (next == null) {
pre.next[i] = null;
} else {
pre.next[i] = next;
next.pre[i] = pre;
}
}
}
private void removeHead() {
if (head.next[0] == null) {
head = null;
}
Node node = head.next[0];
node.high = head.high;
for (int i = 1; i <= maxHigh; i++) {
if (head.next[i] != null && head.next[i] != node) {
node.next[i] = head.next[i];
head.next[i].pre[i] = node;
}
}
head = node;
}
private void resize(Node node) {
if (node.high == maxHigh) {
//最高层,这个可以接受
maxHigh++;
if (maxHigh == max) {
show();
}
node.high = maxHigh;
head.high = maxHigh;
head.next[maxHigh] = node;
node.pre[maxHigh] = head;
return;
}
//找前人
Node pre = getMoreHighPre(node);
//不应该直接用node升级,应该用node区间的中间值
node = middleNode(pre, node);
node.high++;
//加入节点
setNext(pre, node);
node.step = pre.step + 1;
if (node.step > 3 * (maxHigh + 1)) {
resize(node);
}
}
private Node middleNode(Node pre, Node node) {
int high = node.high;
if (pre.next[high + 1] == null) {
return getLast(node);
}
Node next = pre.next[high + 1];
int left = getLen(pre, node, node.high);
int right = getLen(node, next, node.high);
if (left == right) {
return node;
}
if (left > right) {
return left(node, (left - right) / 2);
} else {
return right(node, (right - left) / 2);
}
}
private int getLen(Node left, Node right, int high) {
int step = 0;
while (left != right) {
left = left.next[high];
step++;
}
return step;
}
private Node left(Node node, int step) {
if (step == 0) {
return node;
}
return left(node.pre[node.high], step - 1);
}
private Node right(Node node, int step) {
if (step == 0) {
return node;
}
return right(node.next[node.high], step - 1);
}
private Node getLast(Node node) {
int high = node.high;
while (node.next[high] != null) {
node = node.next[high];
}
return node;
}
private Node getMoreHighPre(Node node) {
int high = node.high;
Node pre = node.pre[high];
while (pre.high == high) {
pre = pre.pre[high];
}
return pre;
}
private void setNext(Node pre, Node node) {
int high = node.high;
if (pre.next[high] == null) {
pre.next[high] = node;
node.pre[high] = pre;
} else {
Node next = pre.next[high];
pre.next[high] = node;
node.pre[high] = pre;
node.next[high] = next;
next.pre[high] = node;
}
}
private void show() {
System.out.println("总数"+i+"为出现越界");
System.out.println("0级,并设置k");
head.k = 0;
int k = 0;
Node node = head;
while (node != null) {
node.k = k++;
node = node.next[0];
}
for (int i = 3; i < max; i++) {
System.out.println(i + "级间隔:");
node = head;
Node next = node.next[i];
while (next != null) {
System.out.print(next.k - node.k + ",");
node = next;
next = node.next[i];
}
System.out.println();
}
}
@Override
public String toString() {
String s = "";
Node node = head;
while (node != null) {
s += node.value + ",";
node = node.next[0];
}
return s;
}
}
}