本次實驗主要是關於樹的應用, 涉及了二叉樹、決策樹、表達式樹、二叉查找樹、紅黑樹五種樹的類型,是對最近學習內容第十章和第十一章的一個總結。html
getRight
操做用於返回根的右子樹。當樹爲空時,拋出錯誤,當樹不爲空時,經過遞歸返回根的右子樹。public LinkedBinaryTree2<T> getRight() { if(root == null) { throw new EmptyCollectionException("BinaryTree"); } LinkedBinaryTree2<T> result = new LinkedBinaryTree2<>(); result.root = root.getRight(); return result; }
contains
操做的實現有兩種方法:一種是直接借用find
方法,另外一種是從新寫一個。
find
方法,find
方法的做用是在二叉樹中找到指定目標元素,則返回對該元素的引用,因此當該元素的引用與查找的元素相同時返回true,不然返回false。public boolean contains(T targetElement) { if (find(targetElement) == targetElement){return true;} else {return false;} }
public boolean contains(T targetElement) { BinaryTreeNode node = root; BinaryTreeNode temp = root; //找到的狀況有三種:查找元素就是根,查找元素位於右子樹,查找元素位於左子樹。 //除了這三種狀況下其他狀況都找不到元素,所以初始設置爲false boolean result = false; //當樹爲空時,返回false if (node == null){ result = false; } //當查找元素就是根時,返回true if (node.getElement().equals(targetElement)){ result = true; } //對右子樹進行遍歷(在右子樹不爲空的狀況下)找到元素則返回true,不然對根的左子樹進行遍歷 while (node.right != null){ if (node.right.getElement().equals(targetElement)){ result = true; break; } else { node = node.right; } } //對根的左子樹進行遍歷,找到元素則返回true,不然返回false while (temp.left.getElement().equals(targetElement)){ if (temp.left.getElement().equals(targetElement)){ result = true; break; } else { temp = temp.left; } } return result; }
toString
方法我借用了ExpressionTree類
中的PrintTree
方法,具體內容曾在第七週博客中說過。preorder
方法因爲有inOrder
方法的參考因此挺好寫的,修改一下三條代碼(三條代碼分別代碼訪問根、訪問右孩子和訪問左孩子)的順序便可,使用了遞歸。在輸出時爲了方便輸出我從新寫了一個ArrayUnorderedList類
的公有方法,直接輸出列表,要比用迭代器輸出方便一些。public ArrayUnorderedList preOrder(){ ArrayUnorderedList<T> tempList = new ArrayUnorderedList<T>(); preOrder(root,tempList); return tempList; } protected void preOrder(BinaryTreeNode<T> node, ArrayUnorderedList<T> tempList) { if (node != null){ //從根節點開始,先訪問左孩子,再訪問右孩子 tempList.addToRear(node.getElement()); preOrder(node.getLeft(),tempList); preOrder(node.getRight(),tempList); } }
postOrder
方法與preorder
方法相似,惟一的區別是後序遍歷先訪問左孩子,再訪問右孩子,最後訪問根結點,代碼和上面差很少就不放了。public void initTree(String[] preOrder,String[] inOrder){ BinaryTreeNode temp = initTree(preOrder,0,preOrder.length-1,inOrder,0,inOrder.length-1); root = temp; } private BinaryTreeNode initTree(String[] preOrder,int prefirst,int prelast,String[] inOrder,int infirst,int inlast){ if(prefirst > prelast || infirst > inlast){ return null; } String rootData = preOrder[prefirst]; BinaryTreeNode head = new BinaryTreeNode(rootData); //找到根結點 int rootIndex = findroot(inOrder,rootData,infirst,inlast); //構建左子樹 BinaryTreeNode left = initTree(preOrder,prefirst + 1,prefirst + rootIndex - infirst,inOrder,infirst,rootIndex-1); //構建右子樹 BinaryTreeNode right = initTree(preOrder,prefirst + rootIndex - infirst + 1,prelast,inOrder,rootIndex+1,inlast); head.left = left; head.right = right; return head; } //尋找根結點在中序遍歷數組中的位置 public int findroot(String[] a, String x, int first, int last){ for(int i = first;i<=last; i++){ if(a[i] == x){ return i; } } return -1; }
DecisionTree
類的實現。
DecisionTree
的構造函數從文件中讀取字符串元素。存儲在樹結點中。而後建立新的結點,將以前定義的結點(或子樹)做爲內部結點的子結點。public DecisionTTree(String filename) throws FileNotFoundException { //讀取字符串 File inputFile = new File(filename); Scanner scan = new Scanner(inputFile); int numberNodes = scan.nextInt(); scan.nextLine(); int root = 0, left, right; //存儲在根結點中 List<LinkedBinaryTree<String>> nodes = new ArrayList<LinkedBinaryTree<String>>(); for (int i = 0; i < numberNodes; i++) { nodes.add(i,new LinkedBinaryTree<String>(scan.nextLine())); } //創建子樹 while (scan.hasNext()) { root = scan.nextInt(); left = scan.nextInt(); right = scan.nextInt(); scan.nextLine(); nodes.set(root, new LinkedBinaryTree<String>((nodes.get(root)).getRootElement(), nodes.get(left), nodes.get(right))); } tree = nodes.get(root); }
evaluate
方法從根結點開始處理,用current表示正在處理的結點。在循環中,若是用戶的答案爲N,則更新current使之指向左孩子,若是用戶的答案爲Y,則更新current使之指向右孩子,循環直至current爲葉子結點時結束,結束後返回current的根結點的引用。public void evaluate() { LinkedBinaryTree<String> current = tree; Scanner scan = new Scanner(System.in); while (current.size() > 1) { System.out.println (current.getRootElement()); if (scan.nextLine().equalsIgnoreCase("N")) { current = current.getLeft(); } else { current = current.getRight(); } } System.out.println (current.getRootElement()); }
public static String toSuffix(String infix) { String result = ""; //將字符串轉換爲數組 String[] array = infix.split("\\s+"); //存放操做數 Stack<LinkedBinaryTree> num = new Stack(); //存放操做符 Stack<LinkedBinaryTree> op = new Stack(); for (int a = 0; a < array.length; a++) { //若是是操做數,開始循環 if (array[a].equals("+") || array[a].equals("-") || array[a].equals("*") || array[a].equals("/")) { if (op.empty()) { //若是棧是空的,將數組中的元素創建新樹結點並壓入操做符棧 op.push(new LinkedBinaryTree<>(array[a])); } else { //若是棧頂元素爲+或-且數組的元素爲*或/時,將元素創建新樹結點並壓入操做符棧 if ((op.peek().root.element).equals("+") || (op.peek().root.element).equals("-") && array[a].equals("*") || array[a].equals("/")) { op.push(new LinkedBinaryTree(array[a])); } else { //將操做數棧中的兩個元素做爲左右孩子,操做符棧中的元素做爲根創建新樹 LinkedBinaryTree right = num.pop(); LinkedBinaryTree left = num.pop(); LinkedBinaryTree temp = new LinkedBinaryTree(op.pop().root.element, left, right); //將樹壓入操做數棧,並將數組中的元素創建新樹結點並壓入操做符棧 num.push(temp); op.push(new LinkedBinaryTree(array[a])); } } } else { //將數組元素創建新樹結點並壓入操做數棧 num.push(new LinkedBinaryTree<>(array[a])); } } while (!op.empty()) { LinkedBinaryTree right = num.pop(); LinkedBinaryTree left = num.pop(); LinkedBinaryTree temp = new LinkedBinaryTree(op.pop().root.element, left, right); num.push(temp); } //輸出後綴表達式 Iterator itr=num.pop().iteratorPostOrder(); while (itr.hasNext()){ result+=itr.next()+" "; } return result; }
removeMin
的實現方法,二叉查找樹有一個特殊的性質就是最小的元素存儲在樹的左邊,最大的元素存儲在樹的右邊。所以實現removeMax
方法只須要把removeMin
方法中全部的left和right對調便可。二叉查找樹的刪除操做有三種狀況,要依據這三種狀況來實現代碼,我在第七週博客教材內容總結中已經分析過了,就不在這裏貼代碼了。removeMin
和removeMax
後,其實findMin
和findMax
就很簡單了,由於在實現刪除操做時首先先要找到最大/最小值,所以只要把找到以後的步驟刪掉,返回找到的最大值或最小值的元素便可。public T findMin() throws EmptyCollectionException { T result; if (isEmpty()){ throw new EmptyCollectionException("LinkedBinarySearchTree"); } else { if (root.left == null){ result = root.element; } else { BinaryTreeNode<T> parent = root; BinaryTreeNode<T> current = root.left; while (current.left != null){ parent = current; current = current.left; } result = current.element; } } return result; } public T findMax() throws EmptyCollectionException { T result; if (isEmpty()){ throw new EmptyCollectionException("LinkedBinarySearchTree"); } else { if (root.right == null){ result = root.element; } else { BinaryTreeNode<T> parent = root; BinaryTreeNode<T> current = root.right; while (current.right != null){ parent = current; current = current.right; } result = current.element; } } return result; }
public HashMap(int initialCapacity, float loadFactor) { 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(); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); }
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; }
public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); //當指定鍵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; }
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { //設置紅黑樹父節點(鏈) TreeNode<K,V> parent; // red-black tree links //設置左節點 TreeNode<K,V> left; //設置右節點 TreeNode<K,V> right; //設置前置節點 TreeNode<K,V> prev; // needed to unlink next upon deletion //設置紅黑標誌 boolean red; //構造函數 TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } //返回根節點 final TreeNode<K,V> root() { for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; } } //與紅黑樹相關的操做(此處略)
treeify
和untreeify
final void treeify(Node<K,V>[] tab) { TreeNode<K,V> root = null; //循環整理 for (TreeNode<K,V> x = this, next; x != null; x = next) { //取出下一個鏈表節點 next = (TreeNode<K,V>)x.next; //將x節點的左右節點設置爲null x.left = x.right = null; //判斷當前紅黑樹是否有根節點 if (root == null) { x.parent = null; //設置顏色爲黑色(根節點爲黑色) x.red = false; //將x節點設置爲根節點 root = x; } //若當前紅黑樹存在根節點 else { //獲取x節點的key K k = x.key; //獲取x節點的hash int h = x.hash; //key的class Class<?> kc = null; //這一部分不是看得很懂,大概是從根節點遍歷,將x節點插入到紅黑樹中 //dir應該指的是樹的子樹的方向,-1爲左側,1爲右側 for (TreeNode<K,V> p = root;;) { int dir, ph; K pk = p.key; if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); TreeNode<K,V> xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { x.parent = xp; if (dir <= 0) xp.left = x; xp.right = x; root = balanceInsertion(root, x); break; } } } } //確保哈希桶指定位置存儲的節點是紅黑樹的根節點 moveRootToFront(tab, root); } final Node<K,V> untreeify(HashMap<K,V> map) { Node<K,V> hd = null, tl = null; //循環,將紅黑樹轉成鏈表 for (Node<K,V> q = this; q != null; q = q.next) { //構造一個普通鏈表節點 Node<K,V> p = map.replacementNode(q, null); if (tl == null) hd = p; else tl.next = p; tl = p; } return hd; }
public V put(K key, V value) { Entry<K,V> t = root; // 當根結點爲空時 if (t == null) { // 將新的key-value建立一個結點,並將該結點做爲根結點 root = new Entry<K,V>(key, value, null); modCount++; return null; } int cmp; //設置一個父結點 Entry<K,V> parent; Comparator<? super K> cpr = comparator; // 若是cpr不爲空,即代表採用定製排序 if (cpr != null) { do { // 將t的值賦給根結點 parent = t; // 拿新插入key和t的key進行比較 cmp = cpr.compare(key, t.key); // 若是新插入的key小於t的key,t等於t左邊的結點 if (cmp < 0) t = t.left; // 若是新插入的key大於t的key,t等於t右邊的結點 else if (cmp > 0) t = t.right; // 若是兩個 key 相等,新的 value 覆蓋原有的 value, // 並返回原有的 value else return t.setValue(value); } while (t != null); } else { //若是t的key爲空,拋出錯誤 if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; do { // 與上述操做相同 parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } // 將新插入的節點做爲parent節點的子節點 Entry<K,V> e = new Entry<K,V>(key, value, parent); // 若是新插入 key 小於 parent 的 key,則 e 做爲 parent 的左子節點 if (cmp < 0) parent.left = e; // 若是新插入 key 小於 parent 的 key,則 e 做爲 parent 的右子節點 else parent.right = e; // 修復紅黑樹 fixAfterInsertion(e); size++; modCount++; return null; }
private void deleteEntry(Entry<K,V> p) { modCount++; size--; // 若是被刪除結點的左子樹、右子樹都不爲空 if (p.left != null && p.right != null) { //用p結點的後繼結點代替p Entry<K,V> s = successor (p); p.key = s.key; p.value = s.value; p = s; } // 若是p的左結點存在,則用replacement表明左結點,不然表明右結點 Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { replacement.parent = p.parent; // 若是p沒有父結點,則 replacemment 變成父結點 if (p.parent == null) root = replacement; // 若是 p 結點是其父結點的左孩子,則用replacement進行賦值 else if (p == p.parent.left) p.parent.left = replacement; // 若是 p 結點是其父結點的右孩子,操做同上 else p.parent.right = replacement; p.left = p.right = p.parent = null; // 修復紅黑樹 if (p.color == BLACK) fixAfterDeletion(replacement); } // 若是 p 結點沒有父結點,設置根結點爲空 else if (p.parent == null) { root = null; } else { if (p.color == BLACK) // 修復紅黑樹 fixAfterDeletion(p); if (p.parent != null) { // 若是 p 是其父結點的左孩子 if (p == p.parent.left) p.parent.left = null; // 若是 p 是其父結點的右孩子 else if (p == p.parent.right) p.parent.right = null; p.parent = null; } } }
public V get(Object key) { // 根據指定key取出對應的Entry Entry>K,V< p = getEntry(key); // 返回該Entry所包含的value return (p==null ? null : p.value); } final Entry<K,V> getEntry(Object key) { // 若是comparator不爲null,代表程序採用定製排序 if (comparator != null) // 返回對於的key return getEntryUsingComparator(key); // 若是key爲空,拋出異常 if (key == null) throw new NullPointerException(); // 將key強制類型轉換爲Comparable Comparable<? super K> k = (Comparable<? super K>) key; // 從根結點開始 Entry<K,V> p = root; while (p != null) { // 用key與當前結點的key進行比較 int cmp = k.compareTo(p.key); // 若是key小於當前結點的key,繼續到當前結點的左子樹中進行檢索 if (cmp < 0) p = p.left; // 若是 key大於當前結點的key,繼續到當前結點的右子樹中進行檢索 else if (cmp > 0) p = p.right; else return p; } return null; }
ArrayUnorderedList類
的公有方法,將該無序列表直接輸出(代碼在節點一的過程當中有)。後來實驗結束後詢問同窗學會了將迭代器方法的遍歷結果輸出。//之後序遍歷爲例 String result = ""; Iterator itr = tree.iteratorPostOrder(); while (itr.hasNext()){ result += itr.next() + " "; } return result;
toString
方法中,後來發現緣由出在了root上,在toString
方法中,root從一開始就是空的,並無獲取到我構造的樹的根結點。ReturnBinaryTree
類中加入了一個獲取根的方法,結果最後輸出的是根的地址。ReturnBinaryTree
類中的方法放的toString
所在的LinkedBinaryTree
類中,由於此時它可以獲取到構造的樹的根節點,所以就能正常輸出了。DecisionTree
類來看,首先第一行的13表明了這顆決策樹中的節點個數,因此在DecisionTree
類中的int numberNodes = scan.nextInt();
一句其實就是獲取文件的第一行記錄節點個數的值。接下來文件中按照層序遍歷的順序將二叉樹中的元素一一列出來,最後文件中的幾行數字其實表明了每一個結點及其左右孩子的位置(仍然按照層序遍歷的順序),而且是從最後一層不是葉子結點的那一層的結點開始,好比[3,7,8]就表明了層序遍歷中第3個元素的左孩子爲第7個元素,右孩子爲第8個元素。