树 AVL AVL(Adelson-Velsky and Landis)树是一种自平衡二叉搜索树 对于树中的每一个节点,左、右子树的高度最多只能相差1
简介 搜索树上的大多数原始操作在O(h)时间内在高度为h树上运行,树的高度在一棵AVL树中,是O(lgN)
一棵AVL树是左右子树高度最大相差1的树(其中空树的高度定义为-1) 在高度为h的AVL树中,最少节点数S(h) = S(h-1) + S(h-2) + 1 ;h=0,S(h)=1;h=1,S(h)=2
AVL的平衡,旋转 AVL是特殊化的二叉树,想要保证AVL的平衡,我们可以用到一种称为平衡的做法。是否进行平衡 的问题由树之间的高度决定
AVL的基础定义与高度计算 这里先给出AVL的基本定义以及高度计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private static class AvlNode <AnyType>{ AnyType elemenet ; AvlNode<AnyType> left ; AvlNode<AnyType> right ; int height ; AvlNode(AnyType theElement){ this (theElement,null ,null ); } AvlNode(AnyType theElement , AvlNode<AnyType> lt , AvlNode<AnyType> rt){ elemenet = theElement ; left = lt ; right = rt ; } }
高度计算:
1 2 3 4 5 private int theHeight (AvlNode<AnyType> t) { return t == null ? -1 :t.height ; }
旋转 实质上,我们设这里有a这个节点,在插入后只有以下四种情况: 1.对a左儿子的左子树进行插入(左、左,同向) 2.对a左儿子的右子树进行插入(左、右,异向) 3.对a右儿子的右子树进行插入(右、右,同向) 4.对a右儿子的左子树进行插入(右、左,异向)
具体理论和原理比较长,见书P86-P91,总之:同向的情况用单旋转,异向的情况用双旋转 代码实现如下:
单旋转平衡(代码实现) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private Node rotateWithLeftChild (AvlNode<AnyType> k2) { AvlNode<AnyType> k1 = k2.left ; k2.left = k1.right ; k1.right = k2 ; k2.height = Math.max(height(k2.left), height(k2.right)) + 1 ; k1.height = Math.max(height(k1.left), k2.height) + 1 ; return k1 ; } private AvlNode<AnyType> rotateWithRightChild (AvlNode<AnyType> k2) { AvlNode<AnyType> k1 = k2.right ; k2.right = k1.left ; k1.left = k2 ; k2.height = Math.max(height(k2.left), height(k2.right)) + 1 ; k1.height = Math.max(height(k1.right),k2.height) + 1 ; return k1 ; }
双旋转平衡(代码实现) 1 2 3 4 5 6 7 8 9 10 11 12 private AvlNode<AnyType> doubleWithLeftChild (AvlNode<AnyType> k3) { k3.left = rotateWithRightChild(k3.left) ; return rotateWithLeftChild(k3) ; } private AvlNode<AnyType> doubleWithRightChild (AvlNode<AnyType> k3) { k3.left = rotateWithLeftChild(k3.left) ; return rotateWithRightChild(k3) ; }
AVL的插入和平衡 AVL的核心就在于在插入之后会进行一次对树的平衡 ,具体做法已经在上一部分说过了,这里直接给出对应代码
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public AvlNode<AnyType> insert (AnyType x , AvlNode<AnyType> t) { if (t == null ){ return new AvlNode <>(x,null ,null ) ; } int compareResult = x.compareTo(t.elemenet) ; if (compareResult < 0 ){ t.left = insert(x,t.left); }else if (compareResult > 0 ){ t.right = insert(x,t.right); }else { } return blance(t) ; } private static final int ALLOWED_IMBAKANCE = 1 ; private AvlNode<AnyType> balance (AvlNode<AnyType> t) { if (t == null ){ return t ; } if (height(t.left) - height(t.right) > ALLOWED_IMBAKANCE){ if (height(t.left.left) >= height(t.left.right)){ t = rotateWithLeftChild(t); }else { t = doubleWithLeftChild(t); } }else { if (height(t.right) - height(t.left) > ALLOWED_IMBAKANCE){ if (height(t.right.right) >= height(t.right.left)){ t = rotateWithRightChild(t) ; }else { t = doubleWithRightChild(t) ; } } } t.height = Math.max(height(t.left) , height(t.right)) + 1 ; return t ; }
AVL的删除 同前文,就是把插入改为删除,或者说“在二叉树原方法的基础上,最后加入平衡的步骤” 依旧是“寻值->操作/删除->再平衡->更新”
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private AvlNode<AnyType> remove (AnyType x , AvlNode<AnyType> t) { if (t == null ){ return ; } int compareResult = x.compareTo(t.elemenet); if (compareResult < 0 ){ t.left = remove(x,t.left) ; }else if (compareResult > 0 ){ t.right = remove(x,t.right) ; }else if (t.left != null && t.right != null ){ t.elemenet = findMin(t.right).elemenet ; t.right = remove(t.elemenet , t.right); }else { t = (t.left != null ) ? t.left : t.right ; } return balance(t) ; }
小结 1.在AVL树中,对于树中的每个节点,左右子树的高度最多可以相差一个,即高度平衡。平衡是AVL的核心,毕竟是自平衡树 2.要删除的节点可以标记为已删除,因此不需要重新平衡 3.在平均情况和最坏情况下,搜索、插入和删除都需要 O(log_2 N) 时间 4.在某些情况下,AVL 树可能会偏重,导致性能相对较差
高级树简介 下面写的树多归“高级数据结构”内容了,这里只作简介/应试要求
红黑树 红黑树是 AVL 树的替代品,红黑树也是一种自平衡二叉搜索树 红背树的操作最坏情况下需要𝑂(lg𝑁)时间,红黑树的高度最多为2lg(𝑁+1) 红背树中的每个节点都需要一个额外的位来存储节点的颜色 ,可以是红色或黑色
红黑树确保从根到叶的路径不超过任何其他路径的两倍 ,因此树是近似平衡的
基本性质与基本介绍 1.每个节点都是红色或黑色 2.根root是黑色的 3.如果一个节点是红色的,它的两个子节点都将被涂成黑色 4.对于每个节点,从节点到后代叶子的所有简单路径都包含相同数量的黑色节点
插入 插入的基本规则是: 1.只能定为红或黑,其中根和null节点必须是黑 2.红节点的子节点必须是黑节点(这条结合步骤理解) 3.相同数量的黑色节点
步骤: 1.按照二叉搜索树中使用的插入规则插入节点 2.将新插入的节点涂成红色 3.如果违反规则,则从上到下修复颜色
小结 •红黑树是自平衡二叉搜索树 •每个节点需要一个额外的位来存储节点的颜色 •节点只能被着色为红色或黑色 •红黑树的开销相对较低插入 •在实践中,与 AVL 树相比,旋转发生的频率相对较低 •使用红黑树可以避免侧超权子树(普通的AVL是很容易发生偏移的)
伸展树SplayTrees splay树保证从空树开始的任意M个连续树操作最多花费O(MlgN)时间 Splay树基于这样一个事实,即BST每次操作的O(N)最坏情况时间并不坏 ,只要它发生的频率相对较低 ,就是说不去把重点放在考虑处理最坏情况上,而是优化最坏情况发生的频率 splay树的基本思想是在一个节点被访问后,通过一系列的AVL树旋转将该节点推到根,在展开树中,如果一个节点很深,那么路径上有很多节点也比较深,通过展开我们可以使所有这些节点上的未来访问更快
基本性质 1.展开深度为 d 的节点 x 需要 𝜽(𝒅) 时间,即与访问 x 的时间成正比的时间
2.展开不仅将 x 移动到根,而且将访问路径上每个节点的深度大致减半
插入和删除节点需要 𝑶(𝐥𝐠𝑵) 摊销时间 在分析中,节点的电位可以是任意值,但是固定的
B树 渐近分析假设整个数据结构在计算机的主存中,当数据大小大于主存容量时,部分数据结构必须存储在磁盘上。如果发生这种情况,算法的渐进效率将变得毫无意义,因为磁盘访问比访问存储在内存中的数据慢得多。
B-tree 及其变体,如 B+-trees 和 B*-trees,是为了提高树操作的效率而开发的
基本性质 M 阶 B 树是 M 叉树 1.数据项存储在外部节点,每个内部节点最多存储 M-1 个密钥来指导搜索 2.密钥以非递减顺序存储 3.根要么是叶子,要么有 2 到 M 个子节点 4.所有内部节点(根节点除外)都有 M/2 和 M 个子节点 5.所有外部节点都处于相同深度,并且具有 L/2 和 L 之间的数据项
请注意,B 树有许多变体,每种变体的属性都略有不同。上述属性适用于 B-tree 的一种流行变体,称为 B+-tree
# 这是啥 整合了书和MIEC的内容
使用方式 同MIEC原笔记