20172302 《Java软件结构与数据结构》实验二:树实验报告

课程:《Java软件结构与数据结构》

班级: 1723

姓名: 侯泽洋

学号:20172302

实验教师:王志强老师

实验日期:2018年11月5日

必修/选修: 必修

实验内容

  • (1)参考教材p212,完成链树LinkedBinaryTree的实现(getRight,contains,toString,preorder,postorder;用JUnit或自己编写驱动类对自己实现的LinkedBinaryTree进行测试,提交测试代码运行截图,要全屏,包含自己的学号信息
  • (2)基于LinkedBinaryTree,实现基于(中序,先序)序列构造唯一一棵二?树的功能,比如给出中序HDIBEMJNAFCKGL和先序ABDHIEJMNCFGKL,构造出附图中的树;用JUnit或自己编写驱动类对自己实现的功能进行测试,提交测试代码运行截图,要全屏,包含自己的学号信息
  • (3)自己设计并实现一颗决策树;提交测试代码运行截图,要全屏,包含自己的学号信息
  • (4)输入中缀表达式,使用树将中缀表达式转换为后缀表达式,并输出后缀表达式和计算结果;提交测试代码运行截图,要全屏,包含自己的学号信息
  • (5)完成PP11.3;提交测试代码运行截图,要全屏,包含自己的学号信息
  • (6)参考http://www.cnblogs.com/rocedu/p/7483915.html对Java中的红黑树(TreeMap,HashMap)进行源码分析,并在实验报告中体现分析结果。(C:\Program Files\Java\jdk-11.0.1\lib\src\java.base\java\util)

实验过程及结果

(1)实验一

完成链树LinkedBinaryTree的实现(getRight,contains,toString,preorder,postorder)
这里的方法编写在学习树时都有写过,所以直接编写了测试类,实验结果如图

(2)实验二

基于LinkedBinaryTree,实现基于(中序,先序)序列构造唯一一棵二?树的功能,这个需要新建类,类中写了公有方法generate0,generate0再去调用私有方法generate,
这个主要是利用传进来的先序和中序的字符串,确定根结点,然后再确定其左右孩子,接下来递归该过程,直至将该字符串读取完成。
实验结果截图:

(3)实验三

自己设计并实现一颗决策树,设计了一棵决策树去确定1至6之间的某个数。

11
Is the number greater than 3?
Is the number greater than 2?
Is the number greater than 4?
Is the number greater than 1?
Is the number greater than 5?
The number is 1.
The number is 2.
The number is 3.
The number is 4.
The number is 5.
The number is 6.
3 5 6
1 3 7
4 9 10
2 8 4
0 1 2

实验结果见图:

(4)实验四

输入中缀表达式,使用树将中缀表达式转换为后缀表达式,这里是使用两个栈,一个是操作符栈,另一个是操作数栈,其中操作数栈是操作数是以树的类型进行存储的。实验结果见图:

(5)实验五

完成PP11.3;11.3在之前已经做过,测试了一次完成。

(6)实验六

看了一下TreeMap和Hashmap的源代码,一个3000多行,一个2400多行,放弃了,太多了。于是从网上找资料看了一些个的源码分析,这里写一些。
1.继承结构
下面是HashMap与TreeMap的继承结构:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
可以看出它们都继承了AbstractMap。而HashMap是直接实现的Map,TreeMap实现的是NavigableMap(Cloneable和Serializable忽略)。
2.TreeMap
TreeMap是NavbagableMap的实现,底层基于红黑树。这个Map按照Comparable将键值排序,或者按照在创建Map时提供的Compartor。
TreeMap的底层是基于红黑树的实现,所以像get、put、remove、containsKey这些方法都会花费log(n)的时间复杂度。这儿不会着重于红黑树的具体实现以及转换,只要知道TreeMap的基本思路就可以了。
(1)put操作

    public V put(K key, V value) {
        Entry<K,V> t = root;
        // 1.如果根节点为 null,将新节点设为根节点
        if (t == null) {
            compare(key, key);
            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            // 2.为 key 在红黑树找到合适的位置
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        } else {
            // 与上面代码逻辑类似,省略
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        // 3.将新节点链入红黑树中
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        // 4.插入新节点可能会破坏红黑树性质,这里修正一下
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

从put方法可以看到,有几步流程:

  1. 如果Map为空,那么直接将新插入的值作为根结点。此时,如果提供了Compartor,就得看Compartor是否支持null键值;如果没有提供Compartor,那么将会抛出NullPointerException。
  2. 如果Map不为空,那么需要找到新插入的键值的父节点。在查找过程中,如果遇到了键值相等的,那么将会调用Entry.setValue()更新值。
  3. 一旦找到了父节点,那么插入新节点,尺寸+1
    (2)get操作
    public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }
    final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        // 查找操作的核心逻辑就在这个 while 循环里
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }

从上面可以看到,get()方法的流程:

  1. 如果提供了Comparator,那么使用getEntryUsingComparator()方法
  2. 如果没有提供Comparator,并且键为null,抛出NullPointerException
  3. 如果没有提供Comparator且键不为null,将键强制转换为Comparable接口,如果键没有实现,那么抛出ClassCastExceotion
  4. 如果没有提供Comparator且键不为null,且键实现了Comparable接口,那么从根结点开始遍历红黑树,一旦找到则返回节点,否则返回null
    (3)remove操作

3.HashMap
HashMap是基于Hash table实现的Map,它实现了Map中所有的可选的操作,并且允许key或value为null,近似的等价于Hashtable(除了HashMap是非同步并且允许null值);它不保证元素的顺序;如果插入的元素被Hash函数正确的分散在不同的桶(槽,bucket)中,get和put操作都只需要常量时间。
(1)put操作

public V put(K key, V value) {
        //传入key的hash值,对hashCode值做位运算
        return putVal(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //如果tab为null,则通过resize初始化
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //计算key的索引,如果为当前位置为null,直接赋值
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            //如果当前位置不为null
            Node<K,V> e; K k;
            //如果相同直接覆盖
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //如果是红黑树节点,添加节点到红黑树,如果过程中发现相同节点则覆盖
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //如果是链表节点
            else {
                for (int binCount = 0; ; ++binCount) {
                    //如果
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //找到相同节点则覆盖
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //覆盖
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //结构变化次数+1
        ++modCount;
        //如果size超过最大限制,扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

put()操作的主要是如下几个步骤:
首先判断Node[]数组table是否为空或null,如果是空那么进行一次resize,这次resize只是起到了一次初始化的作用。
根据key的值计算hash得到在table中的索引i,如果table[i]==null则添加新节点到table[i],然后判断size是否超过了容量限制threshold,如果超过进行扩容。
如果在上一步table[i]不为null时,判断table[i]节点是否和当前添加节点相同(这里使用hash和equals判断,因此需要保证hashCode()方法和equals()方法描述的一致性),如果相同则覆盖该节点的value。
如果上一步判断table[i]和当前节点不同,那么判断table[i]是否为红黑树节点,如果是红黑树节点则在红黑树中添加此key-value。
如果上一步判断table[i]不是红黑树节点则遍历table[i]链表,判断链表长度是否超过8,如果超过则转为红黑树存储,如果没有超过则在链表中插入此key-value。(jdk1.8以前使用头插法插入)。在遍历过程中,如果发现有相同的节点(比较hash和equals)就覆盖value。
维护modCount和size等其他字段。
(2)get操作

public V get(Object key) {
        Node<K,V> e;
        //传入key的hash
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //这里访问(n - 1) & hash其实就是jdk1.7中indexFor方法的作用
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            //判断桶索引位置的节点是不是相同(通过hash和equals判断),如果相同返回此节点
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                //判断是否是红黑树节点,如果是查找红黑树
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    //如果是链表,遍历链表
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        //如果不存在返回null
        return null;
    }

实验过程中遇到的问题和解决过程

  • 问题1:做实验2时我的树打印的始终是不完整的,只有A—J这些个元素,后面的元素就是消失了。
  • 问题1解决方案:通过Debug,第二次时发现了问题,原来是我substring方法用的有问题,查看API中substring方法的具体介绍:

    ? ? ? ?它的参数是包括起始索引,但不包括终止索引,而我在编写过程当中是默认了它是起始索引和终止索引都包括在内,这就会导致遗漏了一部分的元素,因此出现了这个问题,在原来的基础上把终止索引加1后即可解决该问题。
  • 问题2:实验4,实验4想了好久都没有一点思路,我不清楚怎么去使用树,在哪使用树,树是用来存储什么的?
  • 问题2解决方案:我的想法一开始是落在把操作符所有的全部存在一棵树上,根据它的优先级去存储,写了之后才发现想的不对,因为这样我没有办法再去把他们按照正确顺序取出来。后面问了郭恺,他跟我说的是应该建立两个栈,两个栈中存储的数据类型分别是String和树类型,Sring类型的是操作符,树类型的是操作数,他提供的这个思路解决了我的问题。终于把这个问题弄明白了,于是开始重新编写。下面是关于操作符的优先级处理:
if (isOperator(m)) {

                if (m.equals("*") || m.equals("/"))
                    stack.push(m);
                else if (stack.empty())
                    stack.push(m);
                else {
                        while (!stack.isEmpty()) {
                            String s1 = String.valueOf(stack.pop());
                            LinkedBinaryTree operand3 = linkedBinaryTreeStack.pop();
                            LinkedBinaryTree operand4 = linkedBinaryTreeStack.pop();
                            LinkedBinaryTree<String> linkedBinaryTree1 =
                            new LinkedBinaryTree<String>(s1, operand4, operand3);
                            linkedBinaryTreeStack.push(linkedBinaryTree1);
                            if (stack.isEmpty())
                                break;
                    }
                    stack.push(m);
                }
            }

? ? ? ?如果它是乘或除可以直接入栈,而当它是加或减时,需要把栈里的比它优先级高的取出来,取出来的时候需要同时从数栈里取出两个操作数,构成一棵新的树,再放入树的栈中,循环直至栈中没有元素,然后再把该操作符放入栈中,这样就可以实现了。

其他(感悟、思考等)

  • 本次实验做的过程中让我感觉最难的就是实验4,自己一开始怎么想都是没有思路,在郭恺(非常感谢)给我的思路下,终于算是把这个实验做完了。HashMap和TreeMap源码分析不动啊,太多了。

参考资料

原文地址:https://www.cnblogs.com/hzy0628/p/9932266.html

时间: 2024-10-27 05:49:08

20172302 《Java软件结构与数据结构》实验二:树实验报告的相关文章

2018-2019-20172329 《Java软件结构与数据结构》第五周学习总结

2018-2019-20172329 <Java软件结构与数据结构>第五周学习总结 教材学习内容总结 <Java软件结构与数据结构>第九章-排序与查找 一.查找 1.查找概念简述: (1)查找3是这样一个过程,即在某个项目组中寻找某一指定目标元素,或者确定该组中并不存在该目标元素.对其进行查找的项目组有时也称为查找组. (2)查找方式的分类:线性查找,二分查找. (3)查找所要完成的目标:尽可能高效的完成查找,从算法分析的角度而言,我们希望最小化比较操作的次数,通常,查找池里项目数

20172328 2018-2019《Java软件结构与数据结构》第六周学习总结

20172328 2018-2019<Java软件结构与数据结构>第六周学习总结 概述 Generalization 本周学习了第十章:非线性集合与数据结构--树.主要讨论了树的使用和实现,以及考察实现和使用树的实例. 教材学习内容总结 A summary of textbook 树(tree):树是一种非线性结构,其元素被组织成了一个层次结构.下面是树的术语,了解一下吧! 树有一个包含结点(node)和边(edge)的集构成,其中的元素被储存在这些结点中,边则将一个结点和另一个结点连接起来.

20172328 2018-2019《Java软件结构与数据结构》第七周学习总结

20172328 2018-2019<Java软件结构与数据结构>第七周学习总结 概述 Generalization 本周学习了第11章:二叉查找树.在本章中,主要探讨了二叉查找树的概念和各种二叉查找树实现,考察为二叉查找树添加和删除元素的算法以及维护平衡二叉查找树的算法 教材学习内容总结 A summary of textbook 二叉查找树(binary search tree):二叉树定义的扩展,一种带有附加属性的二叉树.附加属性是什么?树中的每个节点,其左孩子都要小于其父节点,而父节点

2018-2019-20172329 《Java软件结构与数据结构》第七周学习总结

2018-2019-20172329 <Java软件结构与数据结构>第七周学习总结 教材学习内容总结 <Java软件结构与数据结构>第十一章-二叉查找树 一.概述 1.什么是二叉查找树:二叉查找树是一种带有附加属性的二叉树,即对树中的每个结点,其左孩子都要小于其父结点,而父结点又小于或等于其右孩子. 2.二叉查找树的定义是二叉树定义的扩展. 3.操作: 操作 描述 addElement 往树中添加一个元素 removeElement 从书中删除一个元素素 removeAllOccu

2018-2019-20172321 《Java软件结构与数据结构》第七周学习总结

2018-2019-20172321 <Java软件结构与数据结构>第七周学习总结 教材学习内容总结 第11章 二叉查找树 一.概述 二叉查找树是一种含有附加属性的二叉树,该属性即其左孩子小于父节点,而父节点又小于等于其右孩子.如下图所示.根结点的左子树每个元素都小于80,右子树每个元素都大于80. 查找方法,要判定一个具体的目标是否存在于树中,需要沿着从根开始的路径,根据查找目标是小于还是大于当前结点的值,相应地转到当前结点的左子结点或右结点.最终或是找到目标元素,或是遇到路径的末端,后者意

2018-2019-20172329 《Java软件结构与数据结构》第八周学习总结

2018-2019-20172329 <Java软件结构与数据结构>第八周学习总结 现在对于我而言,最珍贵的是时间,感觉自己在时间飞逝的时候真的挽留不住什么,只能怒发冲冠的让自己疯狂的学习啦,新的一周要加油!?? 教材学习内容总结 <Java软件结构与数据结构>第十二章-优先队列与堆 一.概述 1.什么是堆: 堆就是具有两个附加属性的一个二叉树. 堆分为小顶堆和大顶堆. 两个附加属性如下: (1)它是一颗完全树.(完全树:如果某树是平衡的,且底层所有叶子都位于树的左边,则认为该树是

20172305 2018-2019-1 《Java软件结构与数据结构》第一周学习总结

20172305 2018-2019-1 <Java软件结构与数据结构>第一周学习总结 教材学习内容总结 本周内容主要为书第一章和第二章的内容: 第一章 软件质量: 正确性(软件达到特定需求的程度) 可靠性(软件发生故障的频率和危害程度) 健壮性(很好处理错误情况的情况) 可用性(用户在软件中掌握和运行任务的容易程度) 可维护性(软件进行修改的容易程度) 可重用性(软件组件可被其他软件系统开发使用的容易程度) 可移植性(软件组成可用于多种计算机环境中的容易程度) 运行效率(不浪费资源的情况下软

2018-2019-20172329 《Java软件结构与数据结构》第三周学习总结

2018-2019-20172329 <Java软件结构与数据结构>第三周学习总结 教材学习内容总结 <Java软件结构与数据结构>第五章-队列 一.概述 1.队列是什么? 队列是种线性集合,其元素从一端加入,从另一端删除:注:队列是按照先进先出的方式处理的.从队列中删除元素的次序,与放置元素的次序是一样的. 2.队列的构成 (1)方法: 操作 描述 enqueue 向队列末端添加一个元素 dequeue 从队列前段删除一个元素 first 考察队列前端的那个元素 isempty

20172328 2018-2019《Java软件结构与数据结构》第八周学习总结

20172328 2018-2019<Java软件结构与数据结构>第八周学习总结 概述 Generalization 本周学习了二叉树的另一种有序扩展?是什么呢?你猜对了!ヾ(?°?°?)??就是堆.本章将讲解堆的链表实现and数组实现,以及往堆中添加元素或从堆中删除元素的算法:还将介绍对的一些用途,包括基本使用和优先队列. 教材学习内容总结 A summary of textbook 堆(heap)就是具有两个附加属性的一颗二叉树: 第一点:它是一颗完全二叉树 ,即叶子节点都在最后一层靠左侧