課程:《程序設計與數據結構》html
班級: 1723java
姓名: 方藝雯數組
學號:20172314數據結構
實驗教師:王志強app
實驗日期:2018年11月8日ide
必修/選修: 必修函數
實驗二-1-實現二叉樹源碼分析
參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder)用JUnit或本身編寫驅動類對本身實現的LinkedBinaryTree進行測試post
實驗二 樹-2-中序先序序列構造二叉樹性能
基於LinkedBinaryTree,實現基於(中序,先序)序列構造惟一一棵二㕚樹的功能,好比給出中序HDIBEMJNAFCKGL和後序ABDHIEJMNCFGKL,構造出附圖中的樹,用JUnit或本身編寫驅動類對本身實現的功能進行測試
實驗二 樹-3-決策樹
本身設計並實現一顆決策樹
實驗二 樹-4-表達式樹
輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,並輸出後綴表達式和計算結果
實驗二 樹-5-二叉查找樹
完成PP11.3
實驗二 樹-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)
getRight方法核心代碼爲
LinkedBinaryTree<T> result = new LinkedBinaryTree <T>(); result.root = root.getRight();
preorder,postorder方法核心代碼爲
//前序遍歷 public void preOrder(BinaryTreeNode<T> root) { if (root != null) { System.out.print(root.element + " "); preOrder(root.left); preOrder(root.right); } } //後序遍歷 public void postOrder(BinaryTreeNode<T> root) { if (root != null) { postOrder(root.left); postOrder(root.right); System.out.print(root.element + " "); } }使用了遞歸的方法進行遍歷
contains,toString,方法均使用課本代碼,其中toString是將PrintTree更名。
實驗結果
核心代碼爲:
public BinaryTreeNode<T> reBuildTree(String[] pre, String[] in, int preStart, int preEnd, int inStart, int inEnd) { BinaryTreeNode root = new BinaryTreeNode(pre[preStart]); root.left = null; root.right = null; if (preStart == preEnd && inStart == inEnd) {//只有一個元素時 return root; } int a = 0; for(a= inStart; a < inEnd; a++){//找到中序遍歷中根節點的位置 if (pre[preStart] == in[a]) { break; } } int leftLength = a - inStart;//找到左子樹的元素個數 int rightLength = inEnd - a;//找到右子樹的元素個數 if (leftLength > 0) {//左右子樹分別進行以上操做 root.left= reBuildTree(pre, in, preStart+1, preStart+leftLength, inStart, a-1); } if (rightLength > 0) { root.right = reBuildTree(pre, in, preStart+1+leftLength, preEnd, a+1, inEnd); } return root; }
最後使用
public void reBuildTree(String [] pre, String [] in) { BinaryTreeNode a = reBuildTree(pre, in, 0, pre.length-1, 0, in.length-1); root = a; }方法調用reBuildTree(String[] pre, String[] in, int preStart, int preEnd, int inStart, int inEnd)方法,完成樹的重建。
實驗結果
核心代碼
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()); }evaluate方法用來決策,從文件中讀入問題以後,根據用戶輸入的結果,來進行下一步選擇,輸出左子樹或右子樹。
實驗結果
核心代碼
public BinaryTreeNode BuildTree(String str) { ArrayList<BinaryTreeNode> num = new ArrayList<BinaryTreeNode>(); ArrayList<String> symbol = new ArrayList<String>(); StringTokenizer st = new StringTokenizer(str); //獲得輸入的數字和符號 String next; while (st.hasMoreTokens()) { next = st.nextToken(); if (next.equals("(")) { String str1 = ""; next = st.nextToken(); while (!next.equals(")")) {//計算括號內的內容,當找到右括號時,進行下面的步驟構造樹 str1 += next + " "; next = st.nextToken(); } num.add(BuildTree(str1));//括號裏的優先,建立一棵樹 if (st.hasMoreTokens()) { next = st.nextToken(); } else break; } if (!next.equals("+") && !next.equals("-") && !next.equals("*") && !next.equals("/")) { num.add(new BinaryTreeNode(next)); //是數字進入num } if (next.equals("+") || next.equals("-")) { BinaryTreeNode<String> tempNode = new BinaryTreeNode<>(next); next = st.nextToken(); if (!next.equals("(")) { symbol.add(tempNode.element);//優先級低,存入符號集 num.add(new BinaryTreeNode(next)); } else { symbol.add(tempNode.element); String temp = st.nextToken(); String s = ""; while (!temp.equals(")")) {//收集括號內的信息 s += temp + " "; temp = st.nextToken(); } num.add(BuildTree(s));//對括號內的建樹 } } if (next.equals("*") || next.equals("/")) { BinaryTreeNode<String> tempNode = new BinaryTreeNode<>(next); next = st.nextToken(); if (!next.equals("(")) {//沒有括號時,以* / 爲父結點建樹,num中最後兩個數分別爲左右孩子 tempNode.setLeft(num.remove(num.size() - 1)); tempNode.setRight(new BinaryTreeNode<String>(next)); num.add(tempNode);//將這個樹添加到num中 } else { //遇到括號,num的最後一個數爲左孩子,剩下的都是右子樹 String temp = st.nextToken(); tempNode.setLeft(num.remove(num.size() - 1));//把* 或/ 前面的數變爲左子樹 String s = ""; while (!temp.equals(")")) {//括號中內容所有是的是右子樹 s += temp + " "; temp = st.nextToken(); } tempNode.setRight(BuildTree(s)); num.add(tempNode); } } } int i = symbol.size(); while (i > 0) {//最後把num中存放的小樹,整合成一棵完整的樹。 BinaryTreeNode<T> root = new BinaryTreeNode(symbol.remove(symbol.size() - 1)); root.setRight(num.remove(num.size() - 1)); root.setLeft(num.remove(num.size() - 1)); num.add(root); i--; } return num.get(0);//輸出最終的樹 }
實驗結果
核心代碼
public T findMin() { T result = null; if (isEmpty()) throw new EmptyCollectionException("LinkedBinarySearchTree"); else { if (root.left == null) { result = root.element; //root = root.right; } else { BinaryTreeNode<T> parent = root; BinaryTreeNode<T> current = root.left; while (current.left != null) { parent = current; current = current.left; } result = current.element; //parent.left = current.right; } //modCount--; } return result; }
@Override public T findMax() { T result = null; if (isEmpty()) throw new EmptyCollectionException("LinkedBinarySearchTree"); else { if (root.right == null) { result = root.element; //root = root.left; } else { BinaryTreeNode<T> parent = root; BinaryTreeNode<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 removeMax() { T result = null; if (isEmpty()) throw new EmptyCollectionException("LinkedBinarySearchTree"); else { if (root.right == null) { result = root.element; root = root.left; } else { BinaryTreeNode<T> parent = root; BinaryTreeNode<T> current = root.right; while (current.right != null) {//找右子樹,他爲最大 parent = current; current = current.right; } result = current.element;//獲得最大值 parent.right = current.left;//用左孩子替換最大的 } modCount--; } return result; }
findMin和findMax相似,removeMax是在findMax的基礎上找到以後將其替換。因爲二叉查找樹左孩子小於根小於右孩子,例如findMin操做,當左孩子爲空時,返回根結點(最小),不然向下查找到最後一個左孩子,返回其值。
實驗結果
TreeMap類的繼承關係
public class TreeMap<K,V> extends AbstractMap<K,V>implements NavigableMap<K,V>, Cloneable, Serializable它繼承並實現了Map,因此TreeMap具備和Map同樣執行put,get的操做,直接經過key取value值。同時實現SortedMap,支持遍歷時按元素的大小有序遍歷。
TreeMap 是一個有序的key-value集合,它是經過紅黑樹實現的。
TreeMap 繼承於AbstractMap,因此它是一個Map,即一個key-value集合。
TreeMap 實現了NavigableMap接口,意味着它支持一系列的導航方法。好比返回有序的key集合。
TreeMap 實現了Cloneable接口,意味着它能被克隆。
TreeMap 實現了java.io.Serializable接口,意味着它支持序列化。
TreeMap基於紅黑樹(Red-Blacktree)實現。該映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的 Comparator進行排序,具體取決於使用的構造方法。構造函數
// 默認構造函數 public TreeMap() { comparator = null; } // 帶比較器的構造函數 public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } // 帶Map的構造函數,Map會成爲TreeMap的子集 public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); } // 帶SortedMap的構造函數,SortedMap會成爲TreeMap的子集 public TreeMap(SortedMap<K, ? extends V> m) { comparator = m.comparator(); try { buildFromSorted(m.size(), m.entrySet().iterator(), null, null); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } }
從TreeMap中刪除第一個節點方法
public Map.Entry<K,V> pollFirstEntry() { // 獲取第一個節點 Entry<K,V> p = getFirstEntry(); Map.Entry<K,V> result = exportEntry(p); // 刪除第一個節點 if (p != null) deleteEntry(p); return result; }
返回小於key值的最大的鍵值對所對應的KEY,沒有的話返回null
public K lowerKey(K key) { return keyOrNull(getLowerEntry(key)); }
獲取Map的頭部,範圍從第一個節點 到 toKey.
public NavigableMap<K,V> headMap(K toKey, boolean inclusive) { return new AscendingSubMap(this, true, null, true, false, toKey, inclusive); }
刪除當前結點
需注意當lastReturned的左右孩子都不爲空時,要將其賦值給next。是由於刪除lastReturned節點以後,next節點指向的仍然是下一個節點。根據紅黑樹的特性可知:當被刪除節點有兩個兒子時。那麼,首先把它的後繼節點的內容複製給該節點的內容,以後刪除它的後繼節點。這意味着當被刪除節點有兩個兒子時,刪除當前節點以後,新的當前節點其實是原有的後繼節點(即下一個節點)。而此時next仍然指向新的當前節點。也就是說next是仍然是指向下一個節點,能繼續遍歷紅黑樹。
public void remove() { if (lastReturned == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (lastReturned.left != null && lastReturned.right != null) next = lastReturned; deleteEntry(lastReturned); expectedModCount = modCount; lastReturned = null; } }
firstEntry()和getFirstEntry()
public Map.Entry<K,V> firstEntry() { return exportEntry(getFirstEntry()); } final Entry<K,V> getFirstEntry() { Entry<K,V> p = root; if (p != null) while (p.left != null) p = p.left; return p; }
firstEntry()和getFirstEntry()都是用於獲取第一個節點,firstEntry()是對外接口;getFirstEntry() 是內部接口。並且,firstEntry()是經過getFirstEntry() 來實現的。之因此不直接調用getFirstEntry()是爲了防止用戶修改返回的Entry。咱們能夠調用Entry的getKey()、getValue()來獲取key和value值,以及調用setValue()來修改value的值,而對firstEntry()返回的Entry對象只能進行getKey()、getValue()等讀取操做。因此要調用 firstEntry()獲取。
HashMap
HashMap由數組+鏈表組成的,數組是HashMap的主體,鏈表則是主要爲了解決哈希衝突而存在的,若是定位到的數組位置不含鏈表(當前entry的next指向null),那麼對於查找,添加等操做很快,僅需一次尋址便可;若是定位到的數組包含鏈表,對於添加操做,其時間複雜度爲O(n),首先遍歷鏈表,存在即覆蓋,不然新增,對於查找操做來說,仍需遍歷鏈表,而後經過key對象的equals方法逐一比對查找。因此,性能考慮,HashMap中的鏈表出現越少,性能纔會越好。
get方法
get方法經過key值返回對應value,若是key爲null,直接去table[0]處檢索
public V get(Object key) {//若是key爲null,則直接去table[0]處去檢索便可。 if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); }
getEntry方法
get方法的實現相對簡單,key(hashcode)-->hash-->indexFor-->最終索引位置,找到對應位置table[i],再查看是否有鏈表,遍歷鏈表,經過key的equals方法比對查找對應的記錄。要注意的是,上面在定位到數組位置以後而後遍歷鏈表的時候,e.hash==hash是有必要的,不能僅經過equals判斷。由於若是傳入的key對象重寫了equals方法卻沒有重寫hashCode,而恰巧此對象定位到這個數組位置,若是僅僅用equals判斷多是相等的,但其hashCode和當前對象不一致,這種狀況,根據Object的hashCode的約定,不能返回當前對象,而應該返回null。
final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } //經過key的hashcode值計算hash值 int hash = (key == null) ? 0 : hash(key); //indexFor (hash&length-1) 獲取最終數組索引,而後遍歷鏈表,經過equals方法比對找出對應記錄 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; }
roundUpToPowerOf2方法
這個處理使得數組長度必定爲2的次冪,Integer.highestOneBit是用來獲取最左邊的bit(其餘bit位爲0)所表明的數值。
private static int roundUpToPowerOf2(int number) { // assert number >= 0 : "number must be non-negative"; return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; }
那麼爲何數組長度必定是2的次冪呢?
這樣會保證低位全爲1,而擴容後只有一位差別,也就是多出了最左位的1,這樣在經過 h&(length-1)的時候,只要h對應的最左邊的那一個差別位爲0,就能保證獲得的新的數組索引和老數組索引一致,同時,數組長度保持2的次冪,length-1的低位都爲1,會使得得到的數組索引index更加均勻,若是不是2的次冪,也就是低位不是全爲1此時,h的低位部分再也不具備惟一性了,哈希衝突的概率會變的更大。
問題一解決:前序序列能夠肯定根結點,由循環得出
for(a= inStart; a < inEnd; a++){//找到中序遍歷中根節點的位置 if (pre[preStart] == in[a]) { break; } }
那麼在中序序列中,根結點左右的元素便可確立,肯定左右元素的數目leftLength和rightLength,若大於0,則分別進行
root.left= reBuildTree(pre, in, preStart+1, preStart+leftLength, inStart, a-1);
和
root.right = reBuildTree(pre, in, preStart+1+leftLength, preEnd, a+1, inEnd);
再次肯定左右子樹的根結點,如此循環,直到全部的結點被肯定,這時樹就造成了。
此次的實驗報告有一點難度,花費挺長時間的,不過對樹的瞭解更加深刻了,學習到不少。