数据结构-跳表学习-基本结构

引入

程序员小灰的公众号(风趣的漫画&&数据结构和算法) https://mp.weixin.qq.com/s/Ok0laJMn4_OzL-LxPTHawQ

学习过程

问题描述

有序数组查找定值=>有序链表查找目标结点

基本思路

类似查字典时用的目录,先用目录确定起始页码,再翻到字典页做最后的查找。

逻辑图解

基本结构

用双向链表来组成链表网络
基本的结构图

操作思路

查找结点

(1)从最上层索引查找,找到左侧最接近的索引结点,到下一层结点重复查找步骤,一直定位到结点链表;
在这里插入图片描述

(2)最后找到的也许是目标结点;也可能目标结点不存在,找到的是目标结点的前驱结点
在这里插入图片描述

插入结点

在找到前驱结点后,要把新结点插入到前驱结点后;到这还没完事,别忘了维护所涉及的索引层,暂时用Random随机数决定是否晋升为索引结点。
(1)以极端情况——新结点一路运气爆棚,每层索引都晋升,甚至不得不新加一层索引… 为例
在这里插入图片描述

删除结点

与插入结点的思路类似,删除结点后要记得维护索引哦~
别忘了维护索引!

代码实现

结点定义

/**
 * 结点定义
 * 
 * @version 
 * @Description 
 * @author xps
 * @date: Sep 4, 2020
 */
class Node {
	// 上下左右结点
	Node up, down, left, right;
	// 存的值
	int val;
	public Node(int val) {
		super();
		this.val = val;
	}
	
}

跳表定义

/**
 * 跳表定义
 * 
 * @version 
 * @Description 
 * @author xps
 * @date: Sep 4, 2020
 */
class SkipList {
	
	// 跳表的比较头
	Node head = new Node(Integer.MIN_VALUE);
	// 跳表的比较尾
	Node tail = new Node(Integer.MAX_VALUE);
	// 理论最高层数
	int maxLevel = 0;
	// 实际最高层数
	int actMaxLevel = 0;
	public SkipList() {
		super();
		head.right = tail;
		tail.left = head;
	}
	
	/**
	 * 增加新结点
	 * 
	 * @param data 添加结点的数值大小
	 */
	public void add(int data) {
		// 先找到前置结点
		Node frontNode = find(data);
		// 防止结点重复
		if (data == frontNode.val) {
			return;
		}
		// 插入新结点到前置结点后面
		insert(frontNode, new Node(data));
		maxLevel++;
		System.out.println("刚插入新结点" + data);
		printNode(head);
		// 遍历每层索引,随机决定是否在当前层索引添加新结点
		Random random = new Random();
		// 此次添加索引的最高层数(新增一个及结点,最多新加一层索引)
		int highLevel = actMaxLevel >= maxLevel ? maxLevel : (actMaxLevel + 1);
		for (int i = 1; i <= highLevel; i++) {
			boolean flag = random.nextBoolean();
			if (flag) {
				// 如果要新加一层索引
				if (i - actMaxLevel == 1) {
					addNewLevel();
				}
				// 要找到当前链表(新插入结点)对应索引的左结点
				Node leftNode = frontNode;
				while (leftNode.up == null) {
					leftNode = leftNode.left;
				}
				// 上层索引的前置结点
				Node newFrontNode = leftNode.up;
				// 上层的新结点
				Node n = new Node(data);
				// 上层索引执行同样的插入操作
				insert(newFrontNode, n);
				// 上层索引新增结点 和 下层结点关联
				n.down = frontNode.right;
				frontNode.right.up = n;
				frontNode = newFrontNode;
			} else {
				// 当前索引层不添加新结点,则后续索引层也不会添加新索引
				break;
			}
		}
	}
	
	/**
	 * 删除结点
	 * 
	 * @param data 要删除结点的值
	 */
	public void delete(int data) {
		// 仍然是先找到前置结点
		Node frontNode = find(data);
		if (data != frontNode.right.val) {
			System.out.println("未找到结点" + data);
			return;
		}
		// 删除目标结点
		remove(frontNode.right);
		System.out.println("单纯删除结点后");
		printNode(head);
		maxLevel--;
		// 最底层索引的head
		Node firstHead = head;
		while (firstHead.down != null) {
			firstHead = firstHead.down;
		}
		firstHead = firstHead.up;
		// 如果没有索引层
		if (firstHead == null) {
//			actMaxLevel = 0;
			return;
		}
		// 最底层索引上一层的head
		Node secondHead = firstHead.up;
		// 上两层索引一致的标志
		boolean equalFlag = true;
		// 如果只剩一层索引,肯定不会索引重复
		if (secondHead == null) {
			equalFlag = false;
		}
		while (equalFlag && firstHead.right != null) {
			int firstVal = firstHead.right.val;
			int secondVal = secondHead.right.val;
			if (firstVal == secondVal) {
				firstHead = firstHead.right;
				secondHead = secondHead.right;
			} else {
				equalFlag = false;
				break;
			}
		}
		// 如果索引一致,则下层索引替换上层索引
		if (equalFlag) {
			// 此时两层索引都走到了最右(尾)
			// 若重复的是顶层索引,直接让次层索引晋升为顶级索引
			boolean repeatFlag = false;
			if (secondHead.up == null) {
				tail = firstHead;
				firstHead = firstHead.left;
				while (firstHead.left != null) {
					firstHead.up = null;
					firstHead = firstHead.left;
				}
				head = firstHead;
				repeatFlag = true;
			}
			if (!repeatFlag) {
				while (secondHead != null) {
					if (secondHead.up != null) {
						secondHead.up.down = firstHead;
						firstHead.up = secondHead.up;
					}
					secondHead = secondHead.left;
					firstHead = firstHead.left;
				}
			}
			actMaxLevel--;
		}
		
		// 若索引没有存数据结点,则删除这层索引;防止删掉底层链表
		while (head.right.val == Integer.MAX_VALUE && head.down != null) {
			Node newHead = head.down;
			Node newTail = tail.down;
			newHead.up = null;
			newTail.up = null;
			head = newHead;
			tail = newTail;
			actMaxLevel--;
		}
	}

	/**
	 * 查找前置结点
	 * 
	 * @param data 数值
	 * @return 查找数值的前置结点
	 */
	public Node find(int data) {
		// 从当前索引头开始查找
		Node a = head;
		if (data <= Integer.MIN_VALUE || data >= Integer.MAX_VALUE) {
			System.out.println("越界");
			return null;
		}
		while (true) {
			while (data > a.right.val) {
				// 结点右移
				a = a.right;
			}
			if (a.down != null) {
				a = a.down;
			} else {
				// 没有下层结点,表示到了链表层
				break;
			}
		}
		return a;
	}
	
	/**
	 * 插入结点
	 * 
	 * @param frontNode 前置结点
	 * @param newNode 新结点
	 */
	public void insert(Node frontNode, Node newNode) {
		// 右侧结点双向连接
		frontNode.right.left = newNode;
		newNode.right = frontNode.right;
		// 右侧结点双向连接
		frontNode.right = newNode;
		newNode.left = frontNode;
	}
	
	/**
	 * 打印当前层结点
	 * 
	 * @param head 一层的头结点
	 */
	public void printNode(Node head) {
		Node a = head;
		while (a != null) {
			System.out.print(a.val + "\t");
			a = a.right;
		}
		System.out.println();
		if (head.down != null) {
			printNode(head.down);
		}
	}
	
	/**
	 * 删除目标结点
	 * 
	 * @param node 要删除的目标结点
	 */
	public void remove(Node node) {
		// 前置结点 后置结点直接关联
		Node leftNode = node.left;
		Node rightNode = node.right;
		leftNode.right = rightNode;
		rightNode.left = leftNode;
		// 如果上层索引有这个结点的索引,也要删除
		if (node.up != null) {
			remove(node.up);
		}
	}
	
	/**
	 * 新增一层索引
	 * 
	 */
	public void addNewLevel() {
		Node newHead = new Node(Integer.MIN_VALUE);
		Node newTail = new Node(Integer.MAX_VALUE);
		newHead.right = newTail;
		newTail.left = newHead;
		newHead.down = head;
		newTail.down = tail;
		head.up = newHead;
		tail.up = newTail;
		// 重置整个链表的索引头和索引尾
		head = newHead;
		tail = newTail;
		actMaxLevel++;
	}
	
}

测试

public class TestSkipList {

	public static void main(String[] args) {
		SkipList list = new SkipList();
		list.add(50);
		list.add(15);
		list.add(13);
		list.add(20);
		System.out.println("删除前:");
		list.printNode(list.head);
		list.delete(20);
		System.out.println("删除20后");
		list.printNode(list.head);
		list.delete(50);
		System.out.println("删除50后");
		list.printNode(list.head);
		list.delete(15);
		System.out.println("删除15后");
		list.printNode(list.head);
		list.add(100);
		System.out.println("添加100后");
		list.printNode(list.head);
	}

}

打印结果

success

总结

数组的二分查找是建立在下标已知的基础上,链表本身查找元素困难,为了类比数组的查找,给链表添加索引,目标结点和索引结点比较,缩小范围。(空间换时间)

扩展

据说redis的底层实现也用到了跳表,那有空得学习。

后记
断断续续的两天,才写完这篇博客,幸亏那时的草稿本还在,不然重新画图又要花费不少时间;csdn的markdown编辑器倒是还挺全面的,我一眼注意到了那个数学公式,点进去跳到了可汗学院的项目…看来以后写公式不用粘图片了;最后呢,分享一句伟大科学家牛顿的一句话,“用多重又混杂的方式发现不了真理,简简单单才是正确的打开方式”(Truth is ever to be found in simplicity, and not in the multiplicity and confusion of things)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值