20172328《程序設計與數據結構》實驗二:樹

20172328《程序設計與數據結構》實驗二:樹

  • 課程:《軟件結構與數據結構》
  • 班級: 1723
  • 姓名: 李馨雨
  • 學號:20172328
  • 實驗教師:王志強老師
  • 實驗日期:2018年11月5日-2018年11月12日
  • 必修選修: 必修html

    1、實驗要求內容

  • 實驗1:實現二叉樹
  • 參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder)
    用JUnit或本身編寫驅動類對本身實現的LinkedBinaryTree進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息
  • 實驗2:中序先序序列構造二叉樹
  • 基於LinkedBinaryTree,實現基於(中序,先序)序列構造惟一一棵二㕚樹的功能,好比給出中序HDIBEMJNAFCKGL和後序ABDHIEJMNCFGKL,構造出附圖中的樹,用JUnit或本身編寫驅動類對本身實現的功能進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息
  • 實驗3:決策樹
  • 本身設計並實現一顆決策樹,提交測試代碼運行截圖,要全屏,包含本身的學號信息,課下把代碼推送到代碼託管平臺
  • 實驗4:表達式樹
  • 輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,並輸出後綴表達式和計算結果(若是沒有用樹,則爲0分),提交測試代碼運行截圖,要全屏,包含本身的學號信息,課下把代碼推送到代碼託管平臺
  • 實驗5:二叉查找樹
  • 完成PP11.3,提交測試代碼運行截圖,要全屏,包含本身的學號信息,課下把代碼推送到代碼託管平臺
  • 實驗6 : 紅黑樹分析
  • 參考本博客:點擊進入對Java中的紅黑樹(TreeMap,HashMap)進行源碼分析,並在實驗報告中體現分析結果。java

    2、實驗過程及結果

  • 實驗1:實現二叉樹的解決過程及結果
    數組

  • 實驗2:中序先序序列構造二叉樹的解決過程及結果
    安全

  • 實驗3:決策樹的解決過程及結果
    數據結構

  • 實驗4:表達式樹的解決過程及結果

    app

  • 實驗5:二叉查找樹的解決過程及結果

    框架

  • 實驗6 : 紅黑樹分析的解決過程及結果dom

    寫在前面:剛找到TreeMap和HashMap的源碼,實際上是有些慌張不知所措的,靜下心來看一看,發現實際上是對不少方法的註釋很長,因此兩個源碼都是很長。函數

  • 首先,咱們先要去了解Map是啥?Key是啥?而Value又是啥?源碼分析

  • 在數組中咱們是經過數組下標來對其內容索引的,而在Map中咱們經過對象來對對象進行索引,用來索引的對象叫作key,其對應的對象叫作value。這就是平時說的鍵值對Key - value。

  • HashMap和TreeMap最本質的區別:
    • HashMap經過hashcode方法對其內容進行快速查找,而 TreeMap中全部的元素都保持着某種固定的順序,若是你須要獲得一個有序的結果你就應該使用TreeMap,由於HashMap中元素的排列順序是不固定的。
  • HashMap 是一個散列表,它存儲的內容是鍵值對(key-value)映射。HashMap繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable接口。HashMap的實現不是同步的,這意味着它不是線程安全的。它的key、value均可覺得null。此外,HashMap中的映射不是有序的。在HashMap中經過get()來獲取value,經過put()來插入value,ContainsKey()則用來檢驗對象是否已經存在。能夠看出,和ArrayList的操做相比,HashMap除了經過key索引其內容以外,別的方面差別並不大。
  • TreeMap 是一個有序的key-value集合,它是經過紅黑樹實現的。TreeMap繼承於AbstractMap,因此它是一個Map,即一個key-value集合。TreeMap實現了NavigableMap接口,意味着它支持一系列的導航方法。好比返回有序的key集合。TreeMap實現了Cloneable接口,意味着它能被克隆。TreeMap實現了java.io.Serializable接口,意味着它支持序列化。TreeMap基於紅黑樹(Red-Blacktree)實現。該映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。TreeMap的基本操做 containsKey、get、put 和 remove 的時間複雜度是 log(n) 。
    另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

  • HashMap的源碼分析
  • HashMap的構造函數
// 默認構造函數。
HashMap()
// 指定「容量大小」的構造函數
HashMap(int capacity)
// 指定「容量大小」和「加載因子」的構造函數
HashMap(int capacity, float loadFactor)
// 包含「子Map」的構造函數
HashMap(Map<? extends K, ? extends V> map)
  • 關於HashMap構造函數的理解:
    • HashMap遵循集合框架的約束,提供了一個參數爲空的構造函數與有一個參數且參數類型爲Map的構造函數。除此以外,還提供了兩個構造函數,用於設置HashMap的容量(capacity)與平衡因子(loadFactor)。
    • 從代碼上能夠看到,容量與平衡因子都有個默認值,而且容量有個最大值
    • 默認的平衡因子爲0.75,這是權衡了時間複雜度與空間複雜度以後的最好取值(JDK說是最好的),太高的因子會下降存儲空間可是查找(lookup,包括HashMap中的put與get方法)的時間就會增長。
  • HashMap的繼承關係
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
  • 關於HashMap繼承和實現的理解:
    • 標記接口Cloneable,用於代表HashMap對象會重寫java.lang.Object#clone()方法,HashMap實現的是淺拷貝(shallow copy)。
    • 標記接口Serializable,用於代表HashMap對象能夠被序列化
    • HashMap是一種基於哈希表(hash table)實現的map,哈希表(也叫關聯數組)一種通用的數據結構,大多數的現代語言都原生支持,其概念也比較簡單:key通過hash函數做用後獲得一個槽(buckets或slots)的索引(index),槽中保存着咱們想要獲取的值.
  • HashMap的一些重要對象和方法
  • HashMap中存放的是HashMap.Entry對象,它繼承自Map.Entry,其比較重要的是構造函數。Entry實現了單向鏈表的功能,用next成員變量來級連起來。
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    int hash;
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }
    // setter, getter, equals, toString 方法省略
    public final int hashCode() {
        //用key的hash值與上value的hash值做爲Entry的hash值
        return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
    }
    /**
     * This method is invoked whenever the value in an entry is
     * overwritten by an invocation of put(k,v) for a key k that's already
     * in the HashMap.
     */
    void recordAccess(HashMap<K,V> m) {
    }
    /**
     * This method is invoked whenever the entry is
     * removed from the table.
     */
    void recordRemoval(HashMap<K,V> m) {
    }
    }
  • HashMap內部維護了一個爲數組類型的Entry變量table,用來保存添加進來的Entry對象。其實這是解決衝突的一個方式:鏈地址法(開散列法)
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
  • get操做
public V get(Object key) {
    //單獨處理key爲null的狀況
    if (key == null)
        return getForNullKey();
    Entry<K,V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
}
private V getForNullKey() {
    if (size == 0) {
        return null;
    }
    //key爲null的Entry用於放在table[0]中,可是在table[0]衝突鏈中的Entry的key不必定爲null
    //因此須要遍歷衝突鏈,查找key是否存在
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
        if (e.key == null)
            return e.value;
    }
    return null;
}
final Entry<K,V> getEntry(Object key) {
    if (size == 0) {
        return null;
    }
    int hash = (key == null) ? 0 : hash(key);
    //首先定位到索引在table中的位置
    //而後遍歷衝突鏈,查找key是否存在
    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;
}
  • put操做(含update操做)
private void inflateTable(int toSize) {
    //輔助函數,用於填充HashMap到指定的capacity
    // Find a power of 2 >= toSize
    int capacity = roundUpToPowerOf2(toSize);
    //threshold爲resize的閾值,超事後HashMap會進行resize,內容的entry會進行rehash
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
}
/**
 * Associates the specified value with the specified key in this map.
 * If the map previously contained a mapping for the key, the old
 * value is replaced.
 */
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    //這裏的循環是關鍵
    //當新增的key所對應的索引i,對應table[i]中已經有值時,進入循環體
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        //判斷是否存在本次插入的key,若是存在用本次的value替換以前oldValue,至關於update操做
        //並返回以前的oldValue
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    //若是本次新增key以前不存在於HashMap中,modCount加1,說明結構改變了
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
    //若是增長一個元素會後,HashMap的大小超過閾值,須要resize
    if ((size >= threshold) && (null != table[bucketIndex])) {
        //增長的幅度是以前的1倍
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
    //首先獲得該索引處的衝突鏈Entries,有可能爲null,不爲null
    Entry<K,V> e = table[bucketIndex];
    //而後把新的Entry添加到衝突鏈的開頭,也就是說,後插入的反而在前面(第一次還真沒看明白)
    //須要注意的是table[bucketIndex]自己並不存儲節點信息,
    //它就至關因而單向鏈表的頭指針,數據都存放在衝突鏈中。
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}
//下面看看HashMap是如何進行resize,廬山真面目就要揭曉了
void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    //若是已經達到最大容量,那麼就直接返回
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
    Entry[] newTable = new Entry[newCapacity];
    //initHashSeedAsNeeded(newCapacity)的返回值決定了是否須要從新計算Entry的hash值
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
/**
 * Transfers all entries from current table to newTable.
 */
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    //遍歷當前的table,將裏面的元素添加到新的newTable中
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            //最後這兩句用了與put放過相同的技巧
            //將後插入的反而在前面
            newTable[i] = e;
            e = next;
        }
    }
}
/**
 * Initialize the hashing mask value. We defer initialization until we
 * really need it.
 */
final boolean initHashSeedAsNeeded(int capacity) {
    boolean currentAltHashing = hashSeed != 0;
    boolean useAltHashing = sun.misc.VM.isBooted() &&
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    //這裏說明了,在hashSeed不爲0或知足useAltHash時,會重算Entry的hash值
    //至於useAltHashing的做用能夠參考下面的連接
    // http://stackoverflow.com/questions/29918624/what-is-the-use-of-holder-class-in-hashmap
    boolean switching = currentAltHashing ^ useAltHashing;
    if (switching) {
        hashSeed = useAltHashing
            ? sun.misc.Hashing.randomHashSeed(this)
            : 0;
    }
    return switching;
}
  • remove操做
public V remove(Object key) {
    Entry<K,V> e = removeEntryForKey(key);
    //能夠看到刪除的key若是存在,就返回其所對應的value
    return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
    if (size == 0) {
        return null;
    }
    int hash = (key == null) ? 0 : hash(key);
    int i = indexFor(hash, table.length);
    //這裏用了兩個Entry對象,至關於兩個指針,爲的是防治衝突鏈發生斷裂的狀況
    //這裏的思路就是通常的單向鏈表的刪除思路
    Entry<K,V> prev = table[i];
    Entry<K,V> e = prev;
    //當table[i]中存在衝突鏈時,開始遍歷裏面的元素
    while (e != null) {
        Entry<K,V> next = e.next;
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) {
            modCount++;
            size--;
            if (prev == e) //當衝突鏈只有一個Entry時
                table[i] = next;
            else
                prev.next = next;
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }
    return e;
}
  • TreeMap的源碼分析
  • 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)
  • TreeMap的繼承關係
public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
  • 關於TreeMap繼承和實現的理解:
    • TreeMap實現繼承於AbstractMap,而且實現了NavigableMap接口。
    • TreeMap的本質是R-B Tree(紅黑樹),它包含幾個重要的成員變量: root, size, comparator
    • root是紅黑數的根節點。它是Entry類型,Entry是紅黑數的結點,它包含了紅黑數的6個基本組成成分:key(鍵)、value(值)、left(左孩子)、right(右孩子)、parent(父節點)、color(顏色)。Entry結點根據key進行排序,Entry節點包含的內容爲value。
    • 紅黑數排序時,根據Entry中的key進行排序;Entry中的key比較大小是根據比較器comparator來進行判斷的。size是紅黑數中結點的個數。
  • TreeMap的一些重要方法:
  • 是否包含key結點:
public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }
  • 是否包含某個值:
public boolean containsValue(Object value) {
        for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
            if (valEquals(value, e.value))
                return true;
        return false;
    }
  • 返回某一TreeMap上的value值
public V get(Object key) {
        Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }
  • 將某一個特定的Map存入TreeMap並進行自動排序
public void putAll(Map<? extends K, ? extends V> map) {
        int mapSize = map.size();
        if (size==0 && mapSize!=0 && map instanceof SortedMap) {
            Comparator<?> c = ((SortedMap<?,?>)map).comparator();
            if (c == comparator || (c != null && c.equals(comparator))) {
                ++modCount;
                try {
                    buildFromSorted(mapSize, map.entrySet().iterator(),
                                    null, null);
                } catch (java.io.IOException | ClassNotFoundException cannotHappen) {
                }
                return;
            }
        }
        super.putAll(map);
    }
  • 返回某一個結點
final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }
  • 而後TreeMap中就是NavigableMap API 的方法,SubMaps、public Methods、View classes、Red-black mechanics了。

3、實驗過程當中遇到的問題和解決過程

  • 問題1:首先是在作實驗1的時候本身不能完整的輸出一個數,只能輸出包含樹根(有三個數)的二叉樹,鏈接在左孩子上的左子樹沒法正常輸出。
  • 問題1的解決:經過詢問王文彬同窗,他教我理解了我代碼中存在的問題,實際上是由於我構造了兩個子樹,但卻沒有鏈接在一塊兒造成一個完整的樹,修改後將左子樹left加入整個樹的構造中就能夠了。

  • 問題2:在實驗二我理解完前序輸出和中序輸出的奧妙以後,終於在苦苦的編碼戰鬥中寫完了程序時,測試一下,結果卻很適合給學長學姐們國考加油!

  • 解決過程:看圖說話

  • 問題3:在實驗三中個人決策樹被我獨具匠心的寫成了一個穿搭教程。可是可是在作的時候仍是出現了BUG,當時我記成了Y是通向左子樹、N是通向右子樹,因此我出現了前言不接後語的問題,當時由於記反了但本身又不知道找了很久的問題。還有就是我當時多加了兩個回答語句體,在讀文件的時候我把新連接的子樹順序放到了最後,結果出現了問題跳躍,不能銜接。
  • 經過認真的屢次研究修改,終於個人決策樹完美出道了。

  • 問題4和問題4的解決:在作實驗4的時候沒有思路,看了郭愷同窗的代碼理解了一些,是創建了兩個棧,兩個棧中存儲的數據類型分別是String和樹類型,Sring類型來解決操做符,樹類型的來解決操做數。

其餘(感悟、思考等)

我以爲不少東西理解和代碼實現不是一回事,理解了我也不知道如何精確下手,可是在編寫的時候我又能更深入的理解好多遍。雖然過程及其「撕心裂肺」,可是仍是要多多受虐,才能在下次受虐的時候減輕疼痛。

4、參考資料

相關文章
相關標籤/搜索