課程:《程序設計與數據結構》
班級: 1723
姓名: 趙曉海
學號:20172311
實驗教師:王志強
實驗日期:2018年11月2日
必修/選修: 必修html
參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder)java
用JUnit或本身編寫驅動類對本身實現的LinkedBinaryTree進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息git
課下把代碼推送到代碼託管平臺程序員
基於LinkedBinaryTree,實現基於(中序,先序)序列構造惟一一棵二㕚樹的功能,好比給出中序HDIBEMJNAFCKGL和後序ABDHIEJMNCFGKL,構造出附圖中的樹算法
用JUnit或本身編寫驅動類對本身實現的功能進行測試,提交測試代碼運行截圖,要全屏,包含本身的學號信息api
課下把代碼推送到代碼託管平臺數組
本身設計並實現一顆決策樹數據結構
提交測試代碼運行截圖,要全屏,包含本身的學號信息app
課下把代碼推送到代碼託管平臺框架
輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,並輸出後綴表達式和計算結果(若是沒有用樹,則爲0分)
提交測試代碼運行截圖,要全屏,包含本身的學號信息
課下把代碼推送到代碼託管平臺
完成PP11.3
提交測試代碼運行截圖,要全屏,包含本身的學號信息
課下把代碼推送到代碼託管平臺
參考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)
1.測試類代碼:
2.運行結果:
LinkedBinaryTree
LinkedBinaryTreeTest
對鏈式二叉樹的實現及應用有了較爲深刻的認識。
1.測試類代碼:
2.核心方法代碼:
3.運行截圖:
想起咱們課堂上親手作這個題的速度,嗯!和計算機的差距仍是蠻大的!
1.寫入文件:
2.測試類代碼及運行結果:
input
BackPainAnalyzer
DecisionTree
利用樹建立了本身的一個決策樹,仍是比較開心的。
1.測試類代碼:
2.用樹將中綴表達式轉換爲後綴表達式代碼:
3.測試結果代碼:
PostfixTester
PostfixEvaluator
實現了使用樹將中綴表達式轉爲後綴表達式的方法,雖然不包含有括號的狀況,但仍是挺開心的!
1.測試類代碼:
2.測試結果:
LinkedBinarySearchTree
LinkedBinarySearchTreeTest
較爲深刻的理解並實現了二叉查找樹。
有了10000行代碼的基礎,後面的學習提升要依靠代碼閱讀了,好比JUnit的源碼,JHotdraw的源碼,Java Collection API的源碼,Java JCE的源碼等
侯捷老師在《STL源碼剖析(jjhou)》一書中說:
我開玩笑地對朋友說,這本書出版,給大學課程中的「數據結構」和「算法」兩門授課老師出了個難題。幾乎全部可能的做業題目(複雜度證實題除外),本書都有了詳盡的解答。然而,若是學生可以從龐大>的 SGI STL 源碼中乾淨抽出某一部份,加上本身的包裝,作爲呈堂做業,也足以證實你有資格得到學分和高分。事實上,追蹤一流做品並於其中吸收養份,遠比本身關起門來寫個三流做品,價值高得多 >— 個人確認爲99.99 % 的程序員所寫的程序,在 SGI STL 面前都是三流水準。
1.首先查看HashMap和TreeMap的源代碼以後感受它們的一大特色就是註釋寫的很是完善並且格式有統一的標準,來一段HashMap的開頭感覺一下
package java.util; import java.io.IOException; import java.io.InvalidObjectException; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import jdk.internal.misc.SharedSecrets; /** * Hash table based implementation of the {@code Map} interface. This * implementation provides all of the optional map operations, and permits * {@code null} values and the {@code null} key. (The {@code HashMap} * class is roughly equivalent to {@code Hashtable}, except that it is * unsynchronized and permits nulls.) This class makes no guarantees as to * the order of the map; in particular, it does not guarantee that the order * will remain constant over time. * * <p>This implementation provides constant-time performance for the basic * operations ({@code get} and {@code put}), assuming the hash function * disperses the elements properly among the buckets. Iteration over * collection views requires time proportional to the "capacity" of the * {@code HashMap} instance (the number of buckets) plus its size (the number * of key-value mappings). Thus, it's very important not to set the initial * capacity too high (or the load factor too low) if iteration performance is * important. * * <p>An instance of {@code HashMap} has two parameters that affect its * performance: <i>initial capacity</i> and <i>load factor</i>. The * <i>capacity</i> is the number of buckets in the hash table, and the initial * capacity is simply the capacity at the time the hash table is created. The * <i>load factor</i> is a measure of how full the hash table is allowed to * get before its capacity is automatically increased. When the number of * entries in the hash table exceeds the product of the load factor and the * current capacity, the hash table is <i>rehashed</i> (that is, internal data * structures are rebuilt) so that the hash table has approximately twice the * number of buckets. * * <p>As a general rule, the default load factor (.75) offers a good * tradeoff between time and space costs. Higher values decrease the * space overhead but increase the lookup cost (reflected in most of * the operations of the {@code HashMap} class, including * {@code get} and {@code put}). The expected number of entries in * the map and its load factor should be taken into account when * setting its initial capacity, so as to minimize the number of * rehash operations. If the initial capacity is greater than the * maximum number of entries divided by the load factor, no rehash * operations will ever occur. * * <p>If many mappings are to be stored in a {@code HashMap} * instance, creating it with a sufficiently large capacity will allow * the mappings to be stored more efficiently than letting it perform * automatic rehashing as needed to grow the table. Note that using * many keys with the same {@code hashCode()} is a sure way to slow * down performance of any hash table. To ameliorate impact, when keys * are {@link Comparable}, this class may use comparison order among * keys to help break ties. * * <p><strong>Note that this implementation is not synchronized.</strong> * If multiple threads access a hash map concurrently, and at least one of * the threads modifies the map structurally, it <i>must</i> be * synchronized externally. (A structural modification is any operation * that adds or deletes one or more mappings; merely changing the value * associated with a key that an instance already contains is not a * structural modification.) This is typically accomplished by * synchronizing on some object that naturally encapsulates the map. * * If no such object exists, the map should be "wrapped" using the * {@link Collections#synchronizedMap Collections.synchronizedMap} * method. This is best done at creation time, to prevent accidental * unsynchronized access to the map:<pre> * Map m = Collections.synchronizedMap(new HashMap(...));</pre> * * <p>The iterators returned by all of this class's "collection view methods" * are <i>fail-fast</i>: if the map is structurally modified at any time after * the iterator is created, in any way except through the iterator's own * {@code remove} method, the iterator will throw a * {@link ConcurrentModificationException}. Thus, in the face of concurrent * modification, the iterator fails quickly and cleanly, rather than risking * arbitrary, non-deterministic behavior at an undetermined time in the * future. * * <p>Note that the fail-fast behavior of an iterator cannot be guaranteed * as it is, generally speaking, impossible to make any hard guarantees in the * presence of unsynchronized concurrent modification. Fail-fast iterators * throw {@code ConcurrentModificationException} on a best-effort basis. * Therefore, it would be wrong to write a program that depended on this * exception for its correctness: <i>the fail-fast behavior of iterators * should be used only to detect bugs.</i> * * <p>This class is a member of the * <a href="{@docRoot}/java/util/package-summary.html#CollectionsFramework"> * Java Collections Framework</a>. * * @param <K> the type of keys maintained by this map * @param <V> the type of mapped values * * @author Doug Lea * @author Josh Bloch * @author Arthur van Hoff * @author Neal Gafter * @see Object#hashCode() * @see Collection * @see Map * @see TreeMap * @see Hashtable * @since 1.2 */ public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { private static final long serialVersionUID = 362498820763181265L;
2.通過本身的一番分析以後感受毫無頭緒,不知道HashMap和TreeMap是用來幹嗎的,細節代碼更是看不懂。
3.上網查閱相關資料幫助理解分析。
HashMap簡介
HashMap 是一個散列表,它存儲的內容是鍵值對(key-value)映射。HashMap 繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable接口。簡單來講,HashMap由數組+鏈表組成的,數組是HashMap的主體,鏈表則是主要爲了解決哈希衝突而存在的,若是定位到的數組位置不含鏈表(當前entry的next指向null),那麼對於查找,添加等操做很快,僅需一次尋址便可;若是定位到的數組包含鏈表,對於添加操做,其時間複雜度爲O(n),首先遍歷鏈表,存在即覆蓋,不然新增;對於查找操做來說,仍需遍歷鏈表,而後經過key對象的equals方法逐一比對查找。因此,性能考慮,HashMap中的鏈表出現越少,性能纔會越好。
什麼是哈希表
咱們知道,數據結構的物理存儲結構只有兩種:順序存儲結構和鏈式存儲結構(像棧,隊列,樹,圖等是從邏輯結構去抽象的,映射到內存中,也這兩種物理組織形式),而在上面咱們提到過,在數組中根據下標查找某個元素,一次定位就能夠達到,哈希表利用了這種特性,哈希表的主幹就是數組。
好比咱們要新增或查找某個元素,咱們經過把當前元素的關鍵字 經過某個函數映射到數組中的某個位置,經過數組下標一次定位就可完成操做。存儲位置 = f(關鍵字)其中,這個函數f通常稱爲哈希函數,這個函數的設計好壞會直接影響到哈希表的優劣。
哈希衝突
然而萬事無完美,若是兩個不一樣的元素,經過哈希函數得出的實際存儲地址相同怎麼辦?也就是說,當咱們對某個元素進行哈希運算,獲得一個存儲地址,而後要進行插入的時候,發現已經被其餘元素佔用了,其實這就是所謂的哈希衝突,也叫哈希碰撞。前面咱們提到過,哈希函數的設計相當重要,好的哈希函數會盡量地保證 計算簡單和散列地址分佈均勻,可是,咱們須要清楚的是,數組是一塊連續的固定長度的內存空間,再好的哈希函數也不能保證獲得的存儲地址絕對不發生衝突。那麼哈希衝突如何解決呢?哈希衝突的解決方案有多種:開放定址法(發生衝突,繼續尋找下一塊未被佔用的存儲地址),再散列函數法,鏈地址法,而HashMap便是採用了鏈地址法,也就是數組+鏈表的方式。
HashMap的總體結構
HashMap的構造函數
HashMap共有4個構造函數,以下:
// 默認構造函數。 HashMap() // 指定「容量大小」的構造函數 HashMap(int capacity) // 指定「容量大小」和「加載因子」的構造函數 HashMap(int capacity, float loadFactor) // 包含「子Map」的構造函數 HashMap(Map<? extends K, ? extends V> map)
HashMap的API
void clear() Object clone() boolean containsKey(Object key) boolean containsValue(Object value) Set<Entry<K, V>> entrySet() V get(Object key) boolean isEmpty() Set<K> keySet() V put(K key, V value) void putAll(Map<? extends K, ? extends V> map) V remove(Object key) int size() Collection<V> values()
HashMap的繼承關係
從圖中能夠看出:
(01) HashMap繼承於AbstractMap類,實現了Map接口。Map是"key-value鍵值對"接口,AbstractMap實現了"鍵值對"的通用函數接口。
(02) HashMap是經過"拉鍊法"實現的哈希表。它包括幾個重要的成員變量:table, size, threshold, loadFactor, modCount。
table是一個Entry[]數組類型,而Entry實際上就是一個單向鏈表。哈希表的"key-value鍵值對"都是存儲在Entry數組中的。
size是HashMap的大小,它是HashMap保存的鍵值對的數量。
threshold是HashMap的閾值,用於判斷是否須要調整HashMap的容量。threshold的值="容量*加載因子",當HashMap中存儲數據的數量達到threshold時,就須要將HashMap的容量加倍。
loadFactor就是加載因子。
modCount是用來實現fail-fast機制的。
數據節點Entry的數據結構
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; // 指向下一個節點 Entry<K,V> next; final int hash; // 構造函數。 // 輸入參數包括"哈希值(h)", "鍵(k)", "值(v)", "下一節點(n)" Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } // 判斷兩個Entry是否相等 // 若兩個Entry的「key」和「value」都相等,則返回true。 // 不然,返回false public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } // 實現hashCode() public final int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } // 當向HashMap中添加元素時,繪調用recordAccess()。 // 這裏不作任何處理 void recordAccess(HashMap<K,V> m) { } // 當從HashMap中刪除元素時,繪調用recordRemoval()。 // 這裏不作任何處理 void recordRemoval(HashMap<K,V> m) { } }
HashMap的主要對外接口
1.clear()
clear() 的做用是清空HashMap。它是經過將全部的元素設爲null來實現的。
2.containsKey(Object key)
containsKey(Object key) 的做用是判斷HashMap是否包含key。
3.containsValue(Object value)
containsValue(Object value) 的做用是判斷HashMap是否包含「值爲value」的元素。
4.entrySet()、values()、keySet()
entrySet()的做用是返回「HashMap中全部Entry的集合」,它是一個集合.
5.get(Object key)
get(Object key) 的做用是獲取key對應的value。
6.put(K key, V value)
put(K key, V value) 的做用是對外提供接口,讓HashMap對象能夠經過put()將「key-value」添加到HashMap中。
7.putAll(Map<? extends K, ? extends V> m)
putAll(Map<? extends K, ? extends V> m) 的做用是將"m"的所有元素都添加到HashMap中。
8.remove(Object key)
remove(Object key) 的做用是刪除「鍵爲key」元素。
---
TreeMap 簡介
TreeMap 是一個有序的key-value集合,它是經過紅黑樹實現的。
TreeMap 繼承於AbstractMap,因此它是一個Map,即一個key-value集合。
TreeMap基於紅黑樹(Red-Black tree)實現。該映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。
TreeMap的基本操做 containsKey、get、put 和 remove 的時間複雜度是 log(n) 。
TreeMap的繼承關係
從圖中能夠看出:
(01) TreeMap實現繼承於AbstractMap,而且實現了NavigableMap接口。
(02) 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的構造函數
// 默認構造函數。使用該構造函數,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的API
Entry<K, V> ceilingEntry(K key) K ceilingKey(K key) void clear() Object clone() Comparator<? super K> comparator() boolean containsKey(Object key) NavigableSet<K> descendingKeySet() NavigableMap<K, V> descendingMap() Set<Entry<K, V>> entrySet() Entry<K, V> firstEntry() K firstKey() Entry<K, V> floorEntry(K key) K floorKey(K key) V get(Object key) NavigableMap<K, V> headMap(K to, boolean inclusive) SortedMap<K, V> headMap(K toExclusive) Entry<K, V> higherEntry(K key) K higherKey(K key) boolean isEmpty() Set<K> keySet() Entry<K, V> lastEntry() K lastKey() Entry<K, V> lowerEntry(K key) K lowerKey(K key) NavigableSet<K> navigableKeySet() Entry<K, V> pollFirstEntry() Entry<K, V> pollLastEntry() V put(K key, V value) V remove(Object key) int size() SortedMap<K, V> subMap(K fromInclusive, K toExclusive) NavigableMap<K, V> subMap(K from, boolean fromInclusive, K to, boolean toInclusive) NavigableMap<K, V> tailMap(K from, boolean inclusive) SortedMap<K, V> tailMap(K fromInclusive)
TreeMap的紅黑樹相關內容
1 數據結構
1.1 紅黑樹的節點顏色--紅色
private static final boolean RED = false;
1.2 紅黑樹的節點顏色--黑色
private static final boolean BLACK = true;
1.3 「紅黑樹的節點」對應的類。
static final class Entry<K,V> implements Map.Entry<K,V> { ... }
2 相關操做
2.1 左旋
private void rotateLeft(Entry<K,V> p) { ... }
2.2 右旋
private void rotateRight(Entry<K,V> p) { ... }
2.3 插入操做
public V put(K key, V value) { ... }
2.4 插入修正操做
紅黑樹執行插入操做以後,要執行「插入修正操做」。
目的是:保紅黑樹在進行插入節點以後,仍然是一顆紅黑樹
private void fixAfterInsertion(Entry<K,V> x) { ... }
2.5 刪除操做
private void deleteEntry(Entry<K,V> p) { ... }
2.6 刪除修正操做
紅黑樹執行刪除以後,要執行「刪除修正操做」。
目的是保證:紅黑樹刪除節點以後,仍然是一顆紅黑樹
private void fixAfterDeletion(Entry<K,V> x) { ... }
TreeMap的Entry相關函數
TreeMap的 firstEntry()、 lastEntry()、 lowerEntry()、 higherEntry()、 floorEntry()、 ceilingEntry()、 pollFirstEntry() 、 pollLastEntry() 原理都是相似的;下面以firstEntry()來進行詳細說明
咱們先看看firstEntry()和getFirstEntry()的代碼:
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() 是內部接口。並且,firstEntry() 是經過 getFirstEntry() 來實現的。那爲何外界不能直接調用 getFirstEntry(),而須要畫蛇添足的調用 firstEntry() 呢?
先告訴你們緣由,再進行詳細說明。這麼作的目的是:防止用戶修改返回的Entry。getFirstEntry()返回的Entry是能夠被修改的,可是通過firstEntry()返回的Entry不能被修改,只能夠讀取Entry的key值和value值。
如今咱們清晰的瞭解到:
(01) firstEntry()是對外接口,而getFirstEntry()是內部接口。
(02) 對firstEntry()返回的Entry對象只能進行getKey()、getValue()等讀取操做;而對getFirstEntry()返回的對象除了能夠進行讀取操做以後,還能夠經過setValue()修改值。
`
TreeMap的key相關函數
TreeMap的firstKey()、lastKey()、lowerKey()、higherKey()、floorKey()、ceilingKey()原理都是相似的;下面以ceilingKey()來進行詳細說明
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」。它的實現代碼以下:
final Entry<K,V> getCeilingEntry(K key) { Entry<K,V> p = root; while (p != null) { int cmp = compare(key, p.key); // 狀況一:若「p的key」 > key。 // 若 p 存在左孩子,則設 p=「p的左孩子」; // 不然,返回p if (cmp < 0) { if (p.left != null) p = p.left; else return p; // 狀況二:若「p的key」 < key。 } else if (cmp > 0) { // 若 p 存在右孩子,則設 p=「p的右孩子」 if (p.right != null) { p = p.right; } else { // 若 p 不存在右孩子,則找出 p 的後繼節點,並返回 // 注意:這裏返回的 「p的後繼節點」有2種可能性:第一,null;第二,TreeMap中大於key的最小的節點。 // 理解這一點的核心是,getCeilingEntry是從root開始遍歷的。 // 若getCeilingEntry能走到這一步,那麼,它以前「已經遍歷過的節點的key」都 > key。 // 能理解上面所說的,那麼就很容易明白,爲何「p的後繼節點」有2種可能性了。 Entry<K,V> parent = p.parent; Entry<K,V> ch = p; while (parent != null && ch == parent.right) { ch = parent; parent = parent.parent; } return parent; } // 狀況三:若「p的key」 = key。 } else return p; } return null; }
問題1:實驗二 樹-4-表達式樹在測試時出現邏輯錯誤,錯誤以下:
問題1解決方案:經單步調試發現符號的判斷出現問題,最後查閱資料瞭解到==不能判斷字符串是否相等,應該用.equals方法判斷字符串是否相等,改正以後以下:
本次實驗讓我充分認識到本身的不足,首先是在編碼能力方面,不少代碼我很久都編不出來,不得不上網查閱資料和詢問同窗;而後是個人代碼不夠規範,經過實驗6對Java API源碼的查看分析,我認識到之後編碼時應該儘可能規範。但同時我也學到了不少,好比我知道了在有必定編碼基礎以後要多多查閱一些高質量源碼,從優秀的代碼中去吸收精華,從而提升本身的編碼水平,而不是本身一味的編編編。總而言之,本次實驗讓我受益不淺。