20172305 2017-2018-2 《程序設計與數據結構》實驗二報告
20172305 2017-2018-2 《程序設計與數據結構》實驗報告
課程:《程序設計與數據結構》
班級: 1723
姓名: 譚鑫
學號:20172305
實驗教師:王誌強
實驗日期:2018年10月13日
必修/選修: 必修
實驗內容
- 實驗二-1-實現二叉樹:
- (1)參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder)
- (2)用JUnit或自己編寫驅動類對自己實現的LinkedBinaryTree進行測試
- 實驗二-2-中序先序序列構造二叉樹:
- (1)基於LinkedBinaryTree,實現基於(中序,先序)序列構造唯一一棵二?樹的功能,比如給出中序HDIBEMJNAFCKGL和後序ABDHIEJMNCFGKL,構造出樹
- (2)用JUnit或自己編寫驅動類對自己實現的功能進行測試
- 實驗二-3-決策樹:
- (1)設計並實現一棵決策樹
- (2)用JUnit或自己編寫驅動類對自己實現的功能進行測試
- 實驗二-4-表達式樹:
- (1)輸入中綴表達式,使用樹將中綴表達式轉換為後綴表達式,並輸出後綴表達式和計算結果
- (2)用JUnit或自己編寫驅動類對自己實現的功能進行測試
- 實驗二-5-二叉查找樹:
- (1)完成PP11.3
- (2)用JUnit或自己編寫驅動類對自己實現的功能進行測試
- 實驗二-6-紅黑樹分析:
- (1)參考文件對Java中的紅黑樹(TreeMap,HashMap)進行源碼分析,並在實驗報告中體現分析結果。(C:\Program Files\Java\jdk-11.0.1\lib\src\java.base\java\util)
實驗過程及結果
實驗二-1-實現二叉樹:就是對二叉樹的測試,二叉樹不像二叉查找樹一樣可以進行添加刪除。所以,在構造一個二叉樹的時候需要不斷的new一下,把每一個結點進行拼接才能構造一棵樹。getRight,contains,toString,preorder,postorder。
- getRight和getLeft,是返回某結點的左側和右側的操作
- contains,是判定指定目標是否在該樹中的操作
- toString,是將樹進行輸出
- preorder,是對樹進行前序遍歷
- postorder,是對樹進行後序遍歷
- 具體分析在第六周博客
- 實現的操作相對簡單,只是遍歷的時候發現叠代器運用起來感覺很麻煩,所以就根據遍歷思想創建了四個簡單點的遍歷方法。
- LinkedBinaryTree
LinkedBinaryTreeTest
- 實驗二-2-中序先序序列構造二叉樹:根據中序和先序的內容來判斷樹內元素的具體位置,通過先序來判斷根結點,再在中序來判斷根結點的左子樹和右子樹;在將兩個部分繼續在先序中查詢根結點,在中序中判斷結點的左右子樹,就會判斷出樹中各個結點的位置。
- PreIntree
Test
實驗二-3-決策樹:實現一棵決策樹,這個實驗就是仿照書上第十章的背部疼痛診斷器來書寫的。我再此基礎上並沒有進行大的修改,只是仿照內容修改了讀取的文件內容。我所設計的決策樹內容如圖:
- DesicionTree
Test
- 實驗二-4-表達式樹:通過輸入的中綴表達式利用樹來實現中綴轉後綴,並由二叉樹轉換成後綴表達式並輸出。
在實現後綴的過程中,我們可以調用之前的書上代碼來實現後綴表達式的轉換成數字。- PostfixEvaluator
- InfixSuffixTree
Test
- 實驗二-5-二叉查找樹:就是對二叉查找樹的測試,二叉查找樹會優於二叉樹可以進行添加刪除。所以,在構造一個二叉查找樹的時候,把每一個結點內的內容加進去就可以。
- LinkedBinarySearchTree
LinkedBinarySearchTreeTest
實驗二-6-紅黑樹:紅黑樹的問題是最麻煩的,根據每一個樹的結點內容實現紅黑的添加,但是紅黑的添加會出現碰撞,這就需要我們的具體分析過程,而TreeMap和TreeSet方法都是依靠紅黑樹來實現的。
紅黑樹的性質:
- (1)每個結點或者是黑色,或者是紅色
- (2)根結點是黑色
- (3)每個葉結點是黑色(是指為空的葉結點)
- (4)如果一個結點是紅色的,則它的子節點必須是黑色的
- (5)從一個結點到該結點的子孫結點的所有路徑上包含相同數目的黑結點
- TreeMap和TreeSet是API的兩個重要成員,其中 TreeMap是Map接口的常用實現類,而TreeSet是Set接口的常用實現類。雖然TreeMap和TreeSet實現的接口規範不同,TreeMap實現的接口是Map,TreeSet實現的接口是Set,但TreeSet底層是通過TreeMap來實現的,因此二者的實現方式完全一樣。而TreeMap底層的實現就是用的紅黑樹數據結構來實現的。
- TreeMap是一個有序的key-value集合,它是通過紅黑樹實現的。
- TreeMap繼承於AbstractMap,所以它是一個Map,即一個key-value集合。
- TreeMap實現了NavigableMap接口,意味著它支持一系列的導航方法。比如返回有序的key集合。
- TreeMap實現了Cloneable接口,意味著它能被克隆。
- TreeMap實現了java.io.Serializable接口,意味著它支持序列化。
- TreeMap的簡單測試:
關於Set接口:Set接口代表不允許重復元的Collection。由接口SortedSet給出的一種特殊類型的Set保證其中的各項處於有序狀態。由於Set所要求的一些獨特的操作是一些插入、刪除以及有效的執行查找能力。對於Set,add方法如果執行成功就返回true,否則返回false,因為被添加項已經存在。保持各項有序狀態的Set的實現是TreeSet。
關於Map接口:Map接口代表由關鍵字以及它們的值組成的一些項的集合。關鍵字必須是唯一的,但是若幹關鍵字的可以映射到一些相同的值。因此,值不是唯一的。在SortedMap接口中,映射中的關鍵字保持邏輯上有序狀態。SortedMap接口的一種實現是TreeMap。
TreeMap和TreeSet的相同點:
- TreeMap和TreeSet都是有序的集合,也就是說他們存儲的值都是拍好序的。
- TreeMap和TreeSet都是非同步集合,因此他們不能在多線程之間共享,不過可以使用方法Collections.synchroinzedMap()來實現同步
- 運行速度都要比Hash集合慢,他們內部對元素的操作時間復雜度為O(logN),而HashMap/HashSet則為O(1)。
TreeMap和TreeSet的不同點:
- 最主要的區別就是TreeSet和TreeMap非別實現Set和Map接口
- TreeSet只存儲一個對象,而TreeMap存儲兩個對象Key和Value(僅僅key對象有序)
- TreeSet中不能有重復對象,而TreeMap中可以存在
// 默認構造函數。使用該構造函數,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)
//默認構造方法,根據其元素的自然順序進行排序
public TreeSet()
//構造一個包含指定 collection 元素的新TreeSet,它按照其元素的自然順序進行排序。
public TreeSet(Comparator<? super E> comparator)
//構造一個新的空TreeSet,它根據指定比較器進行排序。
public TreeSet(Collection<? extends E> c)
//構造一個與指定有序 set具有相同映射關系和相同排序的新 TreeSet。
public TreeSet(SortedSet<E> s)
- TreeMap的Empty方法,firstEntry()和getFirstEntry()都是用於獲取第一個節點。但是,firstEntry() 是對外接口;getFirstEntry()是內部接口。而且,firstEntry()是通過getFirstEntry()來實現的。兩個方法並會顯得很麻煩,因為通過firstEntry()方法和可以避免修改返回的Entry,這樣保證了完整性,而且產生了兩個方法,一個可以是避免修改的方法,一個可以是修改的方法。通過查閱資料可以總結出對firstEntry()返回的Entry對象只能進行getKey()、getValue()等讀取操作;而對getFirstEntry()返回的對象除了可以進行讀取操作之後,還可以通過setValue()修改值。
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;
}
- TreeMap的Key方法,返回大於/等於key的最小的鍵值對所對應的KEY,沒有的話返回null。
public K ceilingKey(K key) {
return keyOrNull(getCeilingEntry(key));
}
- TreeMap的values方法,values方法是通過new Values()來實現返回TreeMap中值的集合,而Values()正好是集合類Value的構造函數,這樣返回的是一個集合了。
public Collection<V> values() {
Collection<V> vs = values;
return (vs != null) ? vs : (values = new Values());
}
- TreeMap的entrySet方法,entrySet方法是返回TreeMap的所有鍵值對組成的集合,而且它單位是單個鍵值對。
public Set<Map.Entry<K,V>> entrySet() {
EntrySet es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
- TreeMap還有兩個遍歷方法,順序遍歷和逆序遍歷,順序遍歷,就是從第一個元素開始,逐個向後遍歷;而倒序遍歷則恰恰相反,它是從最後一個元素開始,逐個往前遍歷。
- TreeMap遍歷鍵值對的方法,先根據entrySet()獲取TreeMap的“鍵值對”的Set集合,再通過叠代器遍歷得到的集合。
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();
}
- TreeMap遍歷鍵的方法,先keySet()獲取TreeMap的鍵的Set集合,再通過叠代器遍歷得到的集合。
String key = null;
Integer integer = null;
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
key = (String)iter.next();
integer = (Integer)map.get(key);
}
實驗過程中遇到的問題和解決過程
- 問題1:如何利用先序和後序轉換成整棵樹?
問題1的解決方案:根據先序來查找根結點的位置,再在中序查找到根結點,通過根結點的位置分出左子樹和右子樹兩個部分。將每一部分重新看成一個樹,並在先序中查找到這部分的根結點(此為根結點的右子結點),再進行右側部分,不斷地遞歸下去就會實現,當這個中序中的位置和後序中的元素一致,就會結束。
- 問題2:如何利用樹實現中綴轉後綴得出結果?
問題2的解決方案:在上學期實現的四則運算的思路來看,整個運算式需要先不斷截取成一個字符,把運算符方法放到一個無序列表,將數放到一個定義為鏈表式樹類的無序列表。針對運算的邏輯順序,括號內的優先級最高,乘除的運算其次,最後是加減的運算。所以,在斷開存放的時候先判斷括號,如果遇到括號,那麽直至查找到另一半括號,中間的內容都為括號內的,將這一部分整體作為一個新的運算式通過遞歸不斷打開,使得括號內的內容拆成一棵樹,將這部分的樹存放到無序列表中。通過判斷截取存放的過程將運算符為加減的存放到無序列表,遇到乘除的時候將存放樹的無序列表取出最後一個(為乘除前一位內容可能是括號展成的樹或是數字),判斷後面是否為括號,如果為括號就取到另一半括號為止,這樣同樣是用到遞歸來實現拆分成樹,將取到的乘除作為根結點,前面的內容作為左子樹,後面的內容作為右子樹;如果是不為括號,那麽就是個數,就將數作為樹,這不過是一個只有根結點沒有左右結點的樹,加上之前的乘除號重新做一個樹,存放到無序列表中,最後該無序列表會只存放一個樹,只需輸出一下就好。
public BinaryTreeNode change(String strings){ //把“=”進行摳出 StringTokenizer tokenizer = new StringTokenizer(strings,"="); String string = tokenizer.nextToken(); //開始轉換 StringTokenizer stringTokenizer = new StringTokenizer(string," "); ArrayUnorderedList<String> arrayUnorderedList1 = new ArrayUnorderedList<String>(); ArrayUnorderedList<LinkedBinaryTree> arrayUnorderedList2 = new ArrayUnorderedList<LinkedBinaryTree>(); while (stringTokenizer.hasMoreTokens()){ strings = stringTokenizer.nextToken(); //判斷式子中是否有括號 boolean judge1 = true; if(strings.equals("(")){ String string1 = ""; while (judge1){ strings = stringTokenizer.nextToken(); //開始查詢括號的另一個,如果查到的話就會跳出該循環 if (!strings.equals(")")) string1 += strings + " "; else break; } LinkedBinaryTree linkedBinaryTree = new LinkedBinaryTree(); linkedBinaryTree.root = (change(string1)); arrayUnorderedList2.addToRear(linkedBinaryTree); continue; } //判斷運算符是否是加減的運算 if((strings.equals("+")|| strings.equals("-"))){ arrayUnorderedList1.addToRear(strings); }else ///判斷運算符是否是乘除的運算 if((strings.equals("*")|| strings.equals("/"))){ LinkedBinaryTree left = arrayUnorderedList2.removeLast(); String strings2 = strings; strings = stringTokenizer.nextToken(); if(!strings.equals("(")) { LinkedBinaryTree right = new LinkedBinaryTree(strings); LinkedBinaryTree node = new LinkedBinaryTree(strings2, left, right); arrayUnorderedList2.addToRear(node); }else { String string3 = ""; boolean judge2 = true; while (judge2){ strings = stringTokenizer.nextToken(); if (!strings.equals(")")) string3 += strings + " "; else break; } LinkedBinaryTree linkedBinaryTree = new LinkedBinaryTree(); linkedBinaryTree.root = (change(string3)); LinkedBinaryTree node = new LinkedBinaryTree(strings2,left,linkedBinaryTree); arrayUnorderedList2.addToRear(node); } }else arrayUnorderedList2.addToRear(new LinkedBinaryTree(strings)); } while(arrayUnorderedList1.size()>0){ LinkedBinaryTree left = arrayUnorderedList2.removeFirst(); LinkedBinaryTree right = arrayUnorderedList2.removeFirst(); String oper = arrayUnorderedList1.removeFirst(); LinkedBinaryTree node = new LinkedBinaryTree(oper,left,right); arrayUnorderedList2.addToFront(node); } root = (arrayUnorderedList2.removeFirst()).getRootNode(); return root; }
其他
實驗二的六個實驗看似很多實際上就第二個、第四個和第六個實驗需要認真修改的。這三個實驗的不同提交時間可以減少不小麻煩。但是實驗四的設計思路還是很費勁,構思出但是不會用代碼實現,還有是否要加括號的問題,如果添加那麽就需要在優先級上進行改寫,好在最後寫出來了。感覺這學期的實驗更多的是偏向於邏輯算法的,就像本次實驗這樣,需要更多的思路要考慮。
參考資料
- TreeMap
- TreeMap與TreeSet
20172305 2017-2018-2 《程序設計與數據結構》實驗二報告