必修選修: 必修html
實驗一:實現二叉樹
參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder)
用JUnit或本身編寫驅動類對本身實現的LinkedBinaryTree進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息
課下把代碼推送到代碼託管平臺
實驗二:中序先序序列構造二叉樹
基於LinkedBinaryTree,實現基於(中序,先序)序列構造惟一一棵二㕚樹的功能,好比給出中序HDIBEMJNAFCKGL和後序ABDHIEJMNCFGKL,構造出附圖中的樹,用JUnit或本身編寫驅動類對本身實現的功能進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息
實驗三:決策樹
本身設計並實現一顆決策樹,提交測試代碼運行截圖,要全屏,包含本身的學號信息,課下把代碼推送到代碼託管平臺
實驗四:表達式樹
輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,並輸出後綴表達式和計算結果(若是沒有用樹,則爲0分),提交測試代碼運行截圖,要全屏,包含本身的學號信息,課下把代碼推送到代碼託管平臺
實驗五:二叉查找樹
完成PP11.3,提交測試代碼運行截圖,要全屏,包含本身的學號信息,課下把代碼推送到代碼託管平臺
實驗六 : 紅黑樹分析
參考本博客:點擊進入對Java中的紅黑樹(TreeMap,HashMap)進行源碼分析,並在實驗報告中體現分析結果。java
實驗一過程及結果:
LinedBinaryTree碼雲連接
測試類及測試結果node
package week6.jsjf; public class LinkedBinaryTreeTest { public static void main(String[] args) throws InterruptedException { LinkedBinaryTree c=new LinkedBinaryTree("b");LinkedBinaryTree b=new LinkedBinaryTree("c");LinkedBinaryTree a=new LinkedBinaryTree("Translation",c,b); LinkedBinaryTree e=new LinkedBinaryTree("F"); LinkedBinaryTree f=new LinkedBinaryTree("g",a,e); System.out.println("二叉樹的toString方法"); System.out.println(f.toString()); System.out.println("二叉樹的先序遍歷"); f.preorder(f.getRootNode()); System.out.println(); System.out.println("二叉樹的後序遍歷"); f.postorder(f.getRootNode()); System.out.println(); System.out.println("二叉樹的中序遍歷"); f.Inorder(f.getRootNode()); System.out.println(); System.out.println("二叉樹的層序遍歷"); f.unrecursionlevelOreder(f.getRootNode()); System.out.println(); System.out.println( "查找是否包含a的結果:"+f.contains("Translation")+"二叉樹中的元素:"+"b, c, Translation, F, g"); System.out.println("查找是否包含A的結果:"+f.contains("A")+"二叉樹中的元素:"+"b, c, Translation, F, g"); System.out.println("getRight方法的檢驗,即輸出樹的根節點的右子樹"); System.out.println(f.getRight().toString()); System.out.println("getLeft方法的檢驗,即輸出樹的根節點的左子樹"); System.out.println(f.getLeft().toString()); System.out.println("二叉樹的高度:"+f.getHeight()+"二叉樹的元素個數:"+f.size()+"二叉樹的根元素"+f.getRootElement()); } }
實驗二
這個實驗要求的是經過給定的中綴表達式和後綴表達式構建二叉樹題目中給定了中序HDIBEMJNAFCKGL和先序ABDHIEJMNCFGKL。
咱們由先序知道二叉樹的根節點是爲A,在中序遍歷中就能夠知道以A爲根節點的左子樹爲「HDIBEMJN」,右子樹爲「FCKGL」。因爲我是使用數組記錄的兩個序列。因此在每一次肯定完根節點之後,我就會更新數組的指針,在經過遞歸依次肯定左子樹的根節點和右子樹的根節點,在進行過幾回遞歸以後。就能夠獲得以中序遍歷以及先序遍歷爲基礎的二叉樹了。
測試結果
BTCreation碼雲連接git
實驗三
決策樹,這個的實現是基於書上的代碼,這個實際上也沒有也沒有太大的難度
DecisionTree碼雲連接
CharacterAnalyzer
這是我設計的決策樹
測試結果
express
實驗四
這個實驗主要要求是經過二叉樹將中綴表達式轉爲後綴表達式,輸出並計算後綴表達式並計算後綴表達式的結果。
實驗分析:書上已經有了將後綴表達式構建二叉樹並計算的方法了,實際上餓哦們只須要將中綴表達式按照優先級的順序錄入二叉樹中,就能夠調用樹中的方法計算並獲得結果了。而後我就作了實際上原理與那個直接中綴轉後綴相仿。
碼雲連接
Translation
測試結果截圖
測試代碼數組
public static void main(String[] args) { Translation translation =new Translation(); String a="8 / 4 - 9 + 11 * 2"; translation.Translate(a); System.out.println("中綴表達式樹是"); System.out.println(translation.getTree()); System.out.println("後序遍歷是"+translation.PostOrder()); System.out.println("結果是"+translation.getResult()); }
實驗五
這個實驗主要就是測試一下沒有什麼好講的
碼雲連接
LinkedBinarySearchTree數據結構
測試代碼函數
package week7.jsjf; public class LinkedBinarySearchTreeTest { public static void main(String[] args) throws InterruptedException { LinkedBinarySearchTree a=new LinkedBinarySearchTree(); a.addElement(80); a.addElement(10); a.addElement(9); a.addElement(6); a.addElement(7); a.addElement(85); a.addElement(16); a.addElement(44); System.out.println(a.toString()); System.out.println(a.findMax()); System.out.println(a.findMin()); System.out.println(a.find(85)); a.removeElement(10); System.out.println(a.toString()); a.removeMax(); System.out.println(a.toString()); a.removeMin(); System.out.println(a.toString()); } }
測試結果
//Node是單向鏈表,它實現了Map.Entry接口
static class Node<k,v> implements Map.Entry<k,v> {
final int hash;
final K key;
V value;
Node<k,v> next;
//構造函數Hash值 鍵 值 下一個節點
Node(int hash, K key, V value, Node<k,v> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}源碼分析
public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + = + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } //判斷兩個node是否相等,若key和value都相等,返回true。能夠與自身比較爲true public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; }
}
實驗六
TreeMappost
// 默認構造函數。使用該構造函數,TreeMap中的元素按照天然排序進行排列。 TreeMap() // 建立的TreeMap包含Map TreeMap(Map<? extends K, ? extends V> copyFrom) // 指定Tree的比較器 TreeMap(Comparator<? super K> comparator) // 建立的TreeSet包含copyFrom TreeMap(SortedMap<K, ? extends V> copyFrom)
//構建一個具備默認初始容量 (16) 和默認加載因子 (0.75) 的空哈希映像 HashMap() //構建一個哈希映像,而且添加映像m的全部映射 HashMap(Map<? extends K,? extends V> m) //構建一個擁有特定容量的空的哈希映像 HashMap(int initialCapacity) //構建一個擁有特定容量和加載因子的空的哈希映像 HashMap(int initialCapacity, float loadFactor)
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; }
public K ceilingKey(K key) { return keyOrNull(getCeilingEntry(key)); }
public Collection<V> values() { Collection<V> vs = values; return (vs != null) ? vs : (values = new Values()); }
public Set<Map.Entry<K,V>> entrySet() { EntrySet es = entrySet; return (es != null) ? es : (entrySet = new EntrySet()); }
Integer integ = null; Iterator iter = map.entrySet().iterator(); while(iter.hasNext()) { Map.Entry entry = (Map.Entry)iter.next(); key = (String)entry.getKey(); integ = (Integer)entry.getValue(); }
String key = null; Integer integer = null; Iterator iter = map.keySet().iterator(); while (iter.hasNext()) { key = (String)iter.next(); integer = (Integer)map.get(key); }
HashMap
這個類咱們已經很熟悉了,所謂哈希查找就是將所要存儲的數據經過一個公式來進行計算,獲得所謂的「地址」,而後儲存在數組對應的「地址」中例如「5%4=1,8%4=0」,則「5」和「8」的地址分別爲「1「和」0」,但有時地址之間會發生衝突,這是就要經過其餘方法來解決衝突,在就java自帶的HashMap中,解決這個衝突的方法是鏈地址法,也就是指在數組中存放的是鏈表,一旦存入的數據的地址發生衝突,那麼久將其插入鏈表之中,這樣就很好的解決了衝突的問題,在jdk1.8版本以前,解決衝突的首端都是經過鏈表,可是在jdk1.8.0中,解決這個問題的方案變成了兩個,就是在當鏈表中的元素小於或等於8個是,使用單鏈表來解決這個問題,當發生衝突的元素個數大於8個是,就使用一個紅黑樹來儲存這些元素,這樣能夠提升效率。
HashMap儲存形式圖例
Node是HashMap的一個內部類,實現了Map.Entry接口,本質是就是一個映射(鍵值對)。下列代碼能夠近似理解成相似於單鏈表的結點類
//Node是單向鏈表,它實現了Map.Entry接口 static class Node<k,v> implements Map.Entry<k,v> { final int hash; final K key; V value; Node<k,v> next; //構造函數Hash值 鍵 值 下一個節點 Node(int hash, K key, V value, Node<k,v> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + = + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } //判斷兩個node是否相等,若key和value都相等,返回true。能夠與自身比較爲true public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
這是紅黑樹的相關結點類
//紅黑樹 static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v> { TreeNode<k,v> parent; // 父節點 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; } } }
這是插入節點的方法
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爲空則建立 // table未初始化或者長度爲0,進行擴容 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 步驟②:計算index,並對null作處理 // (n - 1) & hash 肯定元素存放在哪一個桶中,桶爲空,新生成結點放入桶中(此時,這個結點是放在數組中) if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); // 桶中已經存在元素 else { Node<K,V> e; K k; // 步驟③:節點key存在,直接覆蓋value // 比較桶中第一個元素(數組中的結點)的hash值相等,key相等 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) // 將第一個元素賦值給e,用e來記錄 e = p; // 步驟④:判斷該鏈爲紅黑樹 // hash值不相等,即key不相等;爲紅黑樹結點 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; } // 判斷鏈表中結點的key值與插入的元素的key值是否相等 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // 相等,跳出循環 break; // 用於遍歷桶中的鏈表,與前面的e = p.next組合,能夠遍歷鏈表 p = e; } } // 表示在桶中找到key值、hash值與插入元素相等的結點 if (e != null) { // 記錄e的value V oldValue = e.value; // onlyIfAbsent爲false或者舊值爲null if (!onlyIfAbsent || oldValue == null) //用新值替換舊值 e.value = value; // 訪問後回調 afterNodeAccess(e); // 返回舊值 return oldValue; } } // 結構性修改 ++modCount; // 步驟⑥:超過最大容量 就擴容 // 實際大小大於閾值則擴容 if (++size > threshold) resize(); // 插入後回調 afterNodeInsertion(evict); return null; }
平衡紅黑樹的代碼 static <K, V> TreeNode<K, V> balanceInsertion(TreeNode<K, V> root, TreeNode<K, V> x) { //將插入的節點塗成紅色 x.red = true; //此時x節點是剛插入的節點 // 這些變量名不是做者隨便定義的都是有意義的。 // xp:x parent,表明x的父節點。 // xpp:x parent parent,表明x的祖父節點 // xppl:x parent parent left,表明x的祖父的左節點。 // xppr:x parent parent right,表明x的祖父的右節點。 for (TreeNode<K, V> xp, xpp, xppl, xppr;;) { //若是x.parent==null證實x是根節點,並將x(根節點)返回;平衡完畢。 if ((xp = x.parent) == null) { //將根節點塗成黑色 x.red = false; return x; //只要進入此if就不會知足註釋中3條件的任意一個,直接將root返回;平衡完畢。 } else if (!xp.red || (xpp = xp.parent) == null) return root; //若父節點是祖父節點的左子節點,與下面的徹底相反,本質是同樣的 if (xp == (xppl = xpp.left)) { //x節點的祖父節點的右子節點(叔叔節點)不爲null且是紅色,父節點必然也是紅色,此時知足第1種狀況。 //操做:1.將祖父節點的右子節點(叔叔節點)、父節點塗爲黑色 // 2.將祖父節點塗爲紅色 // 3.將祖父節點賦給x(參照節點的變動) if ((xppr = xpp.right) != null && xppr.red) { xppr.red = false; xp.red = false; xpp.red = true; x = xpp; //進入else 說明已是第2或第3種狀況了 } else { //第2種狀況 // 操做:1.標記節點變爲x。 // 2.左旋 // 3.x的父節點、x的祖父節點隨之變化 if (x == xp.right) { root = rotateLeft(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } //第3種狀況 //操做 1.將父節點塗黑 // 2.祖父節點塗紅 // 3.右旋 if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; root = rotateRight(root, xpp); } } } } else {//若父節點是祖父節點的右子節點,與上面的徹底相反,本質同樣的 if (xppl != null && xppl.red) { xppl.red = false; xp.red = false; xpp.red = true; x = xpp; } else { if (x == xp.left) { root = rotateRight(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } if (xp != null) { xp.red = false; if (xpp != null) { xpp.red = true; root = rotateLeft(root, xpp); } } } } } }
擴展Hash表的代碼
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table;//oldTab指向hash桶數組 int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) {//若是oldCap不爲空的話,就是hash桶數組不爲空 if (oldCap >= MAXIMUM_CAPACITY) {//若是大於最大容量了,就賦值爲整數最大的閥值 threshold = Integer.MAX_VALUE; return oldTab;//返回 }//若是當前hash桶數組的長度在擴容後仍然小於最大容量 而且oldCap大於默認值16 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold 雙倍擴容閥值threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//新建hash桶數組 table = newTab;//將新數組的值複製給舊的hash桶數組 if (oldTab != null) {//進行擴容操做,複製Node對象值到新的hash桶數組 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) {//若是舊的hash桶數組在j結點處不爲空,複製給e oldTab[j] = null;//將舊的hash桶數組在j結點處設置爲空,方便gc if (e.next == null)//若是e後面沒有Node結點 newTab[e.hash & (newCap - 1)] = e;//直接對e的hash值對新的數組長度求模得到存儲位置 else if (e instanceof TreeNode)//若是e是紅黑樹的類型,那麼添加到紅黑樹中 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next;//將Node結點的next賦值給next if ((e.hash & oldCap) == 0) {//若是結點e的hash值與原hash桶數組的長度做與運算爲0 if (loTail == null)//若是loTail爲null loHead = e;//將e結點賦值給loHead else loTail.next = e;//不然將e賦值給loTail.next loTail = e;//而後將e複製給loTail } else {//若是結點e的hash值與原hash桶數組的長度做與運算不爲0 if (hiTail == null)//若是hiTail爲null hiHead = e;//將e賦值給hiHead else hiTail.next = e;//若是hiTail不爲空,將e複製給hiTail.next hiTail = e;//將e複製個hiTail } } while ((e = next) != null);//直到e爲空 if (loTail != null) {//若是loTail不爲空 loTail.next = null;//將loTail.next設置爲空 newTab[j] = loHead;//將loHead賦值給新的hash桶數組[j]處 } if (hiTail != null) {//若是hiTail不爲空 hiTail.next = null;//將hiTail.next賦值爲空 newTab[j + oldCap] = hiHead;//將hiHead賦值給新的hash桶數組[j+舊hash桶數組長度] } } } } } return newTab; }
問題:怎麼用二叉樹將中綴表達式轉爲後綴表達式
解答:一開始真的沒有什麼思路。後來通過長時間的思考以及在諮詢過老師以後,大概知道了思路,具體思路就是將輸入的中綴表達式根據優先級的順序插入到樹中(以運算符爲根,而後優先級的優先的輸入到左子樹中)具體操做在註釋中
public Translation(){ tree = new Stack<ExpressionTree>(); stack = new Stack(); } private ExpressionTree getOperand(Stack<ExpressionTree> treeExpression){ ExpressionTree num; num = treeExpression.pop(); return num; } public ExpressionTree Translate(String expression){ ExpressionTree operand1,operand2; char operator; String tempToken; Scanner parser = new Scanner(expression); while(parser.hasNext()){ tempToken = parser.next();//這個方法就是相似於StringTokenizer方法將一個字符串根據分割符分紅不一樣的幾個字符塊 operator=tempToken.charAt(0);//引用字符塊開頭的字節碼 if ((operator == '+') || (operator == '-') || (operator=='*') || (operator == '/'))//判斷是否爲運算符{ if (stack.empty()) stack.push(tempToken);//當儲存符號的棧爲空時,直接進棧 else{ String a =stack.peek()+"";//由於當ope.peek()='-'時,計算機認爲ope.peek()=='-'爲false,因此要轉化爲string 使用equals()方法 if (((a.equals("+"))||(a.equals("-")))&&((operator=='*')||(operator=='/')))//這是判斷優先級的方法。 stack.push(tempToken);//當獲得的符號的優先級大於棧頂元素時,直接進棧 else { String s = String.valueOf(stack.pop()); char temp = s.charAt(0); operand1 = getOperand(tree);//這是將數字實例化成樹的類型 operand2 = getOperand(tree);//這是將數字實例化成樹的類型 tree.push(new ExpressionTree(new ExpressionTreeOp(1, temp, 0), operand2, operand1));//這是以運算符爲根節點,而後以運算數爲左右子節點構建二叉樹。 stack.push(operator); }//當獲得的符號的優先級小於棧頂元素或者優先級相同時時,數字棧出來兩個運算數,造成新的樹進棧 } } else tree.push(new ExpressionTree(new ExpressionTreeOp(2,' ',Integer.parseInt(tempToken)), null, null)); } while(!stack.empty()){ String a = String.valueOf(stack.pop()); operator = a.charAt(0); operand1 = getOperand(tree);//這是將數字實例化成樹的類型 operand2 = getOperand(tree);//這是將數字實例化成樹的類型 tree.push(new ExpressionTree(new ExpressionTreeOp(1, operator, 0), operand2, operand1));//這是以運算符爲根節點,而後以運算數爲左右子節點構建二叉樹。 } return tree.peek(); } public String getTree() { return (tree.peek()).printTree(); } public int getResult(){ return tree.peek().evaluateTree(); } public String PostOrder(){ Iterator iterator = tree.peek().iteratorPostOrder(); String result=""; for (;iterator.hasNext();) result +=iterator.next()+" "; return result; } }
此次實驗難點在於實驗二和一些實驗四,即利用中序和後序遍從來生成二叉樹,和使用二叉樹將中綴表達式轉爲後綴表達式,至於其餘的實驗更加側重於練習。只有通過不斷地練習,熟悉,咱們才能更加熟練的使用各類類和方法,纔可以更加創造性的進行開發和應用。