Redis研究-3.4 为何使用Redis跳跃表

好几天没把笔记整理上来了,一个是这几天在忙着一些投标以及项目论文的事情,哈哈,还有么 就是画图也是比较耗费我时间的,如果有好的画图建议或者工具,麻烦推荐一下,谢谢。废话不多说,直接进入今天的两个主题:二叉搜索树,平衡二叉树。

1.二叉搜索树(BST)

二叉搜索树的定义很简单:是二叉树,且满足两个条件:如果有左孩子,则左孩子的关键字一定不大于父节点的关键字,如果有右孩子,则右孩子的关键字不小于父节点的关键字。同时,左右子树都是二叉搜索树。

BST_1

中序遍历得到的结果是:1、2、3、4、5、6、7、8、9、10

BST_2

中序遍历得到的结果是:1、2、3、4、5、6、7、8、9、10

现在有一个场景,加入我要在这颗树中查找一个关键字11,如果没有的话,则加入到这颗树中,查询步骤是:

1.针对BST_1,根节点是6,因为11大于6,则查找根节点的右孩子(因为二叉搜索树的右孩子的关键字不小于父节点);

2.右孩子的值是9,小于11,继续查他的右孩子;

3.右孩子的值是10,小于11,继续查他的右孩子;

4.因为没有右孩子了,所以,需要将其添加为右孩子。

针对BST_2,我们只需要一直找右孩子就可以了。我们再看一个二叉搜索树:

BST3

中序遍历得到的结果是:1、2、3、4、5、6、7、9、10

现在我要查找一个节点,其中的关键字是8的一个节点,如果不在这颗树中,则把它加到树中作为一个节点:

1.根节点是6,小于8,找右孩子;

2.右孩子的值是9大于8,找他的左孩子;

3.左孩子的值7小于8,找他的右孩子;

4.因为没有右孩子了,所以,将其添加为7的右孩子。

从上面几个过程中我们发现一个规律:动态查找过程中,我们不需要重构这个结构,只是在叶子节点上进行操作,非常方便。

那如果要删除一个节点呢?

删除节点的时候,有几种情况需要考虑:

1.该节点是叶子节点,因此,不用重构;

2.该节点只有左子树或者右子树,则把左子树或者右子树的根,接到删除节点的父节点上就可以了;

3.如果该节点两者都有呢?

我们一个一个的来看:

1.该节点是叶子节点。

我们要删除BST3中的节点7,则先按照查询步骤找到这个节点,发现他没有左子树,也没有右子树,则直接删除,并把该节点的父节点的左孩子或者右孩子(取决于该节点是左孩子还是右孩子)清空即可。

2.该节点只有左子树。

我们要删除BST3中的节点4,则先按照查询步骤找到这个节点,发现他只有左子树,因此,只要删除该节点,并把该节点的左孩子放在改节点的位置即可。

3.该节点只有右子树:

我们要删除BST2中的节点5,则先按照查询步骤找到这个节点,发现他只有由子树,因此,只要删除该节点,并把该节点的右孩子放在该节点的位置即可。

4.该节点有左子树,也有右子树。

我们现在要删除BST1中的根节点,则中序遍历这个节点的左子树,得到的此节点前的那个节点,就是要删除节点的前驱。

比如上述根节点的左子树的中序遍历是:1、2、3、5,则,我是要删除节点6的前驱,将该前驱替代要删除的节点的位置,并设置左指向和右指向即可。

,我们现在再来看一BST。

现在我要删除节点9,按照上面的步骤,9有左右孩子,因此,先做9的左子树的中序遍历,找到9的前驱是8.5,然后来替换。最终得到结果是:

,说了那么多,还是上上代码吧,这次用java代码:

package com.tang.bst;

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

public class BST<Key extends Comparable<Key>> {
	BSTNode<Key> root;

	public BST() {
	}

	/**
	 * 插入一个节点
	 *
	 * @param data
	 *            节点的数据
	 * @return
	 */
	public boolean insert(Key data) {
		if (null == data)
			return false;
		if (null == this.root) {
			this.root = new BSTNode<Key>(data);
			this.root.parent = null;
			this.root.leftChild = null;
			this.root.rightChild = null;
			return true;
		} else {
			return insertBST(this.root, data);
		}

	}

	private boolean insertBST(BSTNode<Key> node, Key data) {
		// 左分支,右分支的判断
		int lOrR = node.data.compareTo(data);
		if (lOrR == 0) {
			return false;
		} else if (lOrR < 0) {
			// 右子树
			if (null == node.rightChild) {
				BSTNode<Key> rChild = new BSTNode<Key>(data);
				rChild.parent = node;
				node.rightChild = rChild;
				return true;
			} else {
				return insertBST(node.rightChild, data);
			}
		} else {
			// 左子树
			if (null == node.leftChild) {
				BSTNode<Key> lChild = new BSTNode<Key>(data);
				lChild.parent = node;
				node.leftChild = lChild;
				return true;
			} else {
				return insertBST(node.leftChild, data);
			}
		}
	}

	/**
	 * 在这棵树中,指定一个节点,然后开始非递归方式中序遍历以这个节点为根节点的二叉树
	 *
	 * @param node
	 *            指定为根节点的节点
	 * @return
	 */
	public List<BSTNode<Key>> noRecurseInOrderTraverse(Key data) {
		if (null == this.root)
			return null;
		BSTNode<Key> beginRoot = null;
		if (null == data) {
			beginRoot = this.root;// 如果没有指定节点为根节点,那么就从这棵树的根节点开始
		} else {
			BSTNode<Key> sNode = searchBST(this.root, data);
			beginRoot = sNode;
		}
		List<BSTNode<Key>> stack = new ArrayList<BSTNode<Key>>();
		List<BSTNode<Key>> retStack = new ArrayList<BSTNode<Key>>();
		if (null != beginRoot) {
			stack.add(beginRoot);
		}

		while (!stack.isEmpty()) {
			while (beginRoot != null && null != beginRoot.leftChild) {
				beginRoot = beginRoot.leftChild;
				stack.add(beginRoot);
			}
			if (!stack.isEmpty()) {
				BSTNode<Key> tmpNode = stack.get(stack.size() - 1);
				stack.remove(stack.size() - 1);
				retStack.add(tmpNode);
				if (tmpNode != null && null != tmpNode.rightChild) {
					beginRoot = tmpNode.rightChild;
					stack.add(beginRoot);
				}
			}
		}
		return retStack;
	}

	/**
	 * 查找指定的节点,如果没有,则将他加入到这颗树上
	 *
	 * @param data
	 * @return 0:表示在原树上就有,1:表示添加进去的新节点,-1:表示查询中出错
	 */
	public boolean search(Key data) {
		if (null == data) {
			return false;
		}
		if (null == this.root) {
			return false;
		}
		return searchBST(this.root, data) == null ? false : true;
	}

	private BSTNode<Key> searchBST(BSTNode<Key> searhNode, Key data) {
		if (null == data) {
			return null;
		}
		if (null == searhNode) {
			return null;
		}
		int lOrR = searhNode.data.compareTo(data);
		if (lOrR > 0) {
			// 要往左子树去找
			return searchBST(searhNode.leftChild, data);
		} else if (lOrR == 0) {
			// 已经找到了,直接返回去
			return searhNode;
		} else {
			// 要往右子树去找
			return searchBST(searhNode.rightChild, data);
		}

	}

	/**
	 * 查找指定的节点,如果没有的话,插入节点
	 *
	 * @param data
	 * @return
	 */
	public boolean searchOrInsert(Key data) {
		return search(data) == false ? insert(data) : true;
	}

	/**
	 * 删除指定的节点
	 *
	 * @param data
	 * @return
	 */
	public boolean delete(Key data) {
		if (null == this.root || null == data) {
			return false;
		}
		BSTNode<Key> node = searchBST(this.root, data);
		if (null == node) {
			return false;
		}
		BSTNode<Key> parent = node.parent;
		BSTNode<Key> leftChild = node.leftChild;
		if (null == node.rightChild) {
			// 因为没有右孩子,因此,只要重新接上她的左孩子就可以了
			if (null == parent) {
				// 说明他是根节点
				if (null != leftChild) {
					node.leftChild.parent = null;
				} else {
					this.root = null;
				}
			} else {
				node.parent.leftChild = leftChild;
			}
		} else if (null == node.leftChild) {
			// 因为没有左孩子,只要重新接上她的右孩子就可以了
			if (parent == null) {
				// 说明他就是根节点
				if (null != node.rightChild) {
					node.rightChild.parent = null;
				} else {
					this.root = null;
				}
			} else {
				node.parent.rightChild = node.rightChild;
			}
		} else {
			// 既有左子树,又有右子树
			// 中序遍历此节点的左子树,找到此节点的前驱
			// 此前驱的特点是,要么是叶子节点,要么是只有左节点
			System.out.println(node.rightChild==null);
			List<BSTNode<Key>> stack=noRecurseInOrderTraverse(node.leftChild.data);
			BSTNode<Key> preNode=stack.get(stack.size()-1);
			BSTNode<Key> rightNode=node.rightChild;
			node.data=preNode.data;
			if(preNode.leftChild!=null){
				node.leftChild=preNode.leftChild;
			}
			if(preNode.parent!=null){
				if(preNode.parent.leftChild.data.compareTo(preNode.data)==0){
					preNode.parent.leftChild=null;
				}else{
					preNode.parent.rightChild=null;//这里有问题
				}
			}
			node.rightChild=rightNode;
			System.out.println(node.rightChild==null);
		}

		return true;
	}

	public static void main(String[] args) {
		BST<Integer> bst = new BST<Integer>();
		bst.insert(new Integer(6));
		bst.insert(new Integer(5));
		bst.insert(new Integer(9));
		bst.insert(new Integer(2));
		bst.insert(new Integer(8));
		bst.insert(new Integer(10));
		bst.insert(new Integer(1));
		bst.insert(new Integer(4));
		bst.insert(new Integer(3));
		List<BSTNode<Integer>> stack = bst.noRecurseInOrderTraverse(null);
		for (Iterator<BSTNode<Integer>> iterator = stack.iterator(); iterator
				.hasNext();) {
			BSTNode<Integer> bstNode = (BSTNode<Integer>) iterator.next();
			System.out.print(bstNode.data + "---");
		}

		bst.delete(new Integer(5));

		System.out.println("删除之后");
		stack = bst.noRecurseInOrderTraverse(null);
		for (Iterator<BSTNode<Integer>> iterator = stack.iterator(); iterator
				.hasNext();) {
			BSTNode<Integer> bstNode = (BSTNode<Integer>) iterator.next();
			System.out.print(bstNode.data + "---");
		}
	}

}

通过上面的学习,我们可以看到,二叉搜索树受输入顺序影响很大,有可能就是形成了一个线性表,效率非常低。咋整呢?收看下一节,欢迎加qq:359311095讨论

时间: 2024-11-08 17:28:57

Redis研究-3.4 为何使用Redis跳跃表的相关文章

redis源码分析4---结构体---跳跃表

redis源码分析4---结构体---跳跃表 跳跃表是一种有序的数据结构,他通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的: 跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点.性能上和平衡树媲美,因为事先简单,常用来代替平衡树. 在redis中,只在两个地方使用了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构. 1 跳跃表节点 1.1 层 层的数量越多,访问其他节点的速度越快: 1.2 前进指针 遍历举例

redis 5.0.7 源码阅读——跳跃表skiplist

redis中并没有专门给跳跃表两个文件.在5.0.7的版本中,结构体的声明与定义.接口的声明在server.h中,接口的定义在t_zset.c中,所有开头为zsl的函数. 一.数据结构 单个节点: typedef struct zskiplistNode { //key,唯一 sds ele; //分值,可重复 double score; //后退指针 struct zskiplistNode *backward; //层 struct zskiplistLevel { //前进指针 struc

学习笔记-Redis设计与实现-跳跃表

跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的. 跳跃表支持平均O(logN).最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点. Redis使用跳跃表作为有序结合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员(member)是比较长的字符串时,Redis就会使用跳跃表来作为有序集合见的底层实现. 5.1 跳跃表的实现 zskiplist结构,包含以下属性: header:

redis - 跳跃表详细介绍

redis吸引很多人使用的一个重要的原因,就是它对众多数据类型的支持.包括string,hash,set,zset,list等五种数据对象. 其中zset用来保证数据的有序存储,实现中,redis使用跳跃表和压缩列表,作为zset的底层实现.当元素数量比较多,或者元素成员是比较长的字符串时,底层实现采用跳跃表. 跳跃表是什么? 一种有序的数据结构,通过在节点中维持多个指向其它节点的指针,达到快速访问的目的. 跳跃表的好处是什么? 1. 跟平衡树相比,实现简单: 2. 平均复杂度为O(logN),

Redis研究(八)—有序集合类型 1

一.介绍 sorted set为集合中的每个元素都关联了一个分数,所以可以获得分数最高或者最低的前N个元素,获得指定分数范围内的元素等与分数有关的操作.虽然集合中每个元素都是不同的,但是他们的分数却可以相同. 有序集合类型与列表类型有些相似 (1)二者都是有序的 (2)二者都可以获得某一个范围的元素. 但是二者有很大的区别 (1)列表通过链表实现,获取两端数据速度快,访问中间数据速度慢. (2)有序集合类型使用散列表和跳跃表实现,获取中间数据也很快(时间复杂度O(log(N))). (3)列表不

redis的跳跃表

跳跃表是一种插入.查询.删除的平均时间复杂度为O(nlogn)的数据结构,在最差情况下是O(n),当然这几乎很难出现. 和红黑树相比较 最差时间复杂度要差很多,红黑树是O(nlogn),而跳跃表是O(n) 平均时间复杂度是一样的 实现要简单很多 https://en.wikipedia.org/wiki/Skip_list 维基的跳跃表例子 跳跃表的结构如上图 跳跃表的实现还是一个链表,是一个有序的链表,在遍历的时候基于比较,但普通链表只能遍历,跳跃表加入了一个层的概念,层数越高的元素越少,每次

Redis中的跳跃表

Redis中的跳跃表 跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的 跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点 跳跃表在Redis用于实现有序集合键和在集群节点中用作内部数据结构 跳跃表的实现 // 跳跃表节点 typedef struct zskiplistNode { // 层 struct zskiplistLevel { // 前进指针 struct zskiplistNode *f

redis 系列7 数据结构之跳跃表

一.概述 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的.在大部分情况下,跳跃表的效率可以和平衡树(关系型数据库的索引就是平衡树结构)相媲美,并且因为跳跃表的实现比平衡树要来得更为简单,所以有不少程序使用跳跃表来代替平衡树. Redis使用跳跃表作为"有序集合键"的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现.

redis跳跃表

最近在阅读redis设计与实现,关于redis数据结构zset的一种底层实现跳跃表一直没有太理解,所以在搜了一下资料,终于搞懂了它的设计思路,记录一下. 参考链接:https://mp.weixin.qq.com/s?src=11&timestamp=1553915878&ver=1515&signature=SuSdA-Ka7Bs7CzSnNHgHFR7DkFFibGdRUui-FkuSRn2OJOkn6uvGznFMheSfoxaSHYlcgfGnBQ9imQdTAg5hiaq