1 2 3 4

20172310 2017-2018《程序设计与数据结构》(下)第七周学习总结

20172310 2017-2018《程序设计与数据结构》(下)第七周学习总结

教材学习内容总结

本章学习的是二叉查找树


11.1 概述

  • 二叉查找树(binay scarch tree)是种带有附加属性的二叉树,即对树中的每个结点,其左孩子都要小于其父结点,而父结点又小于或等于其右孩子。

  • 二叉查找树的定义是上章中讨论的二叉树定义的扩展。因此,下面的操作是二叉树中已定义的那些操作的补充。二叉查找树和平衡二叉查找树的接口是一样的程序列表。


11.2 用链表实现二叉查找树

  • BinaryTreeNode在二叉查找树中也用来表示树中的每个节点,每个BinaryTreeNode对象要维护一个指向结点所存储元素的引用,另外还要维护指向结点的每个孩子的引用。

  • addElement 操作:addElement方法根据给定元素的值,在树中的恰当位置添加该元素。
    添加元素位置的几种情况
    • 如果这个元素不是Comparable,则addElement方法会抛出NoComparableElementException异常。
    • 如果树为空,则这个新元素就将成为根结点。
    • 如果树非空,且它小于根结点中存储的那个元素且根的左孩子为null, 则这个新元素就将成为根的左孩子。如果这个新元素小于根结点中存储的那个元素且根的左孩子不是null, 则会遍历根的左孩子,并再次进行比较操作。
    • 如果这个新元素大于或等于树根存储的那个元素且根的右孩子为null, 则这个新元素会成为根的右孩子。如果这个新元素大于成等于树根处存储的那个元素且根的右孩子不是null,则会遍历根的右孩子,并再次进行比较操作

  • 一旦定义了希望构造的树的类型和树的使用方式,也就能够定义出该树的接口和各种实现。

  • removeElement操作
    • removeElemcnt方法必须选出另一个结点来代替要被删除的那个结点。private方法replacement返回指向一个结点的引用,该结点将代替要删除的结点。
      选择替换结点的三种情况如下:
    • 如果被删除结点没有孩子,则replacement返回null.
    • 如果被删除结点只有一 个孩子, 则replacement返回这个孩子。
    • 如果被删除结点有两个孩子,则replacement 会返回中序后继者(相等元素会放到右边)。
      且这个后继者不可能有左孩子节点,因为如果有左孩子节点则它的左孩子节点会成为那个后继者,而不是它,因此后继者要么没有孩子节点,要么只有右孩子。

二叉查找树的最右侧结点会存放最大元素,而其最左侧结点会存放最小元素.

  • removeMin操作
    按照二叉树的定义,最小元素在二叉查找树中只会存在于最左边的位置,最左的位置分为有3种可能情形:

    • 如果树根没有左孩子,则树根就是最小元素,而树根的右孩了会变成新的根结点。
    • 如果树的最左侧结点是片叶子, 则这片叶子就是最小元素, 这时只需设置其父结点的左孩子引用为mull即可。
    • 如果树的最左侧结点是个内部结点,则需要设置其父结点的左孩 子引用指向这个将删除结点的右孩子。
  • removeMax操作同理


11.3用有序列表实现二叉查找树

  • 树的主要使用之一就是为其他集合提供高效的实现。

  • BinarySearchTreeList实现的分析:BinarySearchTreeList 的实现是一种任何结点的最大深度为log2(n)(其中n为树中存储的元素数目)的平衡二叉查找树。所以,add 操作和remove操作都要求重新平衡化树。
  • 虽然树实现中的有些操作更为有效,比如removeLast、last 和contains:但在利用树实现时,也有一些操作会变得低效,比如removeFirst和first。

11.4平衡二叉查找树

  • 蜕化树:如同链表一般,只有单边的子树。实际效率比链表还低。

  • 如果二叉查找树不平衡,其效率可能比线性结构的还要低。

  • 平衡二叉树:任何一个节点的左右子树深度差不超过1.通过这个限定,阻止了二叉树的左右子树深度差较大的情况,维持了二叉树的稳定。

  • 平衡化技术中的一些方法:

    • 右旋:节点插入在最小不平衡树的右子树的右子树上面。 

    • 左旋:节点插入在最小不平衡节点的左子树的左子树上。

    • 右左旋:节点插入在最小不平衡树的右子树的左子树上面。

    • 左右旋:节点插入在最小不平衡节点的左子树的右子树上面

    • 用法总结:从发生不平衡的结点起,沿刚才回溯的路径取直接下两层的结点,如果这三个结点在一条直线上,则采用单旋转进行平衡化,如果这三个结点位于一条折线上,则采用双旋转进行平衡化。如图:

这是一篇很好的参考资料:数据结构——平衡二叉树

  • 为了解决上述二叉排序树这种左右子树深度因为插入或删除结点而不均匀的情况引入了两种方式红黑树和AVL树。

  • AVL树:是一种平衡二叉树的变体,其中最重要的一个概念是平衡因子:右子树的高度减去做紫薯的高度称为该结点的平衡因子。其旋转方式同上。
  • 红黑树:
    1、根节点是黑色。
    2、如果一个节点是红色的,则它的子节点必须是黑色的。
    3、从树根到树叶的所有路径上包含相同数目的黑色结点。
    4、每个节点或者是黑色,或者是红色。
    5、每个空结点为黑色。

教材学习中的问题和解决过程

  • 问题1:对课本removeElement方法的代码理解有困难,主要是后面的删除元素的图给错了,于是理解了半天。

  • 问题1解决方案:其实最重要的是replacement代码段的理解,课本给出了三种情况的分类,“如果被删除结点有两个孩子,则replacement 会返回中序后继者”这种类型的时候中序后继者是什么意思呢?我结合着课本给出的删除结点的示意图来理解。

如果删除的节点是10,它有左右孩子,中序遍历是先查左孩子,再是该节点,然后是右孩子,中序遍历查找的顺序是7,10,13,15 。现在10是当前所指向的结点,所以从这个步骤开始,继续接下去的遍历,也就是看下一个查找的元素,然后返回它,也就是返回13 。

 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;//创建一个结点current,存放当前节点的右孩子
            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为null,parent.left从被找到的结点变为null
                current.right = node.right;
            }
            
            result = current;
        }
        
        return result;
    }
  • 问题2:对课本中红黑树的操作完全看不懂。

  • 问题2解决方案:看完了课本对于红黑树操作的讲解,我不得不说,这些字我都认识,可放在一起我怎么就理解不了呢?(〃>皿<)
    对课本上给出的合法的红黑树的例子,我也表示怀疑,所以就去百度上找了一些资料来看看。

  • 红黑树中元素的插入:
    1.首先插入结点。插入的结点定为红色( 因为将插入的节点着色为红色,不会违背"从树根到树叶的所有路径上包含相同数目的黑色结点。",少违背一条特性,就意味着我们需要处理的情况越少)。
    2.插入结点后的红黑树仍是一棵二叉查找树,但是插入后变得不再平衡了。之后重新平衡化或者说重新着色的过程则是一种迭代的过程,所以插入节点后,我们要做的就是将破坏红黑树规则的结点通过重新着色上移到别的结点。
    3.接下来是分情况讨论如何变色和旋转,使其重新平衡。
    (1)其父节点为black,这种情况下没有违背红黑树任何条件,直接插入即可(包含被插入的节点为根节点的情况);
    (2)插入的结点的父节点是red的情况下,又可根据叔叔结点的case划分为三种情况来处理;
    - case1:当叔叔结点也为红色时,第一,先将父节点和叔叔结点变为black;第二,将祖父结点变为red;最后运用递归继续改变更底层的结点。

                  - case2:当叔叔结点是black时,如果当前结点是父节点的左孩子,则将父节点染为black,在把祖父结点染为red,最后以祖父节点为支点进行右旋。
    

                - case3:当叔叔结点是black时,而当前结点是父节点的右孩子,则以该插入的结点的父节点作为当前节点进行左旋,变为case2进行处理。

  • 红黑树中元素的删除
    我之前已经总结过,在一棵平衡二叉树要删除一个结点有三种情况。
    这三种情况在红黑树中一样,但我们需要做的是将删除后的二叉树重新平衡和染色,现在,又可分为以下几种情况:
    1)删除的结点为叶子节点,然后判断该节点是否为黑色的,若为true,则在删除后会导致黑高不相等。于是需要对红黑树进行调整。
    2)删除的结点有一个孩子节点,此时直接使用其孩子节点代替z节点,然后判断删除的节点是否为黑,若为true,则对红黑树进行调整。
    3)删除的结点有左右孩子节点,这时首先找到该节点的后继节点,有两种情况:第一种情况是y是z的右孩子节点,第二种情况是y不是z的右孩子节点。

调整的方式:

  1. 当删除情况为第一二种且结点为black时,我们可以将替代原结点的节点x再加一种再额外增加一种黑色。(网上的资料都是这样解释的,但是而额外增加的到底是怎么回事我还没有研究透彻,如果弄懂了后续再修改╭(╯^╰)╮)

在第三种情况中,通过使用z的后继节点y替换z节点,然后使用y的右孩子x来填补y的位置。在此过程需要将节点y进行移动,由于移动之后的y节点保持原来z节点的颜色,而x节点在代替y节点之后可能会出现问题,当然只会在y节点是黑色的情况下才会出现问题,当y节点为红色时,移动时红黑树的性质不会被破坏,y节点为黑色时,一定会出现黑高的不相等,并且也可能会出现两个连续的红色节点。这时需要对其进行下一步调整。

红黑树的删除
这是关于删除的一份资料,跟着这里面看可以理解很多,但自己还是有些内容理解不了,主要是删除比插入的情况更多而且更复杂,理解起来还是很有难度的。

代码调试中的问题和解决过程

  • 问题1:

自己编写AVL树时,按照对左右旋和右左旋的理解编写的代码是这样的,但是参考网上的资料并和结对伙伴讨论发现,大家的代码是

这是为什么呢?

  • 问题1解决方案:我发现其实自己的理解并没有错,左右旋确实是先左旋,再右旋,那么问题在哪?大家普遍都是根据网上的代码来写的,左、右旋是这样的


但实际上左旋中的内容其实是右旋时进行的,而右旋时的内容才是左旋的,我使用了参考的左、右旋代码,于是接下去自己写左右旋和右左旋的代码时出现了错误。
其实对这几个操作的理解还是正确的。

  • 问题2:

  • 问题2解决方案:我看到这里出错了,还以为是旋转操作的书写又发生了错误,可是再重新解读了一遍自己的代码发现完全没有逻辑上的错误,于是又花了挺久的时间,还在同学的帮助下发现自己真的是粗心,因为自己是仿照之前的代码写的,所以最开始定义的是一个node,然后现在需要一个element,结果构造函数中竟然没有写,于是发生了这种错误。

代码托管

(statistics.sh脚本的运行结果截图)

上周考试错题总结

  • 上周无错题(ノ ̄▽ ̄)

结对及互评

点评:

  • 本周结对学习情况

  • 博客和代码中值得学习的或问题:

    • 看问题的角度很新颖,就像是我们大家都在解决如何理解红黑树的插入和删除操作时,我的队友的想法却是AVL树和红黑树可以达到相同的目的,那为什么要创造出红黑树呢,进而去了解这两者的优缺点,又让我涨了知识,点赞。
    • 代码问题很细致,解答也挺详细的。

点评过的同学博客和代码

其他(感悟、思考等,可选)

这周的学习是建立在上周所学知识的基础上的,果然基础要打好,后面的学习才会更有效率。不得不批评一下淘宝买来的课本了,太坑爹了,图错了好几个,我一直研究,一直觉得不对,后来事实证明我是正确的,不过还是浪费了一些时间在这个上面。双周的课程比单周要少一些,所以这周自学Java的时间要多一些,果然知识都是靠时间换来的。

学习进度条

|| | 代码行数(新增/累积)| 博客量(新增/累积)|学习时间(新增/累积)|重要成长|
| -------- | :----------------😐:----------------😐:---------------: |:-----😐
| 目标 | 5000行 | 30篇 | 400小时 | |
| 第一周 | 0/0 | 1/1 | 10/10 | |
| 第二周 | 326/326 | 1/2 | 18/28 | |
| 第三周 | 784/1110 | 1/3 | 25/53 | |
| 第四周 | 2529/3638 | 2/5 | 37/90 | |
| 第五周 | 1254/4892 | 2/7 | 20/110 | |
| 第六周 | 1403/6295 | 2/9 | 32/142 | |
| 第七周 | 1361/7656 | 1/10 | 35/177 | |

  • 计划学习时间:30小时

  • 实际学习时间:35小时

  • 改进情况:这周在概念的理解上比以往花的时间要多。

参考资料

posted @ 2018-11-02 21:12  qx2017  阅读(175)  评论(0编辑  收藏  举报
页尾