20172301 《程序设计与数据结构》第六周学习总结
教材学习内容总结
-
树的概述
- 树由一个包含结点和边的集构成。 树中结点和边的关系是:总边数 = 总结点数 - 1。
- 根结点:就是指位于该树顶层的唯一结点。一棵树只有一个根结点,根结点没有父节点。
- 子结点:一个树中较低层的结点是上一层结点的子结点。也叫作其孩子。
- 兄弟结点:同一双亲的两个结点。
- 叶结点:没有任何子结点的结点。
- 内部节点:一个至少有一个子结点的非根节点。
- 路径长度:通过计算从根到该结点所必须越过的边数目。
- 高度:从根到叶子之间的最远路径的长度。
- 度:树中任一结点可以具有的最大孩子的数目。
- 含有m个元素的平衡n元树具有的高度为lognm。
-
树的分类:
- 二叉树:结点最多具有两个孩子的树。
- 广义树:对结点所含有的孩子数目没有限制的树。
- 平衡树:粗略的来说,如果树的叶子都位于同一层或者至少彼此相差不超过一层,那么就称之为平衡的。
- 完全树:如果某树是平衡的,且底层所有叶子都位于树的左边,则认为该树是完全的。 完全二叉树在每个k层上都有2^k个结点,最后一层除外,在最后一层的结点必须是最左边的结点。
- 满树:如果一颗n元树的所有叶子都位于同一层且每一结点要么是一片叶子,要么是正好具有n个孩子。 满树一定是完全树。
-
树的数组实现
- 计算策略:对某些特定类型的树,特别是二叉树而言,二叉树的存储设从0开始,左子树为(2*n+1),右子树为(2*n+2). 但是,会浪费大量的存储空间,最好用于满树。
- 模拟链接策略:每一个结点存储的将是每一个孩子(可能还有双亲)的数组索引,而不是指向其孩子(可能还有双亲)的指针对象的引用变量,但是该方式增加了删除树中元素的成本。 模拟链接策略允许连续分配数组位置而不用考虑树的完全性。
-
树的遍历:
-
- 前序遍历:从根结点开始,访问每一个结点及其孩子。(A->B->D->E->C)
Visit node Traverse(left child) Traverse(right child)
- 中序遍历:从根结点开始,访问结点的左侧孩子,然后是该结点,再然后是任何剩余的结点。(D->B->E->A->C)
Traverse(left child) Visit node Traverse(right child)
- 后序遍历:从根结点开始,访问结点的孩子,然后是该结点。(D->E->B->C->A)
Traverse(left child) Traverse(right child) Visit node
- 层序遍历:从根节点开始,访问每一层的所有结点,一次一层。(A->B->C->D->E)
-
二叉树性质:
- 性质1:在二叉树的第i层上至多有2^(i-1)个结点(i>=1)。
- 性质2:深度为k的二叉树至多有2^k-1个结点(k>=1)。
- 性质3:对任何一颗二叉树,如果其叶子结点数为n0,度为2的 结点 数为n2,则n0 = n2+1.
-
完全二叉树性质:
- 性质1:具有n个结点的完全二叉树深度为[log2n]+1 ([x]表示不 大于 x的最大整数)。
- 性质2:如果对一颗有n个结点的完全二叉树(其深度为[log2n]+1) 的结点按层序编号(从第1层到第[log2n]+1层,每层从左到右),对任意一个结点i(1<=i<=n)有:
1).如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结 点[i/2]
2).如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩 子是结点2i。
3).如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。
教材学习中的问题和解决过程
- 问题1:书P189 树的数组实现中模拟链接策略里提到了两个名词
基准地址
和偏移量
。应该是与文件存储读取时相关的有关名词。 - 问题1解决方案:
-
网上并没有查到合适的有关基准地址的定义。
-
偏移量:计算机汇编语言中的偏移量定义为把存储单元的实际地址与其所在段的段地址之间的距离称为段内偏移,也称为“有效地址或偏移量”。
-
如图,段地址左移四位,与 有效地址相加,就构成了逻辑地址。一般而言,段地址是cpu自己独立编制的,但是偏移量是程序员编写的。偏移量就是程序的逻辑地址与段首的差值。
-
没有理解,后期学习深入探究。
-
- 问题2:书P201
termType
作用? - 问题2解决方案:在表达式树中,判定是否是操作符运用的方法是判断
termType
变量是否为1。- 我知道
termType
变量是人为设定的。记得当时我们实现四则运算的时候,判断是否是操作符的条件是a.equals("+") || a.equals("-") || a.equals("*") || a.equals("÷")
,我不觉得如果自己需要新建对象再确定termType变量为1是否更加简便。
- 我知道
- 问题3:书P200
printTree
代码如何理解? - 问题3解决方案:我们总是一看到很长的代码,就会没有阅读它的欲望。就像长篇的英语阅读一样。但是,无论它多长,它都是有规律可循的。需要我们耐下性子,静下心思去推敲,清楚每一个变量的作用,清楚每一个循环的跳出条件。抽丝剥茧, 把代码分成部分。
- 我们首先要清楚每一个变量的意义。nodes是存储树中各个结点所指向的元素,possibleNodes是最大结点数+1,levelList是存储各个元素它所对应的层数位置,currentLevel和previousLevel共同在决定着是否该换行 ,即是否该跳至下一行。
如果你清楚二叉树的性质,你就会清楚这是最大的结点数加1。int possibleNodes = (int)Math.pow(2, printDepth + 1);
- 首先,判断第0层,从根节点开始,不断循环,从左到右,依次输出空格和树中的元素。
- 具体的循环过程,我看了侯泽洋同学的博客,写的非常详细,我便不再过多赘述。传送门
代码调试中的问题和解决过程
- 问题1:背部疼痛诊断器找不到文件。
- 问题1解决方案:
- 这是一个小错误,本来我还以为文件名字和文件添加位置不对。实际上,虽然我把txt文件和诊断器程序放在了一个文件夹里面,但是他还是不能直接通过名字进行查找。
- 小组成员段志轩同学告诉我,用绝对地址就可以确保可以查找到文件。
- 我走进了思维误区,以为txt文件的存放方式不对,而并没有考虑地址的问题。
- 问题2:小组成员段志轩同学问我
heght(BinaryTreeNode<T> node)
方法中的递归是如何实现。因为if条件是node == null
,那么到最后是不是返回的值是0?
// 返回这棵树的高度。
public int getHeight()
{
return height(root);
}
// 返回指定节点的高度。
private int height(BinaryTreeNode<T> node)
{
int left;
int right;
if (node == null)
{
return 0;
}
else
{
left = height(node.getLeft());
right = height(node.getRight());
if (left < right)
return right + 1;
else
return left + 1;
}
}
-
问题2解决方案:
- 我特意做了一遍调试。
- 这个方法是从根节点开始调用递归,然后一直找左子树,直到左子树为null。
- 可以观察到这里
left
已经为null
了,那么下一个判断它将返回0。
- 果然,
left
为0,这时候开始递归right
。
- 一直到左右都为0的时候,也就是没有左右孩子的时候,这时候我的返回值是
right + 1
,所以不会存在高度输出为0的结果。所以是正确的。手动@段志轩同学。
-
问题3:背痛诊断器抛出异常
Exception in thread "main" java.lang.ClassCastException: week6.BinaryTreeNode cannot be cast to java.base/java.lang.String
无法转换成String类型。DecisionTree
类中evaluate()
方法中的System.out.println (current.getRootElement());
报错。对于LinkedBinaryTree
中的getRight()
和getLeft()
方法,我一开始写了两种实现方法。但是在运行背痛诊断器的时候,有一种出现了类似无限递归的情况。
- 问题3解决方案:
- 首先,我从根上查找错误,观察指针类的
getRootElement()
操作,运用的是泛型,应该是可以转为String型输出的。 - 然后,考虑背痛诊断器的测试类和
DecisionTree
类有无问题。因为是书上代码,所以我又和书上的代码校对了一遍,代码没有错误。 - 开始代码调试,
DecisionTree expert = new DecisionTree("D:\IdeaProjects\GK20172301_JavaProgramming\src\week6\input.txt");
操作应该是没有问题的。
- 最后发现了问题出在返回树的字根上面。
- 我当时写了两个关于返回字根的方法,但是最后发现都是有错误的。
- 首先是返回右边子树的这个,陷入了无限递归的情况。本来我是想仿照,构造二叉树的第三个方法,参数是元素,左子树,右子树。
- 而返回左子树的错误问题就是不能转换。这里我们不应该new 一个新的二叉树出来。这样就和定义的根没有了关系,所以就无法调用到子树,也就是一个没有孩子的新的对象。程序也就会终止。
- 所以,最后的修改结果应该是定义两个全局变量,
left
和right
,同时在构造方法里定义this.left = left;
,这样,在getLeft()
中就可以直接调用left
了。
- 首先,我从根上查找错误,观察指针类的
代码托管
上周考试错题总结
上周无错题,优秀!
结对及互评
点评过的同学博客和代码
- 上周博客互评情况
其他
这周的代码很复杂,不再是单单的一个类几个方法。对于代码的理解和实现上面还是有所不足的。对于背痛代码的错误,经过两三个小时的调试也没有找到根本原因。
对于上学期的迭代和递归知识也有所忘记,应该及时温习。对于代码要保持高度的紧张。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/1 | 10/10 | |
第二周 | 610/610 | 1/2 | 20/30 | |
第三周 | 593/1230 | 1/3 | 18/48 | |
第四周 | 2011/3241 | 2/5 | 30/78 | |
第五周 | 956/4197 | 1/6 | 22/100 | |
第六周 | 2294/6491 | 2/6 | 20/120 |