1. 程式人生 > >20172305 2017-2018-2 《程式設計與資料結構》實驗二報告

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,是對樹進行後序遍歷
    • 具體分析在第六週部落格
  • 實現的操作相對簡單,只是遍歷的時候發現迭代器運用起來感覺很麻煩,所以就根據遍歷思想建立了四個簡單點的遍歷方法。
  • 實驗二-2-中序先序序列構造二叉樹:根據中序和先序的內容來判斷樹內元素的具體位置,通過先序來判斷根結點,再在中序來判斷根結點的左子樹和右子樹;在將兩個部分繼續在先序中查詢根結點,在中序中判斷結點的左右子樹,就會判斷出樹中各個結點的位置。
  • 實驗二-3-決策樹:實現一棵決策樹,這個實驗就是仿照書上第十章的背部疼痛診斷器來書寫的。我再此基礎上並沒有進行大的修改,只是仿照內容修改了讀取的檔案內容。我所設計的決策樹內容如圖:

  • 實驗二-4-表示式樹:通過輸入的中綴表示式利用樹來實現中綴轉字尾,並由二叉樹轉換成字尾表示式並輸出。
    在實現字尾的過程中,我們可以呼叫之前的書上程式碼來實現字尾表示式的轉換成數字。
  • 實驗二-5-二叉查詢樹:就是對二叉查詢樹的測試,二叉查詢樹會優於二叉樹可以進行新增刪除。所以,在構造一個二叉查詢樹的時候,把每一個結點內的內容加進去就可以。
  • 實驗二-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;
      }

其他

實驗二的六個實驗看似很多實際上就第二個、第四個和第六個實驗需要認真修改的。這三個實驗的不同提交時間可以減少不小麻煩。但是實驗四的設計思路還是很費勁,構思出但是不會用程式碼實現,還有是否要加括號的問題,如果新增那麼就需要在優先順序上進行改寫,好在最後寫出來了。感覺這學期的實驗更多的是偏向於邏輯演算法的,就像本次實驗這樣,需要更多的思路要考慮。

參考資料