《数据结构与算法分析Java语言描述》第二章4.3与4.4节读书笔记
4.3 二叉查找树
树的节点存储数据,一棵非空树要成为查找树,则对于任意节点x有:x的左子树都小于它,右子树节点都大于它。
二叉查找树的平均深度是O(logN),证明可网上查询。可以使用递归实现,不必担心栈溢出。或见4.3.5小节
如下是一个二叉查找树的实现,其中BinaryNode是一个内部类,用来实现节点。
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
public class BinarySearchTree<AnyType> {
private BinaryNode<AnyType> root; private Comparator cmp;
public BinarySearchTree() { this.root = null; }
public BinarySearchTree(Comparator<? super AnyType> c) { root = null; cmp = c; }
private int myCompare(AnyType lhs, AnyType rhs) { if (cmp != null) { return cmp.compare(lhs, rhs); } else { return ((Comparable) lhs).compareTo(rhs); } }
public void makeEmpty() { this.root = null; }
public boolean isEmpty() { return this.root == null; }
public boolean contains(AnyType x) { return contains(x, root); }
public AnyType findMax() { if (isEmpty()) { throw new BufferUnderflowException(); } else { return findMax(root).element; } }
public AnyType findMin() { if (isEmpty()) { throw new BufferUnderflowException(); } else { return findMin(root).element; } }
public void insert(AnyType x) { root = insert(x, root); }
public void remove(AnyType x) { root = remove(x, root); }
public void printTree() { }
public boolean contains(AnyType x, BinaryNode<AnyType> t) { if (t == null) { return false; } int compareResult = myCompare(x, t.element); if (compareResult < 0) { return contains(x, t.left); } else if (compareResult > 0) { return contains(x, t.right); } else { return true; } }
private BinaryNode<AnyType> findMin(BinaryNode<AnyType> t) { if (t == null) { return null; } else if (t.left == null) { return t; } return findMin(t.left); }
private BinaryNode<AnyType> findMax(BinaryNode<AnyType> t) { if (t != null) { while (t.right != null) { t = t.right; } } return t; }
private BinaryNode<AnyType> insert(AnyType x, BinaryNode<AnyType> t) { if (t == null) { return new BinaryNode<>(x, null, null); } int compareResult = myCompare(x, t.element); if (compareResult < 0) { t.left = insert(x, t.left); } else if (compareResult > 0) { t.right = insert(x, t.right); } else {
} return t; }
private BinaryNode<AnyType> remove(AnyType x, BinaryNode<AnyType> t) { if (t == null) { return t; } int compareResult = myCompare(x, t.element); 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.element = findMin(t.right).element; t.right = remove(t.element, t.right); } else { t = t.left != null ? t.left : t.right; } return t; }
private static class BinaryNode<AnyType> {
public AnyType element; public BinaryNode<AnyType> left; public BinaryNode<AnyType> right;
public BinaryNode(AnyType element, BinaryNode<AnyType> left, BinaryNode<AnyType> right) { this.element = element; this.left = left; this.right = right; }
public BinaryNode(AnyType element) { this.element = element; this.left = null; this.right = null; } } }
|
4.3.0 节点元素的比较实现
二叉查找树是有序的,因此必须使存储的元素是可以比较的,要使一个类是可以比的,有两种实现方式,1种是实现Comparable接口,另一种是令其传入Comparator比较器。这里选择了第二种,通过构造方法传入比较器。如果没有传入比较器,则内部将类强转为Comparable的,如果是不可比的,这时会抛出异常。(因为查找树是必须有序的,所以不可比也应该抛出异常)。
4.3.1 contains方法
该方法返回是否包含某个x元素,返回true或false,如果树t是空集,则返回false,如果x比t小,则继续查找左子树,否则继续查找右子树,直到查找到一个节点为null的点,说明没有。这使用递归实现。完整实例77-101行。
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
|
public boolean contains(AnyType x, BinaryNode<AnyType> t) { if (t == null) { return false; } int compareResult = myCompare(x, t.element); if (compareResult < 0) { return contains(x, t.left); } else if (compareResult > 0) { return contains(x, t.right); } else { return true; } }
|
4.3.2 findMin与findMax方法
此两个方法,分别返回树种的最小的节点与最大的节点。对于最小节点,从root节点开始,向左子树查找,只要有左儿子就向左进行,如果左儿子节点为null,说明该节点最小元素。对于最大节点,从root节点开始,向右子树查找,只要有右儿子就向右进行,其余同查找最小节点。完整实例103-122行。
findMIn的实现使用的尾递归,即在尾部只实现对自身的递归调用,而且无其他处理,尾递归一把会被编译器优化,但是建议写成findMax的循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private BinaryNode<AnyType> findMin(BinaryNode<AnyType> t) { if (t == null) { return null; } else if (t.left == null) { return t; } return findMin(t.left); }
private BinaryNode<AnyType> findMax(BinaryNode<AnyType> t) { if (t != null) { while (t.right != null) { t = t.right; } } return t; }
|
4.3.3 insert方法
该方法实现了在树中插入一个元素,然后返回该树的root节点(通过该节点可以获取到整棵树)。由于该树不是平衡的二叉查找树,该方法使用了简单的将将元素插入到叶子节点末端。如果插入的节点比root节点小,则继续与左子树的root节点比较,重复该步骤,直到某个叶子节点,与该叶子节点比较,如果小于,获取该叶子节点的左子节点,如果节点为null,则在null的地方插入;如果大于,获取该叶子节点的右子节点,如果节点为null,则在null的地方插入。完整实例124-151。
该方法是一个递归实现,注意递归较难理解,建议手动推导递归理解,理解每一层递归出栈的数据,后续怎么接收。以及最终结果返回的t的数据的具体含义。
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
|
private BinaryNode<AnyType> insert(AnyType x, BinaryNode<AnyType> t) { if (t == null) { return new BinaryNode<>(x, null, null); } int compareResult = myCompare(x, t.element); if (compareResult < 0) { t.left = insert(x, t.left); } else if (compareResult > 0) { t.right = insert(x, t.right); } else {
} return t; }
|
4.3.4 remove方法
如果要删除的节点是叶子节点x,则直接删除,如果x只有一个子节点,则直接将x的子节点移动到删除的节点x即可。如果x有两个子节点,则将右子树的最小节点移动上去,然后对于右子树,继续将移动上去的元素删除,直到节点为null,则代表子树要删除的元素为null,此时跳出递归。
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
|
private BinaryNode<AnyType> remove(AnyType x, BinaryNode<AnyType> t) { if (t == null) { return t; } int compareResult = myCompare(x, t.element); 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.element = findMin(t.right).element; t.right = remove(t.element, t.right); } else { t = t.left != null ? t.left : t.right; } return t; }
|
- 删除的节点4只有一个子节点:直接将子节点3移动到删除的节点即可。(将节点2的指针指向子节点)
- 删除的节点2两个子节点:将2的值改为右子树的最小的元素3,(注意,此时的原本的3的元素节点并没有删除),然后将右子树中的3的节点按照此逻辑递归删除。
- 如果删除的次数不多,则使用惰性删除,当元素被删除时,只是被标记删除,仍然留在树中。
4.3.5 平均查找深度
树的节点平均深度为O(logN)。一棵树的所有节点的深度的和为内部路径长。
(如上图4-23:(6->2) + ((6->2) + (2->1)) + ((6->2) + (2->4)) + ((6->2) + (2->4) + (4->3)) + (6->8) = 9路径长
深度计算:1 + (1 + 1) + (1+1) + (1+1+1) + 1 = 9路径长
令D(N)是具有N个节点的树T的内部路径长,则D(1) = 0。一棵N节点的树由一棵i节点的左子树和一棵(N-i-1)节点的右子树组成。则D(N) = D(i) + D(N-i-1),但是因为在原树中,左右子树的所有节点深度会被加一,因此需要加 N - 1。则递推公式:
D(N)=D(i)+D(N−i−1)+N−1
对于二叉查找树,所有子树大小都等可能出现,则有D(i)与D(N-i-1)的平均值是(N1)∑j=0N−1D(j)。于是有
D(N)=N2[j=0∑N−1D(j)]+N−1
该值的解,可以的到平均值D(N) = O(NlogN)
4.4 AVL树
AVL是一棵自平衡的二叉查找树。
平衡:每个节点的左子树与右子树高度最大差为1。即 ∣H最高−H最低∣<=1,对于空树,高度定义为0
可以粗略证明,一棵AVL树的高度最多为1.44log(N+2)−1.328。但实际高度只略大于log(N)。对于最少节点一棵高度为9的树,其左子树高度为7,右子树高度为8。在高度h的树中。最少节点S(h)=S(h−1)+S(h−2)+1。对于h=0,S(h) = 1;h=1,S(h) = 2,函数S(h)与斐波那契数列相关。因此推导出上面的高度。
如上图,左图的左子树的高度为3(3->4->2,叶子节点3高1,4高2,2高3),右子树的高度为2(7->8),差为1。右图的左子树高度为3,右子树的高度为1,相差为2,不是AVL树(叶子节点的高度为0,注意与深度的区别)。‘可以简单的看作是两棵子树的最大深度相差为大于1’。
除去插入操作外(删除操作是惰性删除,不删除,只标记删除),所有的对树的操作时间复杂度皆为O(logN),因为插入操作会破坏树的平衡,这时需要进行旋转操作来维持树的平衡。
当一个节点插入后,任意两棵子树的高度差为2,只有插入点到根节点的平衡会被打破,因为只有这些节点的子树发生了变化。把必须平衡的节点记为a,失去平衡的情况可能会有以下四种:
- 对a的左儿子的的左子树进行一次插入,失去平衡,左左(LL)。
- 对a的左儿子的的右子树进行一次插入,失去平衡,左右(LR)。
- 对a的右儿子的的左子树进行一次插入,失去平衡,左左(RL)。
- 对a的右儿子的的右子树进行一次插入,失去平衡,左左(RR)。
情况1是情况4的镜像对称,该情况可以通过单次旋转完成。情况2是情况3的镜像对称,该操作发生在‘内部’的情形,需要通过双旋转平衡。
4.4.1 单旋转
4.4.1.1 左左情况(右旋)
如上图,对于单旋转的情况1,节点k2不满足AVL平衡的性质。为使树平衡,我们需要把节点X向上移动一层,节点Z向下移动一层。将树想象为灵活有重量的,现在拿住K1节点(),K1成为根,因为K2>K1,所以K2变成K1的右孩子。子树Y中的元素都大于K1,小于K2,所以可以将其放置到K2的左孩纸的位置。
如下图是一种实际情况的处理:左左
该平衡二叉树插入6后,对8而言,左子树高度为2,右子树高度为0(不存在),相差为2,只对6、7、8而言,拿住7,进行一次右旋。(树原本是平衡的,被破坏平衡后,先处理较小子树的不平衡情况,所以这里只处理8子树的情况)。
4.4.1.12右右情况(左旋)
情况4是情况1的一种镜像对称(优先处理子树的不平衡,这里K2的子树是平衡的,是对K1不平衡,对K1是右右情况)。实例略。
4.4.2 双旋转
4.4.2.1 左右单旋转
左右的情况:
如上图,子树Y比子树Z高2层,将其抽象成如右图所示地3个连接点与4棵子树,B、C只比D深121 ,把K2重新作为根,K1比K2小,因此作为子节点,同样K3作为右节点。
PS:对于左右的情况,可以先左旋,再右旋(操作次数会增加),如下图(这里假设插入到B):
下图有误,是在K2的节点下插入,应该只有B或者C,且插入的高度为1,但是对旋转逻辑无影响。
同样的对于右左的情况:
右-左 双旋转实例
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
| public class AvlTree<AnyType extends Comparable<AnyType>> {
private static final int ALLOWED_IMBALANCE = 1;
private AvlNode<AnyType> root;
public AvlNode<AnyType> getRoot() { return root; }
public AvlTree() { this.root = null; }
public void insert(AnyType x) { root = insert(x, root); }
public void remove(AnyType x) { root = remove(x, root); }
private int height(AvlNode<AnyType> t) { return t == null ? -1 : t.height; } public int height() { return height(root); }
private AvlNode<AnyType> insert(AnyType x, AvlNode<AnyType> t) { if (t == null) { return new AvlNode<>(x, null, null); } int compareResult = x.compareTo(t.element); if (compareResult < 0) { t.left = insert(x, t.left); } else if (compareResult > 0) { t.right = insert(x, t.right); } else { } return balance(t); }
private AvlNode<AnyType> balance(AvlNode<AnyType> t) { if (t == null) { return t; } if (height(t.left) - height(t.right) > ALLOWED_IMBALANCE) { 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_IMBALANCE) { 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; }
private AvlNode<AnyType> 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> doubleWithLeftChild(AvlNode<AnyType> root) { root.left = rotateWithRightChild(root.left); return rotateWithLeftChild(root); }
private AvlNode<AnyType> rotateWithRightChild(AvlNode<AnyType> root) { AvlNode<AnyType> k1 = root.right; root.right = k1.left; k1.left = root; root.height = Math.max(height(root.left), height(root.right)) + 1; k1.height = Math.max(height(k1.left), height(k1.right)) + 1; return k1; }
private AvlNode<AnyType> remove(AnyType x, AvlNode<AnyType> t) { if (t == null) { return null; } int compareResult = x.compareTo(t.element); 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.element = findMin(t.right).element; t.right = remove(t.element, t.right); } else { t = t.left != null ? t.left : t.right; } return balance(t); }
public AvlNode<AnyType> findMin(AvlNode<AnyType> root) { if (root != null) { while (root.left != null) { root = root.left; } } return root; }
private AvlNode<AnyType> doubleWithRightChild(AvlNode<AnyType> root) { root.right = rotateWithLeftChild(root.right); return rotateWithRightChild(root); }
private static class AvlNode<AnyType> { public AnyType element; public AvlNode<AnyType> left; public AvlNode<AnyType> right; public int height;
public AvlNode(AnyType theElement) { this.element = theElement; this.left = null; this.right = null; }
public AvlNode(AnyType theElement, AvlNode<AnyType> lt, AvlNode<AnyType> rt) { this.element = theElement; this.left = lt; this.right = rt; this.height = 0; } } }
|
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
| public void preOrderPrint(AvlNode<AnyType> node){ if(node != null) { System.out.print(node.element + "->"); preOrderPrint(node.left); preOrderPrint(node.right); } }
public void inOrderPrint(AvlNode<AnyType> node){ if(node != null) { inOrderPrint(node.left); System.out.println(root.element + "->"); inOrderPrint(root.right); } }
public void postOrderPrint(AvlNode<AnyType> node){ if(node != null) { postOrderPrint(node.left); postOrderPrint(root.right); System.out.println(root.element + "->"); } }
|