本次实验主要是关于树的应用, 涉及了二叉树、决策树、表达式树、二叉查找树、红黑树五种树的类型,是对最近学习内容第十章和第十一章的一个总结。html
getRight
操做用于返回根的右子树。当树为空时,抛出错误,当树不为空时,经过递归返回根的右子树。public LinkedBinaryTree2<T> getRight() { if(root == null) { throw new EmptyCollectionException("BinaryTree"); } LinkedBinaryTree2<T> result = new LinkedBinaryTree2<>(); result.root = root.getRight(); return result; }
contains
操做的实现有两种方法:一种是直接借用find
方法,另外一种是从新写一个。
find
方法,find
方法的做用是在二叉树中找到指定目标元素,则返回对该元素的引用,因此当该元素的引用与查找的元素相同时返回true,不然返回false。public boolean contains(T targetElement) { if (find(targetElement) == targetElement){return true;} else {return false;} }
public boolean contains(T targetElement) { BinaryTreeNode node = root; BinaryTreeNode temp = root; //找到的状况有三种:查找元素就是根,查找元素位于右子树,查找元素位于左子树。 //除了这三种状况下其他状况都找不到元素,所以初始设置为false boolean result = false; //当树为空时,返回false if (node == null){ result = false; } //当查找元素就是根时,返回true if (node.getElement().equals(targetElement)){ result = true; } //对右子树进行遍历(在右子树不为空的状况下)找到元素则返回true,不然对根的左子树进行遍历 while (node.right != null){ if (node.right.getElement().equals(targetElement)){ result = true; break; } else { node = node.right; } } //对根的左子树进行遍历,找到元素则返回true,不然返回false while (temp.left.getElement().equals(targetElement)){ if (temp.left.getElement().equals(targetElement)){ result = true; break; } else { temp = temp.left; } } return result; }
toString
方法我借用了ExpressionTree类
中的PrintTree
方法,具体内容曾在第七周博客中说过。preorder
方法因为有inOrder
方法的参考因此挺好写的,修改一下三条代码(三条代码分别代码访问根、访问右孩子和访问左孩子)的顺序便可,使用了递归。在输出时为了方便输出我从新写了一个ArrayUnorderedList类
的公有方法,直接输出列表,要比用迭代器输出方便一些。public ArrayUnorderedList preOrder(){ ArrayUnorderedList<T> tempList = new ArrayUnorderedList<T>(); preOrder(root,tempList); return tempList; } protected void preOrder(BinaryTreeNode<T> node, ArrayUnorderedList<T> tempList) { if (node != null){ //从根节点开始,先访问左孩子,再访问右孩子 tempList.addToRear(node.getElement()); preOrder(node.getLeft(),tempList); preOrder(node.getRight(),tempList); } }
postOrder
方法与preorder
方法相似,惟一的区别是后序遍历先访问左孩子,再访问右孩子,最后访问根结点,代码和上面差很少就不放了。public void initTree(String[] preOrder,String[] inOrder){ BinaryTreeNode temp = initTree(preOrder,0,preOrder.length-1,inOrder,0,inOrder.length-1); root = temp; } private BinaryTreeNode initTree(String[] preOrder,int prefirst,int prelast,String[] inOrder,int infirst,int inlast){ if(prefirst > prelast || infirst > inlast){ return null; } String rootData = preOrder[prefirst]; BinaryTreeNode head = new BinaryTreeNode(rootData); //找到根结点 int rootIndex = findroot(inOrder,rootData,infirst,inlast); //构建左子树 BinaryTreeNode left = initTree(preOrder,prefirst + 1,prefirst + rootIndex - infirst,inOrder,infirst,rootIndex-1); //构建右子树 BinaryTreeNode right = initTree(preOrder,prefirst + rootIndex - infirst + 1,prelast,inOrder,rootIndex+1,inlast); head.left = left; head.right = right; return head; } //寻找根结点在中序遍历数组中的位置 public int findroot(String[] a, String x, int first, int last){ for(int i = first;i<=last; i++){ if(a[i] == x){ return i; } } return -1; }
DecisionTree
类的实现。
DecisionTree
的构造函数从文件中读取字符串元素。存储在树结点中。而后建立新的结点,将以前定义的结点(或子树)做为内部结点的子结点。public DecisionTTree(String filename) throws FileNotFoundException { //读取字符串 File inputFile = new File(filename); Scanner scan = new Scanner(inputFile); int numberNodes = scan.nextInt(); scan.nextLine(); int root = 0, left, right; //存储在根结点中 List<LinkedBinaryTree<String>> nodes = new ArrayList<LinkedBinaryTree<String>>(); for (int i = 0; i < numberNodes; i++) { nodes.add(i,new LinkedBinaryTree<String>(scan.nextLine())); } //创建子树 while (scan.hasNext()) { root = scan.nextInt(); left = scan.nextInt(); right = scan.nextInt(); scan.nextLine(); nodes.set(root, new LinkedBinaryTree<String>((nodes.get(root)).getRootElement(), nodes.get(left), nodes.get(right))); } tree = nodes.get(root); }
evaluate
方法从根结点开始处理,用current表示正在处理的结点。在循环中,若是用户的答案为N,则更新current使之指向左孩子,若是用户的答案为Y,则更新current使之指向右孩子,循环直至current为叶子结点时结束,结束后返回current的根结点的引用。public void evaluate() { LinkedBinaryTree<String> current = tree; Scanner scan = new Scanner(System.in); while (current.size() > 1) { System.out.println (current.getRootElement()); if (scan.nextLine().equalsIgnoreCase("N")) { current = current.getLeft(); } else { current = current.getRight(); } } System.out.println (current.getRootElement()); }
public static String toSuffix(String infix) { String result = ""; //将字符串转换为数组 String[] array = infix.split("\\s+"); //存放操做数 Stack<LinkedBinaryTree> num = new Stack(); //存放操做符 Stack<LinkedBinaryTree> op = new Stack(); for (int a = 0; a < array.length; a++) { //若是是操做数,开始循环 if (array[a].equals("+") || array[a].equals("-") || array[a].equals("*") || array[a].equals("/")) { if (op.empty()) { //若是栈是空的,将数组中的元素创建新树结点并压入操做符栈 op.push(new LinkedBinaryTree<>(array[a])); } else { //若是栈顶元素为+或-且数组的元素为*或/时,将元素创建新树结点并压入操做符栈 if ((op.peek().root.element).equals("+") || (op.peek().root.element).equals("-") && array[a].equals("*") || array[a].equals("/")) { op.push(new LinkedBinaryTree(array[a])); } else { //将操做数栈中的两个元素做为左右孩子,操做符栈中的元素做为根创建新树 LinkedBinaryTree right = num.pop(); LinkedBinaryTree left = num.pop(); LinkedBinaryTree temp = new LinkedBinaryTree(op.pop().root.element, left, right); //将树压入操做数栈,并将数组中的元素创建新树结点并压入操做符栈 num.push(temp); op.push(new LinkedBinaryTree(array[a])); } } } else { //将数组元素创建新树结点并压入操做数栈 num.push(new LinkedBinaryTree<>(array[a])); } } while (!op.empty()) { LinkedBinaryTree right = num.pop(); LinkedBinaryTree left = num.pop(); LinkedBinaryTree temp = new LinkedBinaryTree(op.pop().root.element, left, right); num.push(temp); } //输出后缀表达式 Iterator itr=num.pop().iteratorPostOrder(); while (itr.hasNext()){ result+=itr.next()+" "; } return result; }
removeMin
的实现方法,二叉查找树有一个特殊的性质就是最小的元素存储在树的左边,最大的元素存储在树的右边。所以实现removeMax
方法只须要把removeMin
方法中全部的left和right对调便可。二叉查找树的删除操做有三种状况,要依据这三种状况来实现代码,我在第七周博客教材内容总结中已经分析过了,就不在这里贴代码了。removeMin
和removeMax
后,其实findMin
和findMax
就很简单了,由于在实现删除操做时首先先要找到最大/最小值,所以只要把找到以后的步骤删掉,返回找到的最大值或最小值的元素便可。public T findMin() throws EmptyCollectionException { T result; if (isEmpty()){ throw new EmptyCollectionException("LinkedBinarySearchTree"); } else { if (root.left == null){ result = root.element; } else { BinaryTreeNode<T> parent = root; BinaryTreeNode<T> current = root.left; while (current.left != null){ parent = current; current = current.left; } result = current.element; } } return result; } public T findMax() throws EmptyCollectionException { T result; if (isEmpty()){ throw new EmptyCollectionException("LinkedBinarySearchTree"); } else { if (root.right == null){ result = root.element; } else { BinaryTreeNode<T> parent = root; BinaryTreeNode<T> current = root.right; while (current.right != null){ parent = current; current = current.right; } result = current.element; } } return result; }
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; threshold = initialCapacity; init(); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); }
public V get(Object key) { //当key为空时,返回null if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } private V getForNullKey() { if (size == 0) { return null; } //key为null的Entry用于放在table[0]中,可是在table[0]冲突链中的Entry的key不必定为null,所以,须要遍历冲突链,查找key是否存在 for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; } final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); //首先定位到索引在table中的位置 //而后遍历冲突链,查找key是否存在 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; }
public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); //当指定键key存在时,返回key的value。 return (e == null ? null : e.value); } final Entry<K,V> removeEntryForKey(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); int i = indexFor(hash, table.length); //这里用了两个Entry对象,至关于两个指针,为的是防止出现链表指向为空,即冲突链断裂的状况 Entry<K,V> prev = table[i]; Entry<K,V> e = prev; //当table[i]中存在冲突链时,开始遍历里面的元素 while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) //当冲突链只有一个Entry时 table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { //设置红黑树父节点(链) TreeNode<K,V> parent; // red-black tree links //设置左节点 TreeNode<K,V> left; //设置右节点 TreeNode<K,V> right; //设置前置节点 TreeNode<K,V> prev; // needed to unlink next upon deletion //设置红黑标志 boolean red; //构造函数 TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } //返回根节点 final TreeNode<K,V> root() { for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; } } //与红黑树相关的操做(此处略)
treeify
和untreeify
final void treeify(Node<K,V>[] tab) { TreeNode<K,V> root = null; //循环整理 for (TreeNode<K,V> x = this, next; x != null; x = next) { //取出下一个链表节点 next = (TreeNode<K,V>)x.next; //将x节点的左右节点设置为null x.left = x.right = null; //判断当前红黑树是否有根节点 if (root == null) { x.parent = null; //设置颜色为黑色(根节点为黑色) x.red = false; //将x节点设置为根节点 root = x; } //若当前红黑树存在根节点 else { //获取x节点的key K k = x.key; //获取x节点的hash int h = x.hash; //key的class Class<?> kc = null; //这一部分不是看得很懂,大概是从根节点遍历,将x节点插入到红黑树中 //dir应该指的是树的子树的方向,-1为左侧,1为右侧 for (TreeNode<K,V> p = root;;) { int dir, ph; K pk = p.key; if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); TreeNode<K,V> xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { x.parent = xp; if (dir <= 0) xp.left = x; xp.right = x; root = balanceInsertion(root, x); break; } } } } //确保哈希桶指定位置存储的节点是红黑树的根节点 moveRootToFront(tab, root); } final Node<K,V> untreeify(HashMap<K,V> map) { Node<K,V> hd = null, tl = null; //循环,将红黑树转成链表 for (Node<K,V> q = this; q != null; q = q.next) { //构造一个普通链表节点 Node<K,V> p = map.replacementNode(q, null); if (tl == null) hd = p; else tl.next = p; tl = p; } return hd; }
public V put(K key, V value) { Entry<K,V> t = root; // 当根结点为空时 if (t == null) { // 将新的key-value建立一个结点,并将该结点做为根结点 root = new Entry<K,V>(key, value, null); modCount++; return null; } int cmp; //设置一个父结点 Entry<K,V> parent; Comparator<? super K> cpr = comparator; // 若是cpr不为空,即代表采用定制排序 if (cpr != null) { do { // 将t的值赋给根结点 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; // 若是两个 key 相等,新的 value 覆盖原有的 value, // 并返回原有的 value else return t.setValue(value); } while (t != null); } else { //若是t的key为空,抛出错误 if (key == null) throw new NullPointerException(); 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<K,V>(key, value, parent); // 若是新插入 key 小于 parent 的 key,则 e 做为 parent 的左子节点 if (cmp < 0) parent.left = e; // 若是新插入 key 小于 parent 的 key,则 e 做为 parent 的右子节点 else parent.right = e; // 修复红黑树 fixAfterInsertion(e); size++; modCount++; return null; }
private void deleteEntry(Entry<K,V> p) { modCount++; size--; // 若是被删除结点的左子树、右子树都不为空 if (p.left != null && p.right != null) { //用p结点的后继结点代替p Entry<K,V> s = successor (p); p.key = s.key; p.value = s.value; p = s; } // 若是p的左结点存在,则用replacement表明左结点,不然表明右结点 Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { replacement.parent = p.parent; // 若是p没有父结点,则 replacemment 变成父结点 if (p.parent == null) root = replacement; // 若是 p 结点是其父结点的左孩子,则用replacement进行赋值 else if (p == p.parent.left) p.parent.left = replacement; // 若是 p 结点是其父结点的右孩子,操做同上 else p.parent.right = replacement; p.left = p.right = p.parent = null; // 修复红黑树 if (p.color == BLACK) fixAfterDeletion(replacement); } // 若是 p 结点没有父结点,设置根结点为空 else if (p.parent == null) { root = null; } else { if (p.color == BLACK) // 修复红黑树 fixAfterDeletion(p); if (p.parent != null) { // 若是 p 是其父结点的左孩子 if (p == p.parent.left) p.parent.left = null; // 若是 p 是其父结点的右孩子 else if (p == p.parent.right) p.parent.right = null; p.parent = null; } } }
public V get(Object key) { // 根据指定key取出对应的Entry Entry>K,V< p = getEntry(key); // 返回该Entry所包含的value return (p==null ? null : p.value); } final Entry<K,V> getEntry(Object key) { // 若是comparator不为null,代表程序采用定制排序 if (comparator != null) // 返回对于的key return getEntryUsingComparator(key); // 若是key为空,抛出异常 if (key == null) throw new NullPointerException(); // 将key强制类型转换为Comparable Comparable<? super K> k = (Comparable<? super K>) key; // 从根结点开始 Entry<K,V> p = root; while (p != null) { // 用key与当前结点的key进行比较 int cmp = k.compareTo(p.key); // 若是key小于当前结点的key,继续到当前结点的左子树中进行检索 if (cmp < 0) p = p.left; // 若是 key大于当前结点的key,继续到当前结点的右子树中进行检索 else if (cmp > 0) p = p.right; else return p; } return null; }
ArrayUnorderedList类
的公有方法,将该无序列表直接输出(代码在节点一的过程当中有)。后来实验结束后询问同窗学会了将迭代器方法的遍历结果输出。//之后序遍历为例 String result = ""; Iterator itr = tree.iteratorPostOrder(); while (itr.hasNext()){ result += itr.next() + " "; } return result;
toString
方法中,后来发现缘由出在了root上,在toString
方法中,root从一开始就是空的,并无获取到我构造的树的根结点。ReturnBinaryTree
类中加入了一个获取根的方法,结果最后输出的是根的地址。ReturnBinaryTree
类中的方法放的toString
所在的LinkedBinaryTree
类中,由于此时它可以获取到构造的树的根节点,所以就能正常输出了。DecisionTree
类来看,首先第一行的13表明了这颗决策树中的节点个数,因此在DecisionTree
类中的int numberNodes = scan.nextInt();
一句其实就是获取文件的第一行记录节点个数的值。接下来文件中按照层序遍历的顺序将二叉树中的元素一一列出来,最后文件中的几行数字其实表明了每一个结点及其左右孩子的位置(仍然按照层序遍历的顺序),而且是从最后一层不是叶子结点的那一层的结点开始,好比[3,7,8]就表明了层序遍历中第3个元素的左孩子为第7个元素,右孩子为第8个元素。