課程:《程序設計與數據結構(下)》
班級: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("+")解決了這個問題。
這一章的實驗雖然只有幾個難一點的,好比實驗四和實驗六,但確實讓人更頭疼!他別是實驗四,看到在實驗提交前好多人都無論在什麼課上都在想怎麼作!而後本身在三四小時就把它解決了,想一想仍是能夠的!也許這就是不間斷地思考一個問題的好處吧!