20172306 2018-2019-2 《Java程序设计与数据结构》第七周学习总结
教材学习内容总结
-
概述
- 二叉查找树是一种含有附加属性的二叉树,即其左孩子小于父结点,而父结点又小于或等于右孩子。
- 二叉查找树的定义是二叉树定义的扩展。
- 二叉查找树的各种操作:addElement ,removeElement ,removeAllOccurrences(从树中删除所指定元素的任何存在) ,removeMin ,removeMax ,findMin ,finMax
-
用链表实现二叉查找树
每个BinaryTreeNode对象要维护一个指向结点所存储元素的引用,另外还要维护指向结点的每个孩子的引用。
-
addElement操作(假设:根结点为node)
- (1)如果树为空,则插入的element为根结点
- (2)如果树不为空,element<node,且node的左孩子为null,则null的空为element;如果node的左孩子不为null,则继续遍历根的左孩子,再进行操作。
- (3)如果树不为空,element>= node,且node的右孩子为null,则null的空为element;如果node的右孩子不为null,则继续遍历根的右孩子,再进行操作。
-
removeElement操作
- 与前面的线性结构研究不同,这里不能简单地通过删除指定结点的相关引用指针而删除该结点。相反,这里必须推选出另一个结点来代替要被删除的那个结点。受保护方法replacement返回指向一个结点的引用,该结点将代替要删除的结点 。
- 替换的replacement的代码如下:
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;
}
-
选择替换结点的三种情况:
- 如果被删除结点没有孩子,则replacement返回null
- 如果被删除结点只有一个孩子,则replacement返回这个孩子
- 如果被删除结点有两个孩子,则replacement会返回中序后继者(因为相等元素会放到右边)
-
对于这个删除,有几种情况:
- (1)要删除的结点无孩子结点;删除方法:直接删除该结点
- (2)要删除的结点只有左孩子结点;删除方法:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点;
- (3)要删除的结点只有右孩子结点;删除方法:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点
- (4)要删除的结点有左、右孩子结点;删除方法:找中序后继者,用它的值填补到被删除节点中,再来处理该结点的删除
-
removeAllOccurrences操作
- 我觉得这个操作主要还是利用removeElement。和removeElement不同的是,我们会利用一个find的操作来判断有没有这个targetElement的存在,有的话,就继续利用removeElement的相关方法进行删除。没有这个targetElement就抛出异常。
public void removeAllOccurrences(T targetElement) {
removeElement(targetElement);//在我看来就是利用了之前的删除元素的方法
try {
while (contains((T) targetElement))
removeElement(targetElement);
}
catch (Exception ElementNotFoundException) {
}
}
-
removeMin操作(二叉查找树的最右侧结点会存放最大元素,最左侧结点会存放最小元素)
- 我认为removeMax和removeMin的原理是相同的,懂了removeMin就会removeMax了。
- 最小元素的位置的3种情形:(假设:树根是root)
- (1)如果root没有左孩子,则root为min,root的右孩子就会变成新的root
- (2)如果树的最左侧结点是一片叶子,则这个叶子就是min,这时只需设置其父结点的左孩子引用为null
- (3)如果树的最左侧结点是一个内部结点,则需要设置其父结点的左孩子引用指向这个将删除结点的右孩子
-
用有序列表实现二叉查找树
- 树的主要使用之一就是尾其他集合提供高效的实现
- add和remove操作都会对树的平衡有影响,而这一点同时对所使用的算法的分析也有影响
-
平衡二叉查找树
如果二叉查找树不平衡其效率可能比线性结构的还要低
右旋:a.根的左孩子为新的根元素;b.原根成为新根的右孩子;c.原根的左孩子的右孩子为原根的新的左孩子。(如果是因为树根左孩子的左子树中较长的路径而导致的不平衡,右旋可以解决它)
左旋:a.根的右孩子为新的根元素;b.原根成为新根的左孩子;c.原根的右孩子的左孩子为原根的新的右孩子。 (对于由树根右孩子的右子树中较长路径而导致的不平衡,左旋可以解决它)
右左旋:先将子树中左旋变成右子树比较长的,然后右旋。
左右旋:先将子树中右旋变成左子树比较长的,然后左旋。
了解了左旋和右旋,其实右左旋和左右旋都是在这两个的基础上进行的。
这两种二叉查找树:我们以上的这几种操作都是在平衡二叉查找树的条件下进行的,而AVL树和红黑树就是将不是平衡的树变成平衡树。
-
树只有两个途径能变得不平衡:插入结点和删除结点。
- 原因:我认为是根据之前的插入和删除的操作,可以知道在插入和删除的时候,会对树的长度发生很大的变化,所以极可能导致不平衡。
对于AVL树和红黑树,都要从插入和删除结点的位置需要上溯树,所以通常最好实现为每个结点都包含一个指向其父结点的引用。
-
实现二叉查找树:AVL树
- 平衡化树的一般性方法:其中自树根而下的路径最大长度必须不超过logn,而且自树根而下的路径最小长度必须不小于logn-1。
- 平衡因子:其左右子树的高度差(即右子树的高度减去左子树的高度)——大于1或者小于-1,则以该结点为树根的子树需要重新平衡。
- 我认为对于AVL的右旋、左旋、右左旋、左右旋的过程和之前的没有区别,我们学习AVL树的就是,它注明了平衡因子,而这个就是为了我们在看到一个树时,判断是利用哪种旋的方式的。
-
实现二叉查找树:红黑树
- 红黑树的样子:
- 其中白色代表红色,黑色代表黑色。
- 结点存储一种颜色(红色或黑色,通常用一个布尔值来实现,false等价于红色)
- 规则
- (1)根结点为黑;
- (2)叶子的结点也为黑,因为每个叶子后面的都跟着一个null,null是黑色结点,所以是黑色结点。
- (3)红结点的所有孩子都为黑色;
- (4)从树根到树叶的每条路径都包含同样数目的黑色结点
- 由于红色结点不能有红色孩子,所以路径中至多有一半结点是红色结点、至少有一半结点是黑色结点
-
对于红黑树的插入和删除,书中给的很模糊,老师讲过之后,其实我觉得还是老师的PPT更懂一些,因为有图,条理也很清晰。(附上几张PPT内容)
- 插入
- 一般设置要插入结点的颜色为Red。
- 插入时分为三种情况:
- 被插入为根结点,那么就直接染色为黑色
- 被插入结点的双亲结点为黑色,不用做,因为对于黑色没有什么影响。
- 被插入结点的双亲结点为红色,这就会有三种情况:
- case1:当前结点的双亲结点A为红色,且其祖父的另一个子结点为红:
- case2:当前结点的双亲结点A为红色,且其叔叔结点为黑色,且其为其双亲结点的右孩子:
- case3:当前结点的双亲结点是红色,其叔叔结点为黑色,当前结点是其双亲结点的左孩子:
- 删除
- 有三种情况:
- one:要删除结点没有孩子结点:直接删除
- two:要删除结点只有一个孩子结点:直接使用其孩子结点代替要删除结点
- three:要删除结点有左右孩子结点:要考虑后继结点的问题
- 有三种情况:
- 插入
教材学习中的问题和解决过程
- 问题1:在书中说,LinkedBinarySearchTree类提供了两个构造函数,这两个函数都只是引用了其超类(LinkedBinaryTree类),但是好像很少听到超类这个词
- 问题1解决方案:我上网查了下,就暴露了我以前书看的不好的问题了。原来超类就是父类啊。
public class A{//定义类A
}
public class B extends A{//定义类B,继承类A
}
则,类A就是超类或父类,类B叫子类
问题2:书中这句,如果被删除结点有两个孩子,则replacement会返回中序后继者,中序后继者这个词我没有理解?
问题2解决方案:这个位置我回到第十章那个中序遍历那看了一眼。用一个图来表示我的收获。
而且我还得到了,其实中序后继者,就是找这个要删除的结点的右子树中最小的那个值,这个值就是中序后继者。问题3:书中的红黑树部分说,规则之一是从树根到树叶的每条路径都包含同样数目的黑色结点,但是书中有个图
问题3解决方案:这个是不对的,因为不符合规则中的每条路径的黑色结点相同;且这个可以看出,对于红黑树,它的平衡性不是特别的严格。
代码调试中的问题和解决过程
问题1:在对LinkedBinarySearchTree进行测试时,出现了这样的问题。,它出现了地址。
问题1解决方案:最开始我觉得肯定没问题啊,后来出现这样了。这个问题其实挺普遍的,是toString方法没写的缘故,我忘记写了!!!它这个也是树,所以我直接就将第十章的printTree的给拿过来了。
代码托管
上周考试错题总结
- 上周没有错题
结对及互评
点评模板:
- 博客中值得学习的或问题:
- 内容总结的比较详细
- 问题有个分析,虽然很详细,但是吧,如果可以自己理解,我觉得会更好
点评过的同学博客和代码
- 本周结对学习情况
- 20172325
- 结对照片
- 结对学习内容
- 一起学习了第十一章的内容
- 一起进行了编程十一章的作业
其他(感悟、思考等,可选)
觉得吧,这一章的红黑树真的是迷的一批,我都看不懂,只能慢慢看,但是这一章的内容在第十章的基础上再看的话,我就觉得好像比刚接触树时更懂点了,而且我还在书中把一些转换的过程自己试了试,尽力的搞懂它,我觉得这也算是一种进步吧。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/1 | 6/6 | |
第二周 | 985/985 | 1/1 | 18/24 | |
第三周 | 663/1648 | 1/1 | 16/40 | |
第四周 | 1742 /3390 | 2/2 | 44/84 | |
第五周 | 933/4323 | 1/1 | 23/107 | |
第六周 | 1110/5433 | 2/2 | 44/151 | |
第七周 | 1536/6969 | 1/1 | 56/207 |