课程:《程序设计与数据结构(下)》
班级:1723
姓名: 王志伟
学号:20172309
实验教师:王志强老师
实验日期:2018年11月2日
必修/选修: 必修html
1.参考教材p212,完成链树LinkedBinaryTree的实现(getRight,contains,toString,preorder,postorder).java
2.用JUnit或本身编写驱动类对本身实现的LinkedBinaryTree进行测试,提交测试代码运行截图,要全屏,包含本身的学号信息.node
3.课下把代码推送到代码托管平台.git
1.基于LinkedBinaryTree,实现基于(中序,先序)序列构造惟一一棵二㕚树的功能,好比给出中序HDIBEMJNAFCKGL和后序ABDHIEJMNCFGKL,构造出附图中的树.算法
2.用JUnit或本身编写驱动类对本身实现的功能进行测试,提交测试代码运行截图,要全屏,包含本身的学号信息.chrome
3.课下把代码推送到代码托管平台.express
1.本身设计并实现一颗决策树数组
2.提交测试代码运行截图,要全屏,包含本身的学号信息安全
3.课下把代码推送到代码托管平台数据结构
1.完成PP11.3
2.提交测试代码运行截图,要全屏,包含本身的学号信息
3.课下把代码推送到代码托管平台
1.完成PP11.3
2.提交测试代码运行截图,要全屏,包含本身的学号信息
3.课下把代码推送到代码托管平台
参考点击这里对Java中的红黑树(TreeMap,HashMap)进行源码分析,并在实验报告中体现分析结果。
(文件位于:C:\Program Files\Java\jdk-11.0.1\lib\src\java.base\java\util)
这个实验是要求咱们实现几个方法,而后进行测试。
public LinkedBinaryTree<T> getRight() { return right; } //这个类是一个二叉树类,里面属性left、right,分别表明左子树和右子树,要想获得右子树,直接return right;便可!
2.是否含有元素的方法:contains(T targeElement)
这里编写了两个方法,值得注意的是:一个是公有的(public),另外一个是私有的(private)。说明公有的方法用于外部类调用,而这个私有类的方法被用于用于公有方法。
public boolean contains(T targetElement) { return findNode(targetElement,root)!=null;//调用私有方法findNode() } private BinaryTreeNode<T> findNode(T targetElement, BinaryTreeNode<T> next) {//这个方法在树中查找目标元素,没找到时,分别在其左右子树中查找、**运用了递归**! if (next == null) return null; if (next.getElement().equals(targetElement)) return next; BinaryTreeNode<T> temp = findNode(targetElement, next.getLeft()); if (temp == null) temp = findNode(targetElement, next.getRight()); return temp; }
输出的方法:toString()
这个输出方法经过构建两个无序列表,分别应于放置结点和对应的级数,好用于计算后面输出的空格数。其中最难的是每一层相邻的两个结点之间的空格数。
public String toString() { UnorderedListADT<BinaryTreeNode<T>> nodes = new UnorderedListArrayList<BinaryTreeNode<T>>(); UnorderedListADT<Integer> levelList = new UnorderedListArrayList<Integer>(); BinaryTreeNode<T> current; String result = ""; int printDepth = this.getHeight(); int possibleNodes = (int)Math.pow(2, printDepth + 1); int countNodes = 0; nodes.addToRear((BinaryTreeNode<T>) root); Integer currentLevel = 0; Integer previousLevel = -1; levelList.addToRear(currentLevel); while (countNodes < possibleNodes) { countNodes = countNodes + 1; current = nodes.removefirst(); currentLevel = levelList.removefirst(); if (currentLevel > previousLevel) { result = result + "\n\n"; previousLevel = currentLevel; for (int j = 0; j < ((Math.pow(2, (printDepth - currentLevel))) - 1); j++) result = result + " "; } else { for (int i = 0; i < ((Math.pow(2, (printDepth - currentLevel + 1)) - 1)) ; i++) { result = result + " "; } } if (current != null) { result = result + (current.getElement()).toString(); nodes.addToRear(current.getLeft()); levelList.addToRear(currentLevel + 1); nodes.addToRear(current.getRight()); levelList.addToRear(currentLevel + 1); } else { nodes.addToRear(null); levelList.addToRear(currentLevel + 1); nodes.addToRear(null); levelList.addToRear(currentLevel + 1); result = result + " "; } } return result; }
前序、后序遍历方法:preOrder()
前序遍历的顺序是:先遍历该结点,而后是他们的孩子。
所以用伪代码表示为://详细代码见here
Visit node;//前序遍历 preOrder(leftChild); preOrder(rightChild);
postOrder(leftChild); postOrder(rightChild); visit node;//后序遍历
运行结果:
1.实验二是说给定咋们中序遍历和后序遍历的结果,让后让咋们生成一棵树。科普:当给定前序和后序遍历会结果得不到一棵树,由于分不清左右结点
2.咱们基于一个事实:中序遍历必定是 { 左子树节点集合 },root,{ 右子树节点集合 }。
3.后序序遍历的做用就是找到每颗子树的root位置。
public void generateTree(T[] A,T[] B){//A、B两个数组分别是放前序、后序遍历结果的。 BinaryTreeNode<T> temp = getTree(A, B); root=temp; } private BinaryTreeNode<T> getTree(T[] inOrder,T [] postOrder){ int length = postOrder.length; T rootElement = postOrder[postOrder.length-1]; BinaryTreeNode<T> temp = new BinaryTreeNode<T>(rootElement); if (length==1) return temp; else{ int index = 0; while (inOrder[index]!=rootElement) index++; if (index>0){//分界结点的左边又能够生成一个子中序遍历数组和后序遍历数组。以后运用递归。 T [] leftInOrder = (T[])new Object[index]; for(int i=0;i<leftInOrder.length;i++) leftInOrder[i]=inOrder[i]; T [] leftPostOrder = (T[])new Object[index]; for (int i=0;i<leftPostOrder.length;i++) leftPostOrder[i] = postOrder[i]; temp.setLeft(getTree(leftInOrder,leftPostOrder)); } if (length-index-1>0){//右边也同样,生成子两个数组,运用递归。 T [] rightInOrder = (T[])new Object[length-index-1]; for (int i=0;i<length-index-1;i++) rightInOrder[i]=inOrder[i+index+1]; T [] rightPostOrder = (T[])new Object[length-index-1]; for (int i=0;i<length-index-1;i++) rightPostOrder[i]=postOrder[i+index]; temp.setRight(getTree(rightInOrder,rightPostOrder)); } } return temp; }
运行结果为:
public ExpressionTree getPostfixTree(String expression){//传进去的是一个中缀表达式 。。。一大串代码。。。 return EXpressionTree;//通过一大串代码获得一颗表达式树。 }
ExpressionTree operand1,operand2; char operator; String tempToken; Scanner parser = new Scanner(expression); while(parser.hasNext()){ tempToken = parser.next(); operator=tempToken.charAt(0); if ((operator == '+') || (operator == '-') || (operator=='*') || (operator == '/')){ if (ope.empty()) ope.push(tempToken);//当储存符号的栈为空时,直接进栈 else{ String a =ope.peek()+"";//由于当ope.peek()='-'时,计算机认为ope.peek()=='-'为false,因此要转化为string 使用equals()方法 if (((a.equals("+"))||(a.equals("-")))&&((operator=='*')||(operator=='/'))) ope.push(tempToken);//当获得的符号的优先级大于栈顶元素时,直接进栈 else { String s = String.valueOf(ope.pop()); char temp = s.charAt(0); operand1 = getOperand(treeExpression); operand2 = getOperand(treeExpression); treeExpression.push(new ExpressionTree (new ExpressionTreeOp(1, temp, 0), operand2, operand1)); ope.push(operator); }//当获得的符号的优先级小于栈顶元素或者优先级相同时时,数字栈出来两个运算数,造成新的树进栈 } } else treeExpression.push(new ExpressionTree(new ExpressionTreeOp (2,' ',Integer.parseInt(tempToken)), null, null)); } while(!ope.empty()){ String a = String.valueOf(ope.pop()); operator = a.charAt(0); operand1 = getOperand(treeExpression); operand2 = getOperand(treeExpression); treeExpression.push(new ExpressionTree (new ExpressionTreeOp(1, operator, 0), operand2, operand1)); } return treeExpression.peek();
运行结果为:
生成树后,咱们发现把这颗树直接后序遍历就能够获得后缀表达式。详细代码click here
代码以下:
去除最小元素方法。
public T removeMin() throws EmptyCollectionException { T result = null; if (isEmpty()) throw new EmptyCollectionException("LinkedBinarySearchTree"); else { if (root.left == null) { result = root.element; root = root.right; } else { BinaryTreeNode2<T> parent = root; BinaryTreeNode2<T> current = root.left; while (current.left != null) { parent = current; current = current.left; } result = current.element; parent.left = current.right; } modCount--; } return result; }
去除最大元素方法。
public T removeMax() throws EmptyCollectionException { T result= null; if (isEmpty()) throw new EmptyCollectionException("LinkedBinarySearchTree!"); else{ if (root.right == null){ result = root.element; root=root.left; } else{ BinaryTreeNode2<T> parent = root; BinaryTreeNode2<T> current = root.right; while(current.right != null){ parent = current; current = current.right; } result = current.element; parent.right = current.left; } modCount--; } return result; }
找到最大元素方法。
public T findMax() throws EmptyCollectionException { if (isEmpty()) System.out.println("BinarySearchTree is empty!"); return findmax(root).element; }
首先来介绍什么是Map:
- 在数组中咱们是经过数组下标来对其内容索引的,而在Map中咱们经过对象来对对象进行索引,用来索引的对象叫作key,其对应的对象叫作value。这就是咱们平时说的键值对.
- 他们有一个显著的特征:HashMap经过hashcode对其内容进行快速查找,而 TreeMap中全部的元素都保持着某种固定的顺序,若是你须要获得一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
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
两种常规Map性能
HashMap:适用于在Map中插入、删除和定位元素。
Treemap:适用于按天然顺序或自定义顺序遍历键(key)。
总结
HashMap一般比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在须要排序的Map时候才用TreeMap。
public class HashMap<K,V>extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { /** * The default initial capacity - MUST be a power of two. * 默认的容量必须为2的幂 */ static final int DEFAULT_INITIAL_CAPACITY = 16; /** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. *默认最大值 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * The load factor used when none specified in constructor. * 负载因子 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * The table, resized as necessary. Length MUST Always be a power of two. * 到这里就发现了,HashMap就是一个Entry[]类型的数组了。 */ transient Entry<K,V>[] table;
/** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and load factor. * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ // 初始容量(必须是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); // Find a power of 2 >= initialCapacity int capacity = 1; // 获取最小于initialCapacity的最大值,这个值是2的n次幂,因此咱们定义初始容量的时候尽可能写2的幂 while (capacity < initialCapacity) // 使用位移计算效率更高 capacity <<= 1; this.loadFactor = loadFactor; //哈希表的最大容量的计算,取两个值中小的一个 threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); //建立容量为capacity的Entry[]类型的数组 table = new Entry[capacity]; useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); init(); }
public V put(K key, V value) { //key为null的entry老是放在数组的头节点上,也就是上面说的"桶"中 if (key == null) return putForNullKey(value); // 获取key的哈希值 int hash = hash(key); // 经过key的哈希值和table的长度取模肯定‘桶’(bucket)的位置 int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //假设key映射的entry在链表中已存在,则entry的value替换为新value if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
//bucketIndex 桶的索引值,桶中仅仅能存储一个值(一个Entry 对象)也就是头节点 void addEntry(int hash, K key, V value, int bucketIndex) { // 假设数组中存储的元素个数大于数组的临界值(这个临界值就是 数组长度*负载因子的值 )则进行扩容 if ((size >= threshold) && (null != table[bucketIndex])) { // 扩容,将大小扩为原来的两倍 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); }
HashMap 是链式数组(存储链表的数组)实现查询速度可以。而且能高速的获取key相应的value;
查询速度的影响因素有 容量和负载因子,容量大负载因子小查询速度快但浪费空间,反之则相反。
数组的index值是(key keyword, hashcode为key的哈希值。 len 数组的大小):hashcode%len的值来肯定,假设容量大负载因子小则index一样(index一样也就是指向了同一个桶)的几率小。链表长度小则查询速度快。反之index一样的几率大链表比較长查询速度慢。
对于HashMap以及其子类来讲。他们是採用hash算法来决定集合中元素的存储位置,当初始化HashMap的时候系统会建立一个长度为capacity的Entry数组,这个数组里可以存储元素的位置称为桶(bucket),每一个桶都有其指定索引,系统可以依据索引高速訪问该桶中存储的元素。
无论什么时候HashMap 中的每个桶都仅仅存储一个元素(Entry 对象)。
由于Entry对象可以包括一个引用变量用于指向下一个Entry,所以可能出现HashMap 的桶(bucket)中仅仅有一个Entry,但这个Entry指向还有一个Entry 这样就造成了一个Entry 链。
经过上面的源代码发现HashMap在底层将key_value对当成一个整体进行处理(Entry 对象)这个整体就是一个Entry对象,当系统决定存储HashMap中的key_value对时,全然没有考虑Entry中的value,而不过依据key的hash值来决定每个Entry的存储位置。
还有一个问题就是为何他的顺序是
3 7 8 4 9 10 5 11 12 1 3 4 2 5 6 0 1 2//这样的0 1 2
2 5 6
1 3 6
5 11 12
4 9 10
3 7 8//而非得是倒着来呢?
通过修改:ope.Peek().equals("+")解决了这个问题。
这一章的实验虽然只有几个难一点的,好比实验四和实验六,但确实让人更头疼!他别是实验四,看到在实验提交前好多人都无论在什么课上都在想怎么作!而后本身在三四小时就把它解决了,想一想仍是能够的!也许这就是不间断地思考一个问题的好处吧!