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

20172323 2018-2019-1 《程式設計與資料結構》實驗二報告

課程:《程式設計與資料結構》
班級: 1723
姓名: 王禹涵
學號:20172323
實驗教師:王志強
實驗日期:2018年11月11日
必修/選修: 必修

1.實驗內容

1-實現二叉樹
參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder)

用JUnit或自己編寫驅動類對自己實現的LinkedBinaryTree進行測試,提交測試程式碼執行截圖,要全屏,包含自己的學號資訊

課下把程式碼推送到程式碼託管平臺


2-中序先序序列構造二叉樹
基於LinkedBinaryTree,實現基於(中序,先序)序列構造唯一一棵二㕚樹的功能,比如給出中序HDIBEMJNAFCKGL和後序ABDHIEJMNCFGKL,構造出附圖中的樹

用JUnit或自己編寫驅動類對自己實現的功能進行測試,提交測試程式碼執行截圖,要全屏,包含自己的學號資訊

課下把程式碼推送到程式碼託管平臺


3-決策樹
自己設計並實現一顆決策樹

提交測試程式碼執行截圖,要全屏,包含自己的學號資訊

課下把程式碼推送到程式碼託管平臺


4-表示式樹
輸入中綴表示式,使用樹將中綴表示式轉換為字尾表示式,並輸出字尾表示式和計算結果(如果沒有用樹,則為0分)

提交測試程式碼執行截圖,要全屏,包含自己的學號資訊

課下把程式碼推送到程式碼託管平臺


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)

2. 實驗過程及結果

實現二叉樹

第一部分沒有什麼難度,要求LinkedBinaryTree類實現的各種方法教材中都已經給出,所以只需編寫測試類方法進行測試就行了。實驗結果如下
第一張圖測試了contains、toString方法,toString使用的是表示式樹中的printTree方法


第二張圖展示了getRight和前序後序輸出方法的測試


中序先序序列構造二叉樹

第二部分解決的關鍵在如何通過中序和先序陣列推出樹的結構。首先先序輸出因為是從樹的根結點開始的,所以先序陣列的第一個就是整個二叉樹的根結點了。然後根據這個數到中序列表中去尋找對應的,以這個值為界,前半部分就是它的左子樹,後半部分就是它的右子樹。重複上述步驟就可以繼續劃分子樹的結構直到結束。
這個方法首先判斷前序和中序陣列是否為空或者兩個長度是否相等,如果為空或者不相等就丟擲異常,否則就進行構造樹的操作,首先取出前序列表第一個值,與中序列表進行比對,找到對應的值的位置,然後以此為界將它的左子樹的前序中序陣列和右子樹的前序中序陣列分別存入新的陣列中進行遞迴操作,直到最後每一個數組裡只存有一個數時,呼叫二叉樹的構造方法,就可以得到一棵二叉樹。
結果如圖


決策樹

第三部分的內容很簡單,參照書上程式碼背部疼痛診斷器的內容,修改其中的語句就可以了


表示式樹

這是實驗最難的一部分。這一部分思考了很久,結合了很多資料。主要是需要解決帶括號的問題,設定兩個連結串列,一個存放String字元、一個存放樹。通過split方法將中綴式斷開,判斷每一個字元,如果是加減運算子,就把它存到String連結串列的末尾,如果是乘除運算子,就判斷下一個字元是否是“(”,如果是的話,將括號裡的運算式提出來,運用遞迴演算法先將這一串小式子轉換成二叉樹,放入樹的連結串列中,如果不是的話,就以當前字元的下一個作為新樹的子樹,當前字元為根結點,從樹的連結串列中取出最末一位樹作為左子樹,形成一顆新樹再存回樹的連結串列中。如果是左括號,需要一直遍歷接下來的符號,直到找到右括號,把中間的式子提出來單獨構造新樹。如果是數字,那麼就直接放在樹的陣列中,當作只有根結點的樹。最後通過迭代器的方法進行輸出


二叉查詢樹

這一部分內容就是執行PP11.3,沒有什麼可以記錄的


紅黑樹分析

紅黑樹

  • 每個節點都只能是紅色或者黑色
  • 根節點是黑色
  • 每個葉子節點是黑色的
  • 如果一個節點是紅色的,則它的兩個子節點都是黑色的
  • 從任意一個節點到每個葉子節點的所有路徑都包含相同數目的黑色節點
    TreeMap
    首先是TreeMap是基於紅黑樹實現的,是一個有序的Key-value集合,繼承了AbstractMap,所以是一個Map即Key-value集合。它的基本操作包括了containsKey、get、put和remove方法
    TreeMap基於紅黑樹(Red-Black tree)實現。該對映根據其鍵的自然順序進行排序,或者根據建立對映時提供的 Comparator 進行排序,具體取決於使用的構造方法。
    紅黑樹包含了六個基本組成部分:key(鍵)、value(值)、left(左孩子)、right(右孩子)、parent(父節點)、color(顏色)。Entry節點根據key進行排序,Entry節點包含的內容為value。

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)

設定結點的顏色

private static final boolean RED = false;

private static final boolean BLACK = true;

Entry相關函式

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是對內的。這樣做是為了防止使用者修改返回的Entry,同時,getFirstEntry返回的物件除了可以進行getKey、getValue的操作之外,還可以通過setValue修改圖。


Key相關函式
ceilingKey(K key)的作用是“返回大於/等於key的最小的鍵值對所對應的KEY,沒有的話返回null”

public K ceilingKey(K key) {
    return keyOrNull(getCeilingEntry(key));
}

ceilingKey()是通過getCeilingEntry()實現的。keyOrNull()是獲取節點的key,沒有的話,返回null。

static <K,V> K keyOrNull(TreeMap.Entry<K,V> e) {
    return e == null? null : e.key;
}

getCeilingEntry(K key)的作用是“獲取TreeMap中大於/等於key的最小的節點,若不存在(即TreeMap中所有節點的鍵都比key大),就返回null”。


遍歷方法
遍歷的方式包括遍歷TreeMap的鍵值對(entrySet),遍歷TreeMap的鍵(KeySet)、遍歷TreeMap的值(value)三種。
通過keyIterator() 和 descendingKeyIterator()來說明,keyIterator()的作用是返回順序的KEY的集合,
descendingKeyIterator()的作用是返回逆序的KEY的集合。

Iterator<K> keyIterator() {
    return new KeyIterator(getFirstEntry());
}

final class KeyIterator extends PrivateEntryIterator<K> {
    KeyIterator(Entry<K,V> first) {
        super(first);
    }
    public K next() {
        return nextEntry().key;
    }
}

HashMap
HashMap由陣列+連結串列組成的,陣列是HashMap的主體.
HashMap的主幹是一個Entry陣列。Entry是HashMap的基本組成單元,每一個Entry包含一個key-value鍵值對。
建構函式

public HashMap(int initialCapacity, float loadFactor) {
     //此處對傳入的初始容量進行校驗,最大不能超過MAXIMUM_CAPACITY = 1<<30(230)
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
     
        init();//init方法在HashMap中沒有實際實現,不過在其子類如 linkedHashMap中就會有對應實現
    }

put操作

public V put(K key, V value) {
        //如果table陣列為空陣列{},進行陣列填充(為table分配實際記憶體空間),入參為threshold,此時threshold為initialCapacity 預設是1<<4(24=16)
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
       //如果key為null,儲存位置為table[0]或table[0]的衝突鏈上
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);//對key的hashcode進一步計算,確保雜湊均勻
        int i = indexFor(hash, table.length);//獲取在table中的實際位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        //如果該對應資料已存在,執行覆蓋操作。用新value替換舊value,並返回舊value
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;//保證併發訪問時,若HashMap內部結構發生變化,快速響應失敗
        addEntry(hash, key, value, i);//新增一個entry
        return null;
    }    

在常規構造器中,沒有為陣列table分配記憶體空間(有一個入參為指定Map的構造器例外),而是在執行put操作的時候才真正構建table陣列

3.程式碼託管

實驗二-1-實現二叉樹
實驗二 樹-2-中序先序序列構造二叉樹
實驗二 樹-3-決策樹
實驗二 樹-4-表示式樹
實驗二 樹-5-二叉查詢樹

4. 實驗過程中遇到的問題和解決過程

問題1:
問題1解決方案:

5.其他

這實驗,簡單的簡單難的難,那個紅黑樹的原始碼分析如果沒有資料幫扶,我真可能是一籌莫展,反正有些難受

6.參考資料

  • 《Java程式設計與資料結構教程(第二版)》

  • 《Java程式設計與資料結構教程(第二版)》學習指導