课程:《Java软件结构与数据结构》
班级: 1723
姓名: 郭恺
学号:20172301
实验教师:王志强老师
实验日期:2018年11月20日
必修/选修: 必修html
LinkedBinaryTree
由于是以前的程序项目,因此实现起来很容易。java
getRight()
方法,首先在LinkedBinaryTree
类里面声明一个全局变量protected LinkedBinaryTree<T> left,right;
而后在构造函数里,添加下面两行代码。node
// 建立以指定元素为根元素的二叉树,把树做为它的左子树和右子树 public LinkedBinaryTree(T element, LinkedBinaryTree<T> left, LinkedBinaryTree<T> right) { root = new BinaryTreeNode<T>(element); root.setLeft(left.root); root.setRight(right.root); this.left = left; this.right = right; }
而后直接返回right
便可。编程
// 返回此树的根的右子树。 public LinkedBinaryTree<T> getRight() { return right; }
contains
方法基于私有方法findAgain
实现。只须要判断在树里可否找到目标元素便可。public boolean contains(T targetElement) { return findAgain(targetElement, root) != null; }
toString
方法,这里为了让输出是一个树型,我用了以前ExpressionTree
的printTree
方法。一样,toString
方法能够考虑使用四种遍历方式。preorder
方法和postorder
方法,实现理念和书上给的中序遍历同样。只须要调整一下左右孩子还有结点的顺序。// 执行递归先序遍历。 protected void preOrder(BinaryTreeNode<T> node, ArrayUnorderedList<T> tempList) { if (node != null) { tempList.addToRear(node.getElement()); preOrder(node.getLeft(), tempList); preOrder(node.getRight(), tempList); } }
// 执行递归后序遍历。 protected void postOrder(BinaryTreeNode<T> node, ArrayUnorderedList<T> tempList) { if (node != null) { postOrder(node.getLeft(), tempList); postOrder(node.getRight(), tempList); tempList.addToRear(node.getElement()); } }
HDIBEMJNAFCKGL
和先序ABDHIEJMNCFGKL
。A
是树的根结点。那么根据中序得知A
的左边是左子树,A
的右边是右子树。B
便是左子树的根,根据中序得知B
的左边是左子树的左孩子,B
的右边是左子树的右孩子。A
为根的树。A
为根结点和递归实现的左右子树,建立一个新树。ExpresstionTree
是关于后缀表达式计算的一个树。findMin
和findMax
只要分别查找最左结点和最右结点便可。在看源代码以前,我以为有必要学习一下如何去系统的看程序源代码。这里作一些摘录:
第一,找准入口出口,不要直接跳进去看,任何代码都有触发点,不管是http request,仍是服务器自动启动,仍是main函数,仍是其余的,先从入口开始。
第二,手边一支笔一张纸,除非你是Jeff,不然你不会记得那么多跳转的。一个跳转就写下来函数/方法名和参数,读完一遍,就有了一个sequence diagram雏形 。
第三,私有方法掠过,只要记住输入输出便可,无需看具体实现数组
红黑树遵循如下五点性质:
性质1 结点是红色或黑色。
性质2 根结点是黑色。
性质3 每一个叶子结点(NIL结点,空结点)是黑色的。
性质4 每一个红色结点的两个子结点都是黑色。(从每一个叶子到根的全部路径上不能有两个连续的红色结点)
性质5 从任一结点到其每一个叶子结点的全部路径都包含相同数目的黑色结点。服务器
TreeMap
声明public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable {
public TreeMap() { comparator = null; }
无参构造方法,不指定比较器,排序的实现要依赖key.compareTo()
方法,所以key
必须实现Comparable
接口,并覆写其中的compareTo
方法。
public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; }
采用带比较器的构造方法,排序依赖该比较器,key
能够不用实现Comparable
接口。
public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); }
构造方法一样不指定比较器,调用putAll
方法将Map
中的全部元素加入到TreeMap
中。
public TreeMap(SortedMap<K, ? extends V> m) { comparator = m.comparator(); try { buildFromSorted(m.size(), m.entrySet().iterator(), null, null); } catch (java.io.IOException | ClassNotFoundException cannotHappen) { } }
将比较器指定为m的比较器,然后调用buildFromSorted方法,将SortedMap中的元素插入到TreeMap中,根据SortedMap建立的TreeMap,将SortedMap中对应的元素添加到TreeMap中。数据结构
public V put(K key, V value) { //获得红黑树根结点 Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check // 若是树为空,新建红黑树根结点 root = new Entry<>(key, value, null); size = 1; modCount++; return null; } //若是Map不为空,找到插入新节点的父节点 int cmp; Entry<K,V> parent; Comparator<? super K> cpr = comparator; // 若是比较器不为空 if (cpr != null) { do { // 使用 parent 上次循环后的 t 所引用的 Entry parent = t; // 拿新插入的key和t的key进行比较 cmp = cpr.compare(key, t.key); // 若是新插入的key小于t的key,t等于t的左结点 if (cmp < 0) t = t.left; // 若是新插入的key大于t的key,t等于t的右结点 else if (cmp > 0) t = t.right; else // 若是两个key相等,新value覆盖原有的value,并返回原有的value return t.setValue(value); } while (t != null); } // 没有提供比较器 else { if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } // 将新插入的结点做为parent结点的子结点 Entry<K,V> e = new Entry<>(key, value, parent); // 做为左孩子 if (cmp < 0) parent.left = e; // 做为右孩子 else parent.right = e; // 修复红黑树 fixAfterInsertion(e); size++; modCount++; return null; }
HashMap
类的声明,能够了解他继承了什么类和实现了哪些接口。public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
<K,V>应该表示的是一种映射关系。app
HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。
容量是哈希表中桶的数量,初始容量只是哈希表在建立时的容量。
加载因子是哈希表在其容量自动增长以前能够达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操做(即重建内部数据结构),从而哈希表将具备大约两倍的桶数。函数
这里有两个比较重要的变量,容量和加载因子。容量的值是2的n次幂,加载因子默认为0.75。
源码分析
这里加载因子为何默认是0.75呢?
一般,默认加载因子 (0.75) 在时间和空间成本上寻求一种折衷。加载因子太高虽然减小了空间开销,但同时也增长了查询成本(在大多数 HashMap 类的操做中,包括 get 和 put 操做,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减小 rehash 操做次数。
若是初始容量大于最大条目数除以加载因子,则不会发生 rehash 操做。
这里的rehash操做,我以为相似于数组的扩容。
加载因子就是表示数组链表填满的程度。
加载因子越大,填满的元素越多,空间利用率高了,但冲突的机会加大了.链表长度会愈来愈长,查找效率下降。
加载因子越小,填满的元素越少,冲突的机会减少了,但空间浪费多了.表中的数据将过于稀疏,不少空间还没用,就开始扩容了。
// 初始容量(必须是2的n次幂),负载因子 public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); }
HashMap
使用了数组,链表和红黑树,多种实现。static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; // 哈希值 this.key = key; // 键 this.value = value; // 值 this.next = next; // 下一个 } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } // 判断两个Node是否相等 // 若两个Node的“key”和“value”都相等,则返回true。 // 不然,返回false public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
next
,是用来处理冲突的。HashMap
原本就是一个数组,若是数组中某一个索引起生了冲突,那么就会造成链表。而链表到必定程度的时候,就会造成红黑树。
put
操做public V put(K key, V value) { 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; if ((tab = table) == null || (n = tab.length) == 0) // resize()方法是从新调整HashMap的大小 n = (tab = resize()).length; // 若不为null,计算该key的哈希值,而后将其添加到该哈希值对应的链表中 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { 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; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
问题1:实验二基于(中序,先序)序列构造惟一一棵二㕚树的功能,出现了ArrayIndexOutOfBoundsException
异常,简单来讲就是数组为空。
如图
根据调试发现,在新建了子树的中序和先序数组以后,我并无把原来数组的值导入到新的数组当中。因此致使运行时抛出了数组空的异常。
如图
for (int y = 0; y < in.length; y++) { // 把原数组的数存入新的数组当中 if (y < x) { inLeft[y] = in[y]; preLeft[y] = pre[y + 1]; } else if (y > x) { inRight[y - x - 1] = in[y]; preRight[y - x - 1] = pre[y]; } }
问题2:实验四树的输出格式不对,从而致使后缀表达式输出格式不正确。
如图
一样是通过调试发现,在符号入栈的时候,转化为LinkedBinaryTree
类型存储的时候,并以LinkedBinaryTree
类型为根结点的时候,会根据其toString
方法,变成,\n\n + \n\n \n\n
,因此输出树的时候前面会多出不少行。
如图
个人解决办法是,把操做数栈改为String
类型的,以String
类型的符号做为根结点,这样,结果能够正常输出。
如图
问题3: 实验四中缀转后缀的括号问题。
// 处理括号 if (tempToken.equals("(")) { op.push(tempToken); tempToken = stringTokenizer.nextToken(); while (!tempToken.equals(")")) { if (tempToken.equals("+") || tempToken.equals("-")) { if (!op.isEmpty()) { // 栈不空,判断“(” if (op.peek().equals("(")) op.push(tempToken); else { String b = op.pop(); operand1 = getOperand(expre); operand2 = getOperand(expre); LinkedBinaryTree a = new LinkedBinaryTree(b, operand2, operand1); expre.push(a); op.push(tempToken); } } else { // 栈为空,运算符入栈 op.push(tempToken); } } else if (tempToken.equals("*") || tempToken.equals("/")) { if (!op.isEmpty()) { if (op.peek().equals("*") || op.peek().equals("/")) { String b = op.pop(); operand1 = getOperand(expre); operand2 = getOperand(expre); LinkedBinaryTree a = new LinkedBinaryTree(b, operand2, operand1); expre.push(a); op.push(tempToken); } else { op.push(tempToken); } } } else { // 操做数入栈 LinkedBinaryTree a = new LinkedBinaryTree(tempToken); expre.push(a); } tempToken = stringTokenizer.nextToken(); } while (true) { String b = op.pop(); if (!b.equals("(")) { operand1 = getOperand(expre); operand2 = getOperand(expre); LinkedBinaryTree a = new LinkedBinaryTree(b, operand2, operand1); expre.push(a); } else { // 终止循环 break; } } }