20172313 2018-2019-1 《程序设计与数据结构》第七周学习总结

20172313 2018-2019-1 《程序设计与数据结构》第七周学习总结

教材学习内容总结

  • 概述

    • 二叉查找树:二叉查找树是一种含有附加属性的二叉树,即其左孩子小于父结点,而父结点又小于或等于右孩子。二叉查找树的定义是二叉树定义的扩展
    • 二叉查找树的各种操作
操作 说明
addElement 往树中添加一个元素 
removeElement 从树中删除一个元素 
removeAllOccurrences 从树中删除所指定元素的任何存在 
removeMin 删除树中的最小元素 
removeMax 删除树中的最大元素 
findMin 返回一个指向树中最小元素的引用 
findMax 返回一个指向树中最大元素的引用 
  • 用链表实现二叉查找树:每个BinaryTreeNode对象要维护一个指向结点所存储元素的引用,另外还要维护指向结点的每个孩子的引用。
  • addElement操作:我们只要利用二叉查找树的特性(即对每个父结点,它的左子树中所有项的值小于父结点中的值,而它的右子树中所有项的值都大于T中的值),找到只对应的插入位置即可,如果树为空,则这个新元素就将成为新结点,如果树非空,沿着树查找(根据element的大小来判断向左还是向右)。假如我们现在要插入element为4的结点,如果找到element(4),则什么也不做,否则将element插入到遍历的路径上的最后一个点,如下图所示:
  • removeElement操作:对于二叉查找树来说,删除元素的时候要考虑三种情况:
    • ①如果要删除的结点q恰好是叶子结点,那么它可以立即被删除
    • ② 如果要删除的结点q拥有一个孩子结点,则应该调整要被删除的父结点(p.left 或 p.right)指向被删除结点的孩子结点(q.left 或 q.right)
    • ③如果要删除的结点q拥有两个孩子结点,则删除策略是用q的右子树的最小的数据替代要被删除结点的数据,并递归删除用于替换的结点(此时该结点已为空),此时二叉查找树的结构并不会被打乱,其特性仍旧生效。采用这样策略的主要原因是右子树的最小结点的数据替换要被删除的结点后可以满足维持二叉查找树的结构和特性,又因为右子树最小结点不可能有左孩子,删除起来也相对简单些。
  • removeAllOccurrences操作:可以看做调用了removeElement,当在树中找不到指定元素是,则抛出ElementNotFoundException异常,如果指定的元素不是Comparable,则removeAllOccurrences方法也会抛出ClassCaseException异常。只要树中还含有目标元素,就会再次调用removeElement方法。
    public void removeAllOccurrences(T targetElement)
            throws ElementNotFoundException
    {
        removeElement(targetElement);

        try
        {
            while (contains((T)targetElement))
                removeElement(targetElement);
        }

        catch (Exception ElementNotFoundException)
        {
        }
    }
  • removeMin和removeMax操作:对于findMin(),则需要从根结点开始并且只要有左孩子就向左进行即可,其终止点即为最小值的元素;而对于findMax(),也需要从根结点开始并且只要有右孩子就向右进行即可,终止点即为值最大的元素。
  • 用有序列表实现二叉查找树:add操作和remove操作都可能导致树变得不平衡。
操作 说明 LinkedList BinarySearchTreeList
removeFirst 删除列表的首元素  O(1) O(logn)
removeLast 删除列表的末元素  O(n) (logn)
remove 删除列表中的一个特定元素 O(n) O(logn)*
first 考察列表前端的那个元素  O(1) O(logn)
last 考察列表末端的那个元素  O(n) O(logn)
contains 判断列表是否含有一个特定元素  O(n) O(logn)
is Empty 判定列表是否为空  O(1) O(1)
size 判定列表中的元素数目  O(1) O(1)
add(有序列表特有) 向列表添加一个元素  O(n) O(logn)*
  • 平衡二叉查找树:如果没有平衡假设,最坏情况下addElement操作的实践复杂性是O(n)而不是O(logn),因为可能存在树根是树中的最小元素,而将被插入的元素可能是树中的最大元素,这种情况下,它的效率比链表的还低,因为每个结点还附带额外的开销。
  • AVL树的旋转:当一棵树的最大路径长度大于log2^n,或最小路径长度小于log2^n-1时,就要平衡化该树。对于AVL树,树中的每一个结点,我们都会跟踪其左右子树的高度。对于树中的任何结点,如果其平衡因子(左右子树的高度差)大于1或小于-1,则以该节点为树根的子树需要重新平衡。
  • 上左图是一棵平衡二叉树,它每个结点的左子树和右子树的高度最多相差1,它同时也是一棵二叉查找树,而上右图虽然也是一棵二叉查找树,但是它每个结点的左子树和右子树的高度相差为2,所以它不是平衡二叉树。当引起结点数量变化时,即进行删除和插入操作,如下图,插入一个新的结点,原本的平衡二叉树就失去了平衡。
  • 既然二叉树失去了平衡,我们就要使用适当的操作来使它恢复平衡。如果某结点的平衡因子为-2,左孩子的平衡因子是-1,这就意味着该结点的左子树中有一条过长的路径,所以应该采用右旋。在原始AVL树插入7结点后,结点9变为失衡点,树再满足AVL性质,因此需要对9结点进行左左单旋转(即向右旋转)后,得到下右图,我们发现此时并没有操作树的根结点(6),实际上这是因为正常情况下,不必从树的根结点进行旋转,而是从插入结点处开始,向上遍历树,并更新和修复在这个路径上的每个结点的平衡及其平衡信息(高度)即可。(左旋类似,当某结点的平衡因子是+2,右孩子的平衡因子是+1的时候使用左旋,左旋中的“左”,意味着“被旋转的结点将变成一个左结点”。右旋同理)
  • 当某结点的平衡因子是+2,如果其右孩子的平衡因子是-1,这时子树太“深”了,无论是左旋还是右旋,都无法使操作后的数成为AVL树,这个时候就需要使用双旋,首先让出初始结点右孩子的左孩子,绕着初始结点的右孩子进行一次右旋,然后再让初始结点的右孩子,绕着初始结点进行一次左旋。(左右旋类似,当某结点的平衡因子是-2,左孩子的平衡因子是+1的时候使用左右旋)
  • 红黑树
    • 树中的每一个结点都储存着一种颜色(红色或黑色,通常使用一个布尔值来实现,值false等价于红色)。
    • 根结点为黑色。
    • 每个叶子结点(null)是黑色。(**注意:这里的叶子结点,是指为空(null)的叶子结点!)
    • 从树根到树叶的每条路径都包含有同样数目的黑色结点。
    • 如果一个结点的颜色为红色,那么它的子结点必定是黑色。
    • 在红黑树中,元素的查找仍然是一种O(n)操作,由于红色结点不能有红色孩子,于是路径中至多有一半结点时红色结点、至少有一半结点是黑色结点,据此我们可以论证红黑树的最大高度约为2*logn,于是遍历最长路径的序仍然是logn。
  • 红黑树的添加操作:红黑树本身就是一棵二叉查找树,所以当添加元素或删除元素后,我们仍然需要使所得到的是一棵二叉查找树,这就使得我们要对红黑树进行重新着色。我们先来回头看上面所说的红黑树的性质,如果要保证从树根到树叶的每条路径都包含有同样数目的黑色结点,那么我们把插入的元素设置为红色,就可以保持,接下来我们只需从该结点向上遍历,保证红色结点的子结点必定为红色即可满足红黑树的所有要求。我们把要插入的结点设置为红色,那么根据父结点的颜色又可以分为三种情况。
    • ①被插入的结点是根结点。(我们可以直接将该结点涂成黑色)
    • ②被插入的结点的父结点是黑色。 (我们无需进行操作,插入之后仍为红黑树)
    • ③被插入的结点的父结点是红色。(我们对该种情况要进行着重讨论,因为被插入的结点的父结点是红色,所以该结点的祖父结点必定存在(即父结点的父结点),父结点的兄弟结点也必定存在。(即“叔叔”结点,即使叔叔结点为空,我们也视之为存在,空结点本身就是黑色结点)我们根据叔叔结点的颜色又可以分成三种情况)
情况 处理方式
当前结点的父结点是红色,且当前结点的祖父结点的另一个子结点(叔叔结点)也是红色。 ①将“父结点”设为黑色。
② 将“叔叔结点”设为黑色。
③ 将“祖父结点”设为“红色”。
④ 将“祖父结点”设为“当前结点”(红色结点);指针current由指向插入的结点变为“当前结点“”,之后继续对“当前结点”向上进行操作。
当前结点的父结点是红色,叔叔结点是黑色,且当前结点是其父结点的右孩子 ①将“父结点”作为“新的当前结点”。
②以“新的当前结点”为支点进行左旋。
当前结点的父结点是红色,叔叔结点是黑色,且当前结点是其父结点的左孩子 ① 将“父节点”设为“黑色”。
②将“祖父节点”设为“红色”。
③以“祖父结点”为支点进行右旋。
  • 如上图介绍了如何进行操作,下面我们再来谈谈为什么要这么操作。

    • 第一种情况,由于红色结点的子结点不能是红色,所以我们把父结点要变为黑色,但当我们把父结点变为黑色以后,从树根到树叶之间的黑色结点的个数就不相等了,所以把祖父结点变为红色,同样的,因为红色结点的子孩子不能是红色,所以要把叔叔结点变为黑色。祖父结点一定是黑色吗?答案是肯定的,因为在元素添加之前,该二叉树就是红黑树,父结点是红色的,那么祖父结点一定是黑色的。按照上述步骤处理之后,当前结点,父结点,叔叔结点都满足了红黑树的性质。若此时,祖父结点是根结点,直接将祖父结点设为“黑色”,那就完全解决这个问题了;若祖父结点不是根结点,那我们需要将“祖父结点”设为“新的当前结点”,接着对“新的当前结点”进行分析。
    • 第二种情况,我们在上面表中说到,要以父结点为支点进行左旋,那么为什么要进行左旋呢?处理红黑树的核心思想:将红色的节点移到根节点;然后,将根节点设为黑色。新插入的结点为红色,破坏了整棵红黑树,所以我们要通过左旋来将它上移。上移之后,如果该结点变成了根结点,那么直接把它涂成黑色,若该结点不是 根结点,那么我们需要对父结点进行操作(在下图中也就是40) ,为什么不直接对60的当前结点进行操作,而是转而处理原来的父结点40呢?因为通过左旋之后,原来的父结点(40)变成了子结点(60)的子结点,而处理红黑树的时候需要从下往上处理,所以要先对40的结点操作。
    • 第三种情况,当按照上图第二种情况左旋后,就变成了下面这种情况。由于(40)和(60)两个结点都是红色,所以我们可以先把(60)结点变为黑色,但变为黑色的话,由根结点经过(60)结点的路径黑色结点数就会增加,所以我们可以让(60)的父结点(即(80))变为红色,并以该父结点作为支点进行右旋。
  • 红黑树的删除操作:红黑树的删除和常规二叉树删除元素的操作一样,也分为三种情况。
    • ①被删除的结点没有子结点(即叶子节点),直接将该结点删除。
    • ②被删除的结点有一个子结点,将该结点删除,并让父结点指向该结点的子结点。(可以看前文的示意图)
    • ③被删除的结点有两个子结点,用该结点的右子树的最小的数据替代要被删除结点的数据,并递归删除用于替换的结点。(前文有,在此不再过多赘述)
  • 在删除结点的时候,我们先来想一下所删结点的位置,如果删除的是根结点,那么根结点就可能不为黑色,如果它有子结点,删除后可能会导致红色结点的子结点有红色结点还有可能会导致从根到各个路径的黑色结点的数量不同。我们就来解决上面上说的这些问题。

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

  • 问题一:在学习教材的时候,p225的代码中有这样一行判断条件“(!(element instanceof Comparable))”,不是很理解这行代码的意思。
  • 问题一解决方法:原来是学习过的,时间久了有些忘记。java 中的instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。用法:result = object instanceof class参数:Result:布尔类型。Object:必选项。任意对象表达式。Class:必选项。任意已定义的对象类。说明:如果 object 是 class 的一个实例,则 instanceof 运算符返回 true。如果 object 不是指定类的一个实例,或者 object 是 null,则返回 false。 举例:
interface A{}
    class B implements A{

    }
    class C extends B {

    }

    class instanceoftest {
        public static void main(String[] args){
            A a=null;
            B b=null;
            boolean res;

            System.out.println("instanceoftest test case 1: ------------------");
            res = a instanceof A;
            System.out.println("a instanceof A: " + res);

            res = b instanceof B;
            System.out.println("b instanceof B: " + res);

            System.out.println("/ninstanceoftest test case 2: ------------------");
            a=new B();
            b=new B();

            res = a instanceof A;
            System.out.println("a instanceof A: " + res);

            res = a instanceof B;
            System.out.println("a instanceof B: " + res);

            res = b instanceof A;
            System.out.println("b instanceof A: " + res);

            res = b instanceof B;
            System.out.println("b instanceof B: " + res);

            System.out.println("/ninstanceoftest test case 3: ------------------");
            B b2=(C)new C();

            res = b2 instanceof A;
            System.out.println("b2 instanceof A: " + res);

            res = b2 instanceof B;
            System.out.println("b2 instanceof B: " + res);

            res = b2 instanceof C;
            System.out.println("b2 instanceof C: " + res);
        }
    }

  • 问题二:在学习红黑树的时候,书上提到终止迭代的条件也可以是“current.parent.color == black”,子树的结点不也可以是黑色的吗?为什么判断当前结点的父结点的颜色就可以终止迭代了呢?

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

  • 问题1:在做编程项目pp9_3的时候,不知道怎么计算程序的执行时间。
  • 问题一解决方案:
一般输出日期时间经常会用到Date这个类:

 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
 System.out.println(df.format(new Date()));// new Date()为获取当前系统时间
(1)以毫秒为单位计算
  static long currentTimeMillis() , 该方法返回值是从1970年1月1日凌晨到此时刻的毫秒数

 long startTime=System.currentTimeMillis();   //获取开始时间
 doSomeThing();  //测试的代码段
 long endTime=System.currentTimeMillis(); //获取结束时间
 System.out.println("程序运行时间: "+(end-start)+"ms");
(2)以纳秒为单位计算
 long startTime=System.nanoTime();   //获取开始时间
 doSomeThing();  //测试的代码段
 long endTime=System.nanoTime(); //获取结束时间
 System.out.println("程序运行时间: "+(end-start)+"ns");

代码托管

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

上周考试错题总结

??这周没有错题哦~

结对及互评

点评过的同学博客和代码

  • 本周结对学习情况

    • 结对同学学号1
    • 结对照片
    • 结对学习内容
      • XXXX
      • XXXX
      • ...
  • 上周博客互评情况
    • 学号1
    • 学号2
    • 学号3
    • 学号4
    • ...

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

xxx
xxx

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时
第一周 200/200 2/2 20/20
第二周 300/500 2/4 18/38
第三周 500/1000 3/7 22/60
第四周 300/1300 2/9 30/90
  • 计划学习时间:XX小时
  • 实际学习时间:XX小时
  • 改进情况:

(有空多看看现代软件工程 课件
软件工程师能力自我评价表
)

参考资料

原文地址:https://www.cnblogs.com/yu-kunpeng/p/9880926.html

时间: 2024-10-10 06:39:57

20172313 2018-2019-1 《程序设计与数据结构》第七周学习总结的相关文章

《程序设计与数据结构》第九周学习总结

学号 20172326 <程序设计与数据结构>第九周学习总结 教材学习内容总结 图:图(Graph)是一种复杂的非线性结构,在图结构中,每个元素都可以有零个或多个前驱,也可以有零个或多个后继,也就是说,元素之间的关系是任意的.与树的区别在于树中的一个结点只有一个前驱,也就是说只有一个父结点.但图中的顶点(结点)是没有这种限制关系的. 无向图:边为无需结点对的图.也就是说,现有两个顶点A.B.(A,B)与(B,A)效果相同.当两个顶点之间有边连接,也就是说这两个顶点是邻接的,也称作邻居.一个顶点

20172305 2017-2018-2 《程序设计与数据结构》第一周学习总结

20172305 2017-2018-2 <程序设计与数据结构>第一周学习总结 教材学习内容总结 本书的第一章简单的介绍了计算机和JAVA的历史,基础内容很多,代码的讲解还没用正式进入,本周一直在做敲代码的准备,简单敲了老师给的"Hello World!"以及书后的PP1.3.PP1.4等简单的小程序. 教材学习中的问题和解决过程 问题1:三种不同类型的错误,运行错误和逻辑错误的混淆 问题1解决方案:针对EX1.20的"希望做加法时却做里乘法"进行了网上

20172314 2017-2018-2 《程序设计与数据结构》第一周学习总结

20172314 2017-2018-2 <程序设计与数据结构>第一周学习总结 教材学习内容总结 本书第一章为计算机系统概述,前面是一些计算机相关的基础知识,让我对计算机有了一个总体的认识,不再是一片空白了,对主存储器和CPU影响深刻,主存储器用于保存程序和数据,CPU执行程序指令.在接下来的Java编程语言与程序开发部分,开始看的时候有点迷茫,不是很理解,后来先按照作业中附带的流程完成一些代码托管之后,接触了完整的简单的程序之后,再回过头来看书,看到的一些陌生的名词在脑海中就有了实际的对应,

20172317《程序设计与数据结构》第一周学习总结

20172317 2017-2018-2 <程序设计与数据结构>第一周学习总结 教材学习内容总结 重新温习了一遍计算机的基础 总算了解了局域网广域网因特网万维网这些东西之间的区别了 通过URL的例子知道了网址各个部分的含义 对Java编程语言和程序开发有了一个粗浅的了解 教材学习中的问题和解决过程 问题:练习题SR1.13出现了答案与题目不相符的情况 问题解决方案:题中有个选项是"网卡",答案中没有,反而有个题目没有的选项"调制解调器"(俗称"

20172322 2017-2018-2 《程序设计与数据结构》第二周学习总结

20172322 2017-2018-2 <程序设计与数据结构>第二周学习总结 教材学习内容总结 了解了print与println的区别 了解了字符串的拼接可以用+来完成 了解了转义字符的使用 学会了使用赋值 学会使用部分算术运算符 学会了使用Scanner来实现交互性 教材学习中的问题和解决过程 问题1:在最初接触赋值时对foalt和double的赋值范围不了解 问题1解决方案:使用万能的度娘后看到一个高赞答案后有了了解 问题2:在提前预习时看到2.7图形后敲入的代码无法执行 问题2解决方案

20172328《程序设计与数据结构》第二周学习总结

20172328李馨雨<程序设计与数据结构>第二周学习总结 又到周五,李馨雨同学要开始写博客了.让我们开始吧!(????) 教材学习内容总结 1.首先是String类定义的字符串,然后是print和println的区别.转义字符的学习.(让我印象深刻的\b[回车符]在字符串结尾不显示哈哈,及其更新的\t[换行符],还有在课堂上真正明白了什么是回车.) 2.了解变量.常量.赋值语句.变量:保存数据的内存单元.常量:坚定自己不会变的数据!除非你用java的反射(有点复杂,改权限.去修饰符等等.没实

20172327 2017-2018-2 《程序设计与数据结构》第二周学习总结

20172327 2017-2018-2 <程序设计与数据结构>第二周学习总结 教材学习内容总结 字符串的拼接和转义序列的使用 变量的声明和使用 讨论语法及表达式的处理 定义数据转换类型和实现类型转换的机制 创建Scanner类 教材学习中的问题和解决问题 暂无 代码学习中的问题和解决过程 问题1:在提交过程中,突然遇到无法上传的情况 问题2解决方案:通过上网查找,输入git pull之后弹出一个编辑框,选择关闭之后,再次用git push就成功了. 问题2:在按照例题2.10打代码时,Jav

学号20172328《程序设计与数据结构》第九周学习总结

学号20172328<程序设计与数据结构>第九周学习总结 教材学习内容总结(异常和递归) 第11章:异常 1.[异常处理]: 一个异常:是一个定义非正式情况或错误的对象,由程序或者运行时环境抛出,可以根据需要捕获和处理. 错误:错误类似于异常,但是错误代表不可恢复的问题并且必须捕获处理. 2.[处理异常的三种方法]:①根本不处理异常②当异常发生时处理异常③在程序的某个位置集中处理异常. 3.[未捕获的异常]:如果程序中不处理异常,则程序将非正常的终止运行,并产生关于描述在何处发生什么异常的信息

20172326 《程序设计与数据结构》第九周学习总结

学号 20172326 <程序设计与数据结构>第九周学习总结 教材学习内容总结 异常(exception):定义非正常情况下或错误的情况的对象,由程序或运行时环境抛出,可根据需要进行相应的捕获处理. 异常与错误的区别:错误代表不可恢复的问题并且必须捕获处理.而异常可以忽视,或者使用try语句处理,或调用更高级的方法. 可检测异常与不可检测异常:可检测异常必须由方法捕获,或者必须在可能抛出或传递异常方法的throws子句中列出来.在方法定义的声明头中追加一条throws子句.不可检测异常不需要使

20172322 2017-2018-2 《程序设计与数据结构》第九周学习总结

20172322 2017-2018-2 <程序设计与数据结构>第九周学习总结 教材学习内容总结 异常 学会了使用try-catch来实现未捕获的异常的处理.可以使得异常被捕获进而不导致程序出现错误退出.使用try写入可能产生异常的语句,使用catch来编写在捕获异常后继续执行的代码(可以为空). "未捕获的异常"指在编译过程中不会被编译软件显示异常但是在运行时会出现异常导致程序直接退出,例如:"ArithmeticException"."In