(1)參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder;用JUnit或本身編寫驅動類對本身實現的LinkedBinaryTree進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息php
(2)基於LinkedBinaryTree,實現基於(中序,先序)序列構造惟一一棵二㕚樹的功能,好比給出中序HDIBEMJNAFCKGL和先序ABDHIEJMNCFGKL,構造出附圖中的樹;用JUnit或本身編寫驅動類對本身實現的功能進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息html
(3)本身設計並實現一顆決策樹;提交測試代碼運行截圖,要全屏,包含本身的學號信息java
(4)輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,並輸出後綴表達式和計算結果;提交測試代碼運行截圖,要全屏,包含本身的學號信息node
(5)完成PP11.3;提交測試代碼運行截圖,要全屏,包含本身的學號信息web
(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)數組
完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder)
這裏的方法編寫在學習樹時都有寫過,因此直接編寫了測試類,實驗結果如圖
數據結構
基於LinkedBinaryTree,實現基於(中序,先序)序列構造惟一一棵二㕚樹的功能,這個須要新建類,類中寫了公有方法generate0,generate0再去調用私有方法generate,
這個主要是利用傳進來的先序和中序的字符串,肯定根結點,而後再肯定其左右孩子,接下來遞歸該過程,直至將該字符串讀取完成。
實驗結果截圖:
app
本身設計並實現一顆決策樹,設計了一棵決策樹去肯定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
實驗結果見圖:
源碼分析
輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,這裏是使用兩個棧,一個是操做符棧,另外一個是操做數棧,其中操做數棧是操做數是以樹的類型進行存儲的。實驗結果見圖:
完成PP11.3;11.3在以前已經作過,測試了一次完成。
看了一下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方法能夠看到,有幾步流程:
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()方法的流程:
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想了很久都沒有一點思路,我不清楚怎麼去使用樹,在哪使用樹,樹是用來存儲什麼的?
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); } }
若是它是乘或除能夠直接入棧,而當它是加或減時,須要把棧裏的比它優先級高的取出來,取出來的時候須要同時從數棧裏取出兩個操做數,構成一棵新的樹,再放入樹的棧中,循環直至棧中沒有元素,而後再把該操做符放入棧中,這樣就能夠實現了。