经典的搜索二叉树,是没有重复值的,任何节点为头的数,左孩子都比自己小,右孩子都比自己大
允许重复值的改进的搜索二叉树,可以在每个节点上增加一个统计词频的数据项。表示出现了几次;但是不可相等的放到左右孩子上,搜索二叉树变平衡时,会影响后续的旋转
1、搜索二叉树一定要说明以什么标准来排序
2、经典的搜索二叉树,树上没有重复的用来排序的key值
3、如果有重复节点的需求,可以在一个节点内部增加数据项
搜索二叉树查询key(查询某个key存在还是不存在),当前节点比自己小,到右子树上去找,当前节点比自己大,到其左孩子上去找,越界,说明不存在
1、如果当前节点的value==key,返回true
2、如果当前节点的value 3、如果当前节点的value>key,当前节点向右移动 4、如果当前节点变成null,返回false 和查询过程一样,但当前节点滑到空的时候,就插入在这里 1、先找到key所在的节点 2、如果该节点没有左孩子、没有右孩子,直接删除即可(好理解) 3、如果该节点有左孩子、没有右孩子,直接用左孩子顶替该节点(好理解) 4、如果该节点没有左孩子、有右孩子,直接用右孩子顶替该节点(好理解) 5、如果该节点有左孩子、有右孩子,用该节点后继节点顶替该节点(需要旋转调整,没法有左右孩子去替换,原因是左右孩子也有左右孩子) 一个节点的后继节点,就是该节点右孩子的最左的那个节点。 比如我要删除5节点,那么5节点的后继节点就是其右子树的最左的孩子,也就是6。把6替换掉5,6的右孩子给它父亲作为左孩子,得到 1)基础的搜索二叉树,添加、删除时候不照顾平衡性 2)数据状况很差时,性能就很差 给搜索二叉树引入两个动作:左旋、右旋 输入状况,决定性能。比如输入状况构建出来的树,严重不平衡。极端情况是只有一条通往底部的路径,高度为n; 平衡二叉树的定义,任何子树,左子树的高度和右子树的高度差,不大于1。所以对于n个节点,平衡二叉树的高度,就是logN 平衡搜索二叉树,就是在插入和删除的过程中,动态的保持平衡性。保持平衡的代价保持在logN。平衡搜索二叉树的实现由很多,红黑树只是其中一种 左旋和右旋针对头结点而言的,即对哪个头结点进行左旋还是右旋;顾名思义如果对头结点为a的子树右旋,那么a倒到右边,a的左孩子顶上来,到a原来的位置上去。a原来左孩子的右孩子,现在当做a的左孩子,如下图 a右旋,得到: 同理,a左旋,得到: 带左旋和右旋的搜索二叉树,在经典搜索二叉树上做的扩展,继承经典搜索二叉树。 在Java中,就是TreeMap,有序表和Hash表(HashMap)的操作类似,但是有序表中自动把我们的key排好了顺序,我们也可以传入比较器,自定义对比和排序的规则;我们可以通过TreeMap直接得到最大节点和最小节点,也可以获取大于某个值的第一个Key是什么等等 为什么TreeMap可以做到有序,原因是TreeMap的底层是一个平衡搜索二叉树 1、HashMap的所有操作是O(1)的,TreeMap的所有操作是O(logN) 2、使用有序表的Key必须是可以比较的,没法自然比较的需要传入自定义比较器 在有序表中,有序表是一种规范,类似于接口名。规范为key要按序组织,所有操作要是O(logN)等。各种树结构可以实现有序表的功能。其中红黑树只是有序表的一个实现 AVL树,SB树,红黑树,都是有序表的一种实现。都是平衡搜索二叉树,这三种树在功能和时间复杂度上几乎无差别,在实现细节上也就是在常数时间上,会有差别。三种树调整检查哪些节点的平衡性相同,下文进行说明。每种树针对每个节点的平衡性调整不同,但是都是使用左旋和右旋两个动作 1)都是搜索二叉树 2)插入、删除、查询(一切查询)搜索二叉树怎么做,这些结构都这么做 3)使用调整的基本动作都只有左旋、右旋 4)插入、删除时,从最底层被影响到的节点开始,对往上路径的节点做平衡性检查 5)因为只对一条向上路径的每个节点做O(1)的检查和调整,所以可以做到O(logN) 1)平衡性的约束不同 AVL树最严格、SB树稍宽松、红黑树最宽松 2)插入、删除和搜索二叉树一样,但是额外,做各自的平衡性调整。各自的平衡性调整所使用的动作都是左旋或者右旋 是这三个平衡搜索二叉树中,平衡性最严格的,左树高度和右树高度的绝对值,严格小于2 AVL树在插入节点的时候,只会向上检查节点的平衡性有没有被破坏。删除节点也一样,只会检查删除的那个节点向上的那条链上的节点有没被破坏。删除的时候,如果被删除的节点没有左右孩子那么直接检查,如果有右孩子,是去检查后继节点原来所在的位置的向上节点的平衡性 实质上三种树删除和插入节点,检查哪些节点需要调整平衡都是这样的查找规则,对于删除来说,只有左树和只有右树没影响,如果左右树都存在,是去检查后继节点原来所在的位置向上的平衡性。只是具体到某个节点平衡性的处理上,三种树不一样 1、该节点左树高度和右树高度差的绝对值|L-R|,小于2。不违规,无须调整 2、|L-R|大于1,说明要不然左树高度大了,要不然右树高度大了。而且之前的每一步都进行了调整,所以高度差不会超过2。高度不平衡对应四种情况,被称为RR形违规,RL形违规,LL形违规,LR形违规。首字母表示左树变高了还是右树变高了,比如RR和RL都表示经过插入或者删除,右树的高度变大了 RR表示,右子节点的右树过长,RL表示右子节点的左树过长。同理LL表示左子节点的左子树高度过长导致,LR表示左子节点的右树高度过长导致的不平衡 右旋后得到: 同理RR形违规,对该节点进行一次左旋即可 LR形,让底部的孙子节点,来到顶部来。 下面例子,想办法让x的孙子节点t节点来到顶部,先对y节点进行一次左旋,再对x节点做一次右旋 先对y节点进行一次左旋 再对x节点做一次右旋 原x的孙子节点t此时,调整到的顶部 LL形和RR形旋转一次O(1),LR和RL形旋转两次,也是O(1)。那么即使删除或者添加的节点影响的整个向上的链路,整体复杂度也是O(logN) AVL树继承自带左右旋的平衡搜索二叉树,在此基础上做的扩展,code如下: 对于平衡性而言,任何叔叔节点的子节点格式,不少于该节点的任何一个侄子节点子节点的个数 即a是一个叔叔节点,一定不少于f和g的子节点个数 对于这种约束,也可保证任何节点的左右节点的个数不会有很大悬殊,即使高度不满足严格相减的绝对值小于2,也无伤大雅。整体仍然是O(logN) 2007年,读高中的时候,承志峰研究出来的;常被用作比赛时,AVL树反而在ACM比赛中使用的相对少点 也分为LL,LR,RR,RL四种类型。 当删除和插入某个节点,影响的节点的左孩子,不如其右孩子的左孩子节点个数多,RL形 当删除和插入某个节点,影响的节点的左孩子,不如其右孩子的右孩子节点个数多,RR形 当删除和插入某个节点,影响的节点的右孩子,不如其左孩子的左孩子节点个数多,LL形 当删除和插入某个节点,影响的节点的右孩子,不如其左孩子的右孩子节点个数多,LR形 1、 对于LL形违规,对头结点x进行一次右旋,结束后,递归调用所以节点孩子个数发生变化的节点。右旋的时候,头结点x,和原x左孩子现在变成了头节点的b。这两个节点孩子个数发生了变化,要递归调用这两个节点。原因是原来不违规的节点,调整了位置后,pk的对象变了,要基于现在的结构再检查平衡性。这里虽然套了两个递归,整体仍然是O(1),证明略 2、 RR形违规,和LL形违规类似处理; SB树平衡性相对于AVL树要模糊,所以平衡性调整比AVL的调整粒度要粗,也意味着SB树比AVL树速度要快,比赛常用。而且SB树可以设计成删除节点的时候,不进行平衡性调整,只有在添加节点的时候再进行平衡性调整,添加节点的时候有可能积压了很多的不平衡,但是我们有递归行为,仍然可以调整回平衡的状态;可能为棒状,有可能该次递归行为时间复杂度比较高,但是均摊下来仍然O(logN)水平;该结构比较重要 如果是违规的加点的左树高度超了,且左孩子的左右子节点个数相同,必须做LL形的调整,反之RR形同理 1、 节点有红有黑 2、头节点和叶子节点是黑 3、红节点的子,一定要是黑节点 4、从任何节点到他的子节点,所有的路径中黑节点都是一样多的 从第三点和第四点可以推出,任何长链(黑红交替)和任何段链(都为黑)保证黑一样多,那么长链的长度一定不会比短链长度的二倍还要大。实质上上面条件的目的也是保证最长的链不要比最短的链2倍还多,一定程度上保证了平衡性 红黑树本质上也是搜索二叉树,搜索二叉树怎么增加和删除节点,红黑树相同;同样的加完节点,删除完节点,之后从受影响的节点往上都进行平衡性检查,几种有序表的实现只有检查平衡性的评估指标不同 红黑树平衡性检查,插入的情况下,违规的情况有5种,删除的情况下违规的情况有8种。红黑树自身的这些规定,会引出来13种违规情况下的节点调整。发明红黑树的人把13种情况都研究明白了,现在让我们学,哈哈 面试场上看到问红黑树相关的内容,可以回答本质上也是搜索二叉树,不平衡性较多有13种,AVL和SB树都只有四种LL,LR,RR,RL。比红黑树简单。如果面试官一定要问哪5种,哪8种,那么这个面试官有问题,这种不平衡性可以查文档来获取。这种就属于面试官就是不想让你过,纠结哪8种,哪5种,纯粹是磨工夫 红黑树这么麻烦,好处在哪,为什么这么著名,原因在于红黑树的扰动小。AVL由于平衡性特别清晰,增加和删除节点特别灵敏,扰动大。SB和红黑树的平衡性相对模糊,而且SB在删除的节点的时候,可以不进行平衡性调整,扰动小 有些场景,就是需要扰动小的调整,比如硬盘io的时候,每个树的节点,就是一块硬盘区域。硬盘删除,更新,插入等,如果时间复杂度评估指标相等,还是要选择扰动小的结构。内存中无所谓,扰动大小都可 如果每次插入和删除,行为代价都非常的高,红黑树都不考虑用,而是选择用底层硬盘结构的树,不如B树,和B+树,234树。这些树是多叉树。B树和B+树牺牲了一定的查询效率,虽然也是O(logN),常数项很大,但是没有AVL,SB,和红黑树的效率高。比如数据库组织的一些树,就是考虑到少读写硬盘,就是用的B+树 红黑树,在(AVL,SB) 和 硬盘的树(B,B+)树之间达到平衡。各有取舍 Redis为什么选择跳表的结构,而不是AVL和SB树呢,实质上可以选择,但是考虑到redis可能需要对有序表进行序列化的要求,SkipList就是多层的线性结构,比较好序列化。AVL和SB是个结构化的东西,不好序列化;一种技术的选型,需要根据自己的生存状态去选择的 三种树的平衡性保证策略不同,各自实现各自的平衡性,但是三个树都只有左旋和右旋两种调整策略 最烧脑的结构 跳表也可实现有序表的功能,但是跳表不是搜索二叉树,实现机制跟二叉树也没关系 跳表实现有序表,比较好实现,思想也相对更先进O(logN) 跳表节点有多条往外指的指针,Node里面有一个List变量,类似于多叉树;跳表节点上也有可以比较的key,定义最小的key是null 每个节点有多少个向下指针,随机指定,高层一定和所有节点最多的向下指针的条目数保持一致 跳表的最低层一定含有所有记录节点,概率上第二层有N/2个节点,概率上第三层会有N/4个节点… 高层向底层寻找,实际上跳跃了很多的节点。这种机制跟输入的数据状况没关系,每一个节点随机层数,最后查找O(logN) 给定一些数组,长度不一。每个数组里面是有序的,可理解为二维数组,每一行有序,现在需要找一个a到b的区间,要求每一行都至少有一个数命中在该区间中。求满足这个这样条件的区间的最窄区间,如果存在多个最窄区间,返回区间位置起始最小的那个 解题流程:准备一个有序表,第一步,把每个数组中第0个树加入有序表,得到一个区间就是有序表中的最小值和最大值构成的区间,该区间已经可以包含每个数组中至少一个数在该区间内,但不一定是最小区间;第二步,找到有序表中最小的数在哪个数组中,弹出最小值,把该数组的下一个树加入有序表,看是否更新了最小区间,更小才更新,同样大不更新。重复。。。 最后全局最小区间,就是我们要找的区间; 解题思路:实质上解题流程,是在尝试每一个数字开头的情况下,哪个区间是最小的。以每一个数字去尝试,实质上是一种贪心思想,不去考虑数字不以数组中出现的区间,该区间一定不是最小的 整个流程,只需要运用有序表的基本功能,原始的有序表已经能够满足需求,无需改写有序表,用系统实现的即可; 给定一个数组arr,和两个整数a和b(a<=b) 例如a等于10,b等于30,在arr上,求0到i范围有多少子数组在10和30范围上,假设0带i和为100,反过来就是求0到i-1范围上有多少前缀和有多少落在70到90范围上; 所以,我们求0到p,p在0到i中间,前缀和在70到90上,我们可以得到p+1到i的累加和在10到30范围上。所以这题就是求0到p的前缀和有多少在我们根据a到b和0到i的累加和推出的新的范围上[sum-a, sum-b],就等于我们要求的个数 我们把0到0的和,0到1的和,0到i的和。。。加入到我们的结构中去,求0到i结尾的子数组有多少个达标,就是求该结构上,有多少个前缀和落在了[sum-a, sum-b]的范围上;这个结构可以加入一个数字,且允许有重复值,给定一个范围[sum-a,sum-b]可以通过该结构返回加入的节点有多少个在这个范围上。例如加入到结构中的数字,有1,1,1,4,5,给定范围[1,5],返回6 要实现这样的功能,系统实现的有序表,无法实现,一方面原始有序表无法加入重复数字,第二方面没有这样的方法返回个数。这样的方法,可以实现为,小于a的数有多少个,小于b的数有多少个,那么最终我们需要的个数就是a-b个 在SB树上改造: 有序表结构本身比较重要,我们也经常使用系统实现的有序表,但是涉及到手动改有序表的实现,本身就已经比较难,而且面试出现的概率不是很高 Java的TreeMap底层是红黑树,但是SB树完全可以替换,没任何差别
graph TD
2-->1
2-->5
5-->3
5-->10
10-->8
10-->13
8-->6
6-->7
graph TD
2-->1
2-->6
6-->3
6-->10
10-->8
10-->13
8-->7
package class05;
/**
* Not implemented by zuochengyun
*
* Abstract binary search tree implementation. Its basically fully implemented
* binary search tree, just template method is provided for creating Node (other
* trees can have slightly different nodes with more info). This way some code
* from standart binary search tree can be reused for other kinds of binary
* trees.
*
* @author Ignas Lelys
* @created Jun 29, 2011
*
*/
public class AbstractBinarySearchTree {
/** Root node where whole tree starts. */
public Node root;
/** Tree size. */
protected int size;
/**
* Because this is abstract class and various trees have different
* additional information on different nodes subclasses uses this abstract
* method to create nodes (maybe of class {@link Node} or maybe some
* different node sub class).
*
* @param value
* Value that node will have.
* @param parent
* Node's parent.
* @param left
* Node's left child.
* @param right
* Node's right child.
* @return Created node instance.
*/
protected Node createNode(int value, Node parent, Node left, Node right) {
return new Node(value, parent, left, right);
}
/**
* Finds a node with concrete value. If it is not found then null is
* returned.
* 查找节点
*
* @param element
* Element value.
* @return Node with value provided, or null if not found.
*/
public Node search(int element) {
Node node = root;
while (node != null && node.value != null && node.value != element) {
// 小于当前节点,找左孩子对比
if (element < node.value) {
node = node.left;
} else {
// 大于当前节点,找右孩子对比
node = node.right;
}
}
return node;
}
/**
* Insert new element to tree.
* 插入一个节点
*
* @param element
* Element to insert.
*/
public Node insert(int element) {
// 首先如果这个树是空的,把该节点当成头节点
if (root == null) {
root = createNode(element, null, null, null);
size++;
return root;
}
// 需要插入在该节点下面
Node insertParentNode = null;
Node searchTempNode = root;
while (searchTempNode != null && searchTempNode.value != null) {
insertParentNode = searchTempNode;
if (element < searchTempNode.value) {
searchTempNode = searchTempNode.left;
} else {
searchTempNode = searchTempNode.right;
}
}
Node newNode = createNode(element, insertParentNode, null, null);
if (insertParentNode.value > newNode.value) {
insertParentNode.left = newNode;
} else {
insertParentNode.right = newNode;
}
size++;
return newNode;
}
/**
* Removes element if node with such value exists.
* 删除节点,每个节点由于加入向上的指针,那么旋转的时候会方便些
*
* @param element
* Element value to remove.
*
* @return New node that is in place of deleted node. Or null if element for
* delete was not found.
*/
public Node delete(int element) {
Node deleteNode = search(element);
if (deleteNode != null) {
return delete(deleteNode);
} else {
return null;
}
}
/**
* Delete logic when node is already found.
*
* @param deleteNode
* Node that needs to be deleted.
*
* @return New node that is in place of deleted node. Or null if element for
* delete was not found.
* 注意,删除方法返回的是删除后接管删除节点的位置的节点,返回
*/
protected Node delete(Node deleteNode) {
if (deleteNode != null) {
Node nodeToReturn = null;
if (deleteNode != null) {
if (deleteNode.left == null) {
// 左孩子为空,右孩子直接替换该节点,达到删除的效果
// transplant(a,b) b去替换a的环境,a断连掉,把b返回
nodeToReturn = transplant(deleteNode, deleteNode.right);
} else if (deleteNode.right == null) {
// 右孩子为空,左孩子直接替换,达到删除的目的
nodeToReturn = transplant(deleteNode, deleteNode.left);
} else {
// 否则,要删除的节点既有左孩子,又有右孩子,找右子树的最左的孩子
Node successorNode = getMinimum(deleteNode.right);
// 要删除的节点的右孩子,有左孩子。最左孩子的右孩子要它父亲来接管
if (successorNode.parent != deleteNode) {
transplant(successorNode, successorNode.right);
successorNode.right = deleteNode.right;
successorNode.right.parent = successorNode;
}
// 如果要删除的节点的右孩子,没有左孩子。直接用要删除的节点的右孩子进行替换即可
transplant(deleteNode, successorNode);
successorNode.left = deleteNode.left;
successorNode.left.parent = successorNode;
nodeToReturn = successorNode;
}
size--;
}
return nodeToReturn;
}
return null;
}
/**
* Put one node from tree (newNode) to the place of another (nodeToReplace).
*
* @param nodeToReplace
* Node which is replaced by newNode and removed from tree.
* @param newNode
* New node.
*
* @return New replaced node.
*/
private Node transplant(Node nodeToReplace, Node newNode) {
if (nodeToReplace.parent == null) {
this.root = newNode;
} else if (nodeToReplace == nodeToReplace.parent.left) {
nodeToReplace.parent.left = newNode;
} else {
nodeToReplace.parent.right = newNode;
}
if (newNode != null) {
newNode.parent = nodeToReplace.parent;
}
return newNode;
}
/**
* @param element
* @return true if tree contains element.
*/
public boolean contains(int element) {
return search(element) != null;
}
/**
* @return Minimum element in tree.
*/
public int getMinimum() {
return getMinimum(root).value;
}
/**
* @return Maximum element in tree.
*/
public int getMaximum() {
return getMaximum(root).value;
}
/**
* Get next element element who is bigger than provided element.
*
* @param element
* Element for whom descendand element is searched
* @return Successor value.
*/
// TODO Predecessor
public int getSuccessor(int element) {
return getSuccessor(search(element)).value;
}
/**
* @return Number of elements in the tree.
*/
public int getSize() {
return size;
}
/**
* Tree traversal with printing element values. In order method.
*/
public void printTreeInOrder() {
printTreeInOrder(root);
}
/**
* Tree traversal with printing element values. Pre order method.
*/
public void printTreePreOrder() {
printTreePreOrder(root);
}
/**
* Tree traversal with printing element values. Post order method.
*/
public void printTreePostOrder() {
printTreePostOrder(root);
}
/*-------------------PRIVATE HELPER METHODS-------------------*/
private void printTreeInOrder(Node entry) {
if (entry != null) {
printTreeInOrder(entry.left);
if (entry.value != null) {
System.out.println(entry.value);
}
printTreeInOrder(entry.right);
}
}
private void printTreePreOrder(Node entry) {
if (entry != null) {
if (entry.value != null) {
System.out.println(entry.value);
}
printTreeInOrder(entry.left);
printTreeInOrder(entry.right);
}
}
private void printTreePostOrder(Node entry) {
if (entry != null) {
printTreeInOrder(entry.left);
printTreeInOrder(entry.right);
if (entry.value != null) {
System.out.println(entry.value);
}
}
}
protected Node getMinimum(Node node) {
while (node.left != null) {
node = node.left;
}
return node;
}
protected Node getMaximum(Node node) {
while (node.right != null) {
node = node.right;
}
return node;
}
protected Node getSuccessor(Node node) {
// if there is right branch, then successor is leftmost node of that
// subtree
if (node.right != null) {
return getMinimum(node.right);
} else { // otherwise it is a lowest ancestor whose left child is also
// ancestor of node
Node currentNode = node;
Node parentNode = node.parent;
while (parentNode != null && currentNode == parentNode.right) {
// go up until we find parent that currentNode is not in right
// subtree.
currentNode = parentNode;
parentNode = parentNode.parent;
}
return parentNode;
}
}
// -------------------------------- TREE PRINTING
// ------------------------------------
public void printTree() {
printSubtree(root);
}
public void printSubtree(Node node) {
if (node.right != null) {
printTree(node.right, true, "");
}
printNodeValue(node);
if (node.left != null) {
printTree(node.left, false, "");
}
}
private void printNodeValue(Node node) {
if (node.value == null) {
System.out.print("
1.3 传统搜索二叉树存在的问题
1.3.1 平衡搜索二叉树
1.3.2 左旋和右旋
graph TD
a-->b
a-->c
c-->d
c-->e
b-->f
b-->g
graph TD
b-->f
b-->a
a-->g
a-->c
c-->d
c-->e
graph TD
c-->a
c-->e
a-->b
a-->d
b-->f
b-->g
package class05;
/**
* Not implemented by zuochengyun
*
* Abstract class for self balancing binary search trees. Contains some methods
* that is used for self balancing trees.
*
* @author Ignas Lelys
* @created Jul 24, 2011
*
*/
public abstract class AbstractSelfBalancingBinarySearchTree
extends AbstractBinarySearchTree {
/**
* Rotate to the left.
*
* @param node Node on which to rotate.
* @return Node that is in place of provided node after rotation.
*/
protected Node rotateLeft(Node node) {
Node temp = node.right;
temp.parent = node.parent;
node.right = temp.left;
if (node.right != null) {
node.right.parent = node;
}
temp.left = node;
node.parent = temp;
// temp took over node's place so now its parent should point to temp
if (temp.parent != null) {
if (node == temp.parent.left) {
temp.parent.left = temp;
} else {
temp.parent.right = temp;
}
} else {
root = temp;
}
return temp;
}
/**
* Rotate to the right.
*
* @param node Node on which to rotate.
* @return Node that is in place of provided node after rotation.
*/
protected Node rotateRight(Node node) {
Node temp = node.left;
temp.parent = node.parent;
node.left = temp.right;
if (node.left != null) {
node.left.parent = node;
}
temp.right = node;
node.parent = temp;
// temp took over node's place so now its parent should point to temp
if (temp.parent != null) {
if (node == temp.parent.left) {
temp.parent.left = temp;
} else {
temp.parent.right = temp;
}
} else {
root = temp;
}
return temp;
}
}
1.4 有序表
1.5 有序表的实现(AVL树,SB树,红黑树)
1.5.1 AVL树
1.5.1.1 AVL树针对某个节点的平衡性处理
graph TD
x-->y
x-->p
y-->z
y-->t
z-->k
graph TD
y-->z
y-->x
z-->k
x-->t
x-->p
graph TD
x-->y
x-->p
y-->z
y-->t
t-->k
graph TD
x-->t
x-->p
y-->z
t-->y
t-->k
graph TD
t-->y
t-->x
x-->k
x-->p
y-->z
package class05;
/**
* Not implemented by zuochengyun
*
* AVL tree implementation.
*
* In computer science, an AVL tree is a self-balancing binary search tree, and
* it was the first such data structure to be invented.[1] In an AVL tree, the
* heights of the two child subtrees of any node differ by at most one. Lookup,
* insertion, and deletion all take O(log n) time in both the average and worst
* cases, where n is the number of nodes in the tree prior to the operation.
* Insertions and deletions may require the tree to be rebalanced by one or more
* tree rotations.
*
* @author Ignas Lelys
* @created Jun 28, 2011
*
*/
public class AVLTree extends AbstractSelfBalancingBinarySearchTree {
/**
* @see trees.AbstractBinarySearchTree#insert(int)
*
* AVL tree insert method also balances tree if needed. Additional height
* parameter on node is used to track if one subtree is higher than other
* by more than one, if so AVL tree rotations is performed to regain
* balance of the tree.
*/
@Override
public Node insert(int element) {
Node newNode = super.insert(element);
// 对有影响的节点,顺着parent指针向上做平衡性检查调整
rebalance((AVLNode) newNode);
return newNode;
}
/**
* @see trees.AbstractBinarySearchTree#delete(int)
*/
@Override
public Node delete(int element) {
// 先查出来需要删的值,存在的话进行删除
Node deleteNode = super.search(element);
if (deleteNode != null) {
// 先调用父类,也就是带左右旋的平衡搜索二叉树的删除,把删除的节点后哪个节点接管了被删除的位置,该节点返回
Node successorNode = super.delete(deleteNode);
// 接管的节点不为空,检查是哪一种替换的方式,左孩子接管or右孩子接管,or后继节点接管,or既没有左孩子有没有右孩子直接删除
if (successorNode != null) {
// if replaced from getMinimum(deleteNode.right) then come back there and update
// heights
AVLNode minimum = successorNode.right != null ? (AVLNode) getMinimum(successorNode.right)
: (AVLNode) successorNode;
// 重新计算高度(重要)
recomputeHeight(minimum);
// 重新进行平衡(重要)
rebalance((AVLNode) minimum);
} else { // 并没有任何节点替代被删除节点的位置,被删除节点是孤零零被删除的
recomputeHeight((AVLNode) deleteNode.parent);
rebalance((AVLNode) deleteNode.parent);
}
return successorNode;
}
return null;
}
/**
* @see trees.AbstractBinarySearchTree#createNode(int,
* trees.AbstractBinarySearchTree.Node,
* trees.AbstractBinarySearchTree.Node,
* trees.AbstractBinarySearchTree.Node)
*/
@Override
protected Node createNode(int value, Node parent, Node left, Node right) {
return new AVLNode(value, parent, left, right);
}
/**
* Go up from inserted node, and update height and balance informations if
* needed. If some node balance reaches 2 or -2 that means that subtree must be
* rebalanced.
*
* @param node Inserted Node.
*/
private void rebalance(AVLNode node) {
while (node != null) {
// 先记录一下父环境
Node parent = node.parent;
// 左右树的高度拿出来
int leftHeight = (node.left == null) ? -1 : ((AVLNode) node.left).height;
int rightHeight = (node.right == null) ? -1 : ((AVLNode) node.right).height;
int nodeBalance = rightHeight - leftHeight;
// rebalance (-2 means left subtree outgrow, 2 means right subtree)
// 右树过高
if (nodeBalance == 2) {
// 判断是RR形还是RL形,确定进行一次还是两次旋转
if (node.right.right != null) {
// 旋转,旋转的过程中,一定维护好高度信息
node = (AVLNode) avlRotateLeft(node);
break;
} else {
node = (AVLNode) doubleRotateRightLeft(node);
break;
}
// 左树过高
} else if (nodeBalance == -2) {
// 同理,判断是LL还是LR
if (node.left.left != null) {
node = (AVLNode) avlRotateRight(node);
break;
} else {
node = (AVLNode) doubleRotateLeftRight(node);
break;
}
} else {
updateHeight(node);
}
// 把当前node向上变为父节点,向上窜,继续调整平衡
node = (AVLNode) parent;
}
}
/**
* Rotates to left side.
*/
private Node avlRotateLeft(Node node) {
Node temp = super.rotateLeft(node);
updateHeight((AVLNode) temp.left);
updateHeight((AVLNode) temp);
return temp;
}
/**
* Rotates to right side.
*/
private Node avlRotateRight(Node node) {
Node temp = super.rotateRight(node);
updateHeight((AVLNode) temp.right);
updateHeight((AVLNode) temp);
return temp;
}
/**
* Take right child and rotate it to the right side first and then rotate node
* to the left side.
*/
protected Node doubleRotateRightLeft(Node node) {
node.right = avlRotateRight(node.right);
return avlRotateLeft(node);
}
/**
* Take right child and rotate it to the right side first and then rotate node
* to the left side.
*/
protected Node doubleRotateLeftRight(Node node) {
node.left = avlRotateLeft(node.left);
return avlRotateRight(node);
}
/**
* Recomputes height information from the node and up for all of parents. It
* needs to be done after delete.
*/
private void recomputeHeight(AVLNode node) {
while (node != null) {
node.height = maxHeight((AVLNode) node.left, (AVLNode) node.right) + 1;
node = (AVLNode) node.parent;
}
}
/**
* Returns higher height of 2 nodes.
*/
private int maxHeight(AVLNode node1, AVLNode node2) {
if (node1 != null && node2 != null) {
return node1.height > node2.height ? node1.height : node2.height;
} else if (node1 == null) {
return node2 != null ? node2.height : -1;
} else if (node2 == null) {
return node1 != null ? node1.height : -1;
}
return -1;
}
/**
* Updates height and balance of the node.
*
* @param node Node for which height and balance must be updated.
*/
private static final void updateHeight(AVLNode node) {
int leftHeight = (node.left == null) ? -1 : ((AVLNode) node.left).height;
int rightHeight = (node.right == null) ? -1 : ((AVLNode) node.right).height;
node.height = 1 + Math.max(leftHeight, rightHeight);
}
/**
* Node of AVL tree has height and balance additional properties. If balance
* equals 2 (or -2) that node needs to be re balanced. (Height is height of the
* subtree starting with this node, and balance is difference between left and
* right nodes heights).
*
* @author Ignas Lelys
* @created Jun 30, 2011
* AVLNode继承自搜索二叉树的node,额外补充一个高度信息,用这个高度信息做平衡,一个节点的高度,是以该节点做头节点的树的高度
*/
protected static class AVLNode extends Node {
public int height;
public AVLNode(int value, Node parent, Node left, Node right) {
super(value, parent, left, right);
}
}
}
1.5.2 SB树
graph TD
c-->a
c-->e
a-->b
a-->d
e-->f
e-->g
1.5.2.1 SB树针对某个节点的平衡性处理
package class06;
public class Code01_SizeBalancedTreeMap {
// k继承Comparable,可比较的泛型
public static class SBTNode
package class06;
import java.util.ArrayList;
public class Code02_SizeBalancedTreeMap {
public static class SizeBalancedTreeMap
1.5.3 红黑树
1.5.3.1 红黑树针对某个节点的平衡性处理
Redis为什么选择跳表的结构?
1.6 跳表SkipList(也可实现有序表功能)
package class06;
import java.util.ArrayList;
public class Code03_SkipListMap {
// 跳表的节点定义,key是可以比较的
public static class SkipListNode
1.7 有序表例题实战
1.7.1 哪些情况下需要改写系统的有序表?
求arr中有多少个子数组,累加和在[a,b]这个范围上
返回达标的子数组数量
package class07;
import java.util.HashSet;
public class Code01_CountofRangeSum {
public static int countRangeSum1(int[] nums, int lower, int upper) {
int n = nums.length;
long[] sums = new long[n + 1];
for (int i = 0; i < n; ++i)
sums[i + 1] = sums[i] + nums[i];
return countWhileMergeSort(sums, 0, n + 1, lower, upper);
}
// leetcode不太好理解的版本
private static int countWhileMergeSort(long[] sums, int start, int end, int lower, int upper) {
if (end - start <= 1)
return 0;
int mid = (start + end) / 2;
int count = countWhileMergeSort(sums, start, mid, lower, upper)
+ countWhileMergeSort(sums, mid, end, lower, upper);
int j = mid, k = mid, t = mid;
long[] cache = new long[end - start];
for (int i = start, r = 0; i < mid; ++i, ++r) {
while (k < end && sums[k] - sums[i] < lower)
k++;
while (j < end && sums[j] - sums[i] <= upper)
j++;
while (t < end && sums[t] < sums[i])
cache[r++] = sums[t++];
cache[r] = sums[i];
count += j - k;
}
System.arraycopy(cache, 0, sums, start, t - start);
return count;
}
// 节点改造为,有自己的key,有左右孩子,有size,有词频数量all。all要和size同样维护起来
public static class SBTNode {
public long key;
public SBTNode l;
public SBTNode r;
public long size; // 不同key的size,sb树的平衡指标
public long all; // 总的size
public SBTNode(long k) {
key = k;
size = 1;
all = 1;
}
}
public static class SizeBalancedTreeSet {
private SBTNode root;
private HashSet