課程:《程序設計與數據結構》
班級: 1723
姓名: 譚鑫
學號:20172305
實驗教師:王志強
實驗日期:2018年10月13日
必修/選修: 必修html
實驗二-1-實現二叉樹:就是對二叉樹的測試,二叉樹不像二叉查找樹同樣能夠進行添加刪除。因此,在構造一個二叉樹的時候須要不斷的new一下,把每個結點進行拼接才能構造一棵樹。getRight,contains,toString,preorder,postorder。java
- getRight和getLeft,是返回某結點的左側和右側的操做
- contains,是斷定指定目標是否在該樹中的操做
- toString,是將樹進行輸出
- preorder,是對樹進行前序遍歷
- postorder,是對樹進行後序遍歷
- 具體分析在第六週博客
實驗二-3-決策樹:實現一棵決策樹,這個實驗就是仿照書上第十章的背部疼痛診斷器來書寫的。我再此基礎上並無進行大的修改,只是仿照內容修改了讀取的文件內容。我所設計的決策樹內容如圖:算法
實驗二-6-紅黑樹:紅黑樹的問題是最麻煩的,根據每個樹的結點內容實現紅黑的添加,可是紅黑的添加會出現碰撞,這就須要咱們的具體分析過程,而TreeMap和TreeSet方法都是依靠紅黑樹來實現的。less
紅黑樹的性質:
- (1)每一個結點或者是黑色,或者是紅色
- (2)根結點是黑色
- (3)每一個葉結點是黑色(是指爲空的葉結點)
- (4)若是一個結點是紅色的,則它的子節點必須是黑色的
- (5)從一個結點到該結點的子孫結點的全部路徑上包含相同數目的黑結點
- TreeMap是一個有序的key-value集合,它是經過紅黑樹實現的。
- TreeMap繼承於AbstractMap,因此它是一個Map,即一個key-value集合。
- TreeMap實現了NavigableMap接口,意味着它支持一系列的導航方法。好比返回有序的key集合。
- TreeMap實現了Cloneable接口,意味着它能被克隆。
- TreeMap實現了java.io.Serializable接口,意味着它支持序列化。
- TreeMap的簡單測試:
- HashMap底層實現是鏈表數組而且實現了Map所有的方法。
- HashMap的key用Set存放,因此想作到key不容許重複,key對應的類須要重寫hashCode和equals方法。
- HashMap容許空鍵和空值,元素是無序的,並且順序會不定時改變。
- HashMap插入、獲取的時間複雜度基本是 O(1),遍歷整個 Map 須要的時間與 桶的長度成正比。
- HashMap兩個關鍵因子:初始容量、加載因子
- HashMap的簡單測試:
關於Map接口:Map接口表明由關鍵字以及它們的值組成的一些項的集合。關鍵字必須是惟一的,可是若干關鍵字的能夠映射到一些相同的值。所以,值不是惟一的。在SortedMap接口中,映射中的關鍵字保持邏輯上有序狀態。SortedMap接口的一種實現是TreeMap。在數組中咱們是經過數組下標來對其內容索引的,而在Map中咱們經過對象來對對象進行索引,用來索引的對象叫作key,其對應的對象叫作value。這就是咱們平時說的鍵值對。函數
// 默認構造函數。使用該構造函數,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)
//構建一個具備默認初始容量 (16) 和默認加載因子 (0.75) 的空哈希映像 HashMap() //構建一個哈希映像,而且添加映像m的全部映射 HashMap(Map<? extends K,? extends V> m) //構建一個擁有特定容量的空的哈希映像 HashMap(int initialCapacity) //構建一個擁有特定容量和加載因子的空的哈希映像 HashMap(int initialCapacity, float loadFactor)
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; }
public K ceilingKey(K key) { return keyOrNull(getCeilingEntry(key)); }
public Collection<V> values() { Collection<V> vs = values; return (vs != null) ? vs : (values = new Values()); }
public Set<Map.Entry<K,V>> entrySet() { EntrySet es = entrySet; return (es != null) ? es : (entrySet = new EntrySet()); }
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(); }
String key = null; Integer integer = null; Iterator iter = map.keySet().iterator(); while (iter.hasNext()) { key = (String)iter.next(); integer = (Integer)map.get(key); }
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; } private void inflateTable(int toSize) { int capacity = roundUpToPowerOf2(toSize);//capacity必定是2的次冪 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);//此處爲threshold賦值,取capacity*loadFactor和MAXIMUM_CAPACITY+1的最小值,capaticy必定不會超過MAXIMUM_CAPACITY,除非loadFactor大於1 table = new Entry[capacity]; initHashSeedAsNeeded(capacity); }
public V get(Object key) { Node<K,V> e; //仍是先計算 哈希值 return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; //tab 指向哈希表,n 爲哈希表的長度,first 爲 (n - 1) & hash 位置處的桶中的頭一個節點 if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //若是桶裏第一個元素就相等,直接返回 if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) return first; //不然就得慢慢遍歷找 if ((e = first.next) != null) { if (first instanceof TreeNode) //若是是樹形節點,就調用樹形節點的 get 方法 return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { //do-while 遍歷鏈表的全部節點 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
public V remove(Object key) { Node<K,V> e; // 定義一個節點變量,用來存儲要被刪除的節點(鍵值對) return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; // 調用removeNode方法 } final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; // 聲明節點數組、當前節點、數組長度、索引值 if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node<K,V> node = null, e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) node = ((TreeNode<K,V>)p).getTreeNode(hash, key); else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; modCount++; size--; afterNodeRemoval(node); return node; } } return null; }
public static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
問題1的解決方案:根據先序來查找根結點的位置,再在中序查找到根結點,經過根結點的位置分出左子樹和右子樹兩個部分。將每一部分從新當作一個樹,並在先序中查找到這部分的根結點(此爲根結點的右子結點),再進行右側部分,不斷地遞歸下去就會實現,當這個中序中的位置和後序中的元素一致,就會結束。
問題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; }
實驗二的六個實驗看似不少實際上就第二個、第四個和第六個實驗須要認真修改的。這三個實驗的不一樣提交時間能夠減小不小麻煩。可是實驗四的設計思路仍是很費勁,構思出可是不會用代碼實現,還有是否要加括號的問題,若是添加那麼就須要在優先級上進行改寫,好在最後寫出來了。感受這學期的實驗更多的是偏向於邏輯算法的,就像本次實驗這樣,須要更多的思路要考慮。