标签:效率 lse root 链表实现 做了 资料 contain 直接 左旋转
一、概述
二叉查找树是一种含有附加属性的二叉树,即其左孩子小于父结点,父结点小于或等于右孩子
(二叉查找树的定义是二叉树定义的扩展)
二、 用链表实现二叉查找树
addElement操作:
addElement方法根据给定元素的值,在树中的恰当位置添加该元素,如果这个元素不是 comparable,则addElement方法会抛出NoComparableElemementException;
如果树为空,则这个新元素就将成为根结点;
如果树非空,这个新元素会与树根元素进行比较:
如果它小于根结点中存储的那个元素且根的左孩子为null,则这个新元素就将成为根的左孩子。
如果这个新元素小于根结点中存储的那个元素且根的左孩子不是null,则会遍历根的左孩子,并再次进行比较操作;
如果这个新元素大于或等于树根存储的那个元素且根的右孩子为null,则这个新元素会成为根的右孩子,
如果这个新元素大于或等于树根处存储的那个元素且根的右孩子不是null,则会遍历根的右孩子,并再次进行比较操作
如图:
private BinaryTreeNode<T> replacement(BinaryTreeNode<T> node) {
BinaryTreeNode<T> result = null;
if ((node.left == null) && (node.right == null)) {
result = null;
} else if ((node.left != null) && (node.right == null)) {
result = node.left;
} else if ((node.left == null) && (node.right != null)) {
result = node.right;
} else {
BinaryTreeNode<T> current = node.right;// 初始化右侧第一个结点
BinaryTreeNode<T> parent = node;
// 获取右边子树的最左边的结点
while (current.left != null) {
parent = current;
current = current.left;
}
current.left = node.left;
// 如果当前待查询的结点
if (node.right != current) {
parent.left = current.right;// 整体的树结构移动就可以了
current.right = node.right;
}
result = current;
}
return result;
}
removeAllOccurrences操作
removeAllOccurrences方法负责从二叉查找树中删除指定元素的所有存在;
或者,当在树中找不到指定元素时,则抛出 ElementNotFoundException异常;
如果指定的元素不是Comparable,则removeAllOccurrences方法也会抛出 ClassCastException异常,该方法会调用一次 removeElement方法,以此确保当树中根本不存在指定元素时会抛出异常,
只要树中还含有目标元素,就会再次调用 removeElement方法,注意, removeAllOccurrences方法使用了 LinkedBinaryTree类的 contans方法,还要注意,在 LinkedBinaryTree类中的find方法已经被重载了,以便利用二又查找树的有序属性
三、用有序列表实现二叉查找树
四、平衡二叉查找树
蜕化树:看起来更像一个链表,但实际上它的效率比链表的还低,因为每个结点还附带额外的开销
如图(b)所示:
右旋
要平衡化该树,我们需要:
使树根的左孩子元素成为新的根元素
使原根元素成为这个新树根的右孩子元素。
使树根的左孩子的右孩子,成为原树根的新的左孩子
左旋
要平衡化该树,我们需要:
使树根的右孩子元素成为新的根元素
使原根元素成为这个新树根的左孩子元素。
使原树根右孩子结点的左孩子,成为原树根的新的右孩子。
右左旋
并非所有的不平衡问题都可以只进行某一种旋转就能解决
对于由树根右孩子的左子树中较长路径而导致的不平衡,
我们必须先让树根右孩子的左孩子,绕着树根的右孩子进行一次右旋,然后再让所得的树根右孩子绕着树根进行一次左旋
左右旋
对于由树根左孩子的右子树中较长路径而导致的不平衡,
我们必须先让树根左孩子的右孩子绕着树根的左孩子进行一次左旋,然后再让所得的树根左孩子绕着树根进行一次右旋
五、实现二叉查找树:AVL树
六、实现二叉查找树:红黑树
(1). 红黑树:一种平衡二叉查找树,其中的每个结点存储一种颜色(红或黑,用布尔值实现,false等价于红色)
控制结点颜色的规则:
(2). 在某种程度上,红黑树的平衡限制没有AVL树那么严格,但是,他们的序仍然是logn
(3). 红黑树中的元素插入
红黑树的插入操作类似于前面的addElement方法,但是这里总是把插入的新元素颜色设置为红色,
插入新元素之后,必要时将重新平衡化该树,根据需要改变元素的颜色以便维持红黑树的属性
形式1:current == root (current是当前正在处理的结点)
我们总是设置根结点颜色为黑色,而所有路径都包括树根,因此不能违背各条路径都拥有同样数目黑色元素这一规则
形式2:current.parent.color == black(即当前结点的父结点颜色为黑色)
current所指向的结点总是一个红色结点,这意味着,如果当前结点的父结点是黑色,则可满足所有规则,因为红色结点并不影响路径中的黑色结点数目;
另外由于是从插入点处上溯处理,因此早已平衡了当前结点下面的子树
可能一:
如果是左孩子,利用 current.parent.parent.left.color 得到颜色信息(null元素的颜色为黑色),且存在两种情况:
父结点的兄弟为红色或黑色:这两种情况下,我们阐述的处理步骤都将发生在一个循环内部(该循环的终止条件如前所述)
如果父结点的兄弟为红色,这时的处理步骤如下:
设置current的父亲的颜色为 black
设置父结点的兄弟的颜色为black
设置 current的祖父的颜色为red
设置 current指向current的祖父
如果父结点的兄弟为黑色,首先要查看current是左孩子还是右孩子:
如果current是右孩子,则必须设置current等于其父亲,在继续之前还要再向左旋转current.right;
后面的步骤,与开始时current为左孩子一样
如果current是左孩子:
设置current的父亲的颜色为black
设置current的祖父的颜色为red
如果current的祖父不等于null,则让current的父亲绕着current的祖父向右旋转
可能二:
如果是右孩子,存在两种情况:
父结点的兄弟为红色或黑色:这两种情况下,我们阐述的处理步骤都将发生在一个循环内部(该循环的终止条件如前所述)
如果父结点的兄弟为红色,这时的处理步骤如下(同当前结点的父结点的兄弟颜色为红时):
设置current的父亲的颜色为 black
设置父结点的兄弟的颜色为black
设置 current的祖父的颜色为red
设置 current指向current的祖父
如果父结点的兄弟为黑色,首先要查看current是左孩子还是右孩子(与当前结点的父结点的兄弟颜色为黑时,操作对称):
如果current是左孩子,则必须设置current等于其父亲,在继续之前还要再向右旋转current.left;
后面的步骤,与开始时current为右孩子一样
如果current是右孩子:
设置current的父亲的颜色为black
设置current的祖父的颜色为red
如果current的祖父不等于null,则让current的父亲绕着current的祖父向左旋转
(4). 红黑树中的元素删除
与元素插入的那些情况一样,删除的两种情况也是对称的——取决于 current是左孩子还是右孩子。
当 current为右孩子时:
(在插入时,我们最关注的是当前结点的父亲的兄弟的颜色)
而对删除而言,焦点要放在当前结点的兄弟的颜色上(用 current.parent. left.color来指代这种颜色):
还要观察该兄弟的孩子的颜色,要注意的重要一点是:颜色的默认值是black;
这样,任何时刻如果试图取得mull对象的颜色,结果都将是black
其他的情况很容易推导出来,只要把上述情况中的“左”换成“右”、“右”换成“左”即可
如果兄弟的颜色是red,则在做其他事之前必须完成如下处理步骤:
* 设置兄弟的颜色为black
* 设置current的父亲的颜色为red
* 让兄弟绕着 current的父亲向右旋转
* 设置兄弟等于 current的父亲的左孩子
下面再继续处理过程:不管这个初始兄弟是red还是 black,这里的处理会根据兄弟的孩子的颜色分成两种情况:
如果兄弟的两个孩子都是black(或null),则需要
* 设置兄弟的颜色为red
* 设置 current等于 current的父亲
如果兄弟的两个孩子不全为black,则将查看兄弟的左孩子是否是black,如果是,则在继续之前必须完成如下步骤:
* 设置兄弟的右孩子的颜色为 black
* 设置兄弟的颜色为red
* 让兄弟的右孩子绕着兄弟本身向右旋转
* 设置兄弟等于cumt的父亲的左孩子
最后是兄弟的两个孩子都不为 black这一情况,这时必须:
* 设置兄弟的颜色为 current的父亲的颜色。
* 设置 current的父亲的颜色为black
* 设置兄弟的左孩子的颜色为black
* 让兄弟绕着 current的父亲向右旋转
* 设置 current等于树根
该循环终止之后,我们要酬除该结点,并设置其父亲的孩子引用为mull
【参考资料】
【参考资料】
错题1:
错题1解析:
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/1 | 4/4 | |
第二周 | 560/560 | 1/2 | 6/10 | |
第三周 | 415/975 | 1/3 | 6/16 | |
第四周 | 1055/2030 | 1/4 | 14/30 | |
第五周 | 1051/3083 | 1/5 | 8/38 | |
第六周 | 785/3868 | 1/6 | 16/54 |
标签:效率 lse root 链表实现 做了 资料 contain 直接 左旋转
原文地址:https://www.cnblogs.com/zhouyajie/p/9879471.html