20172303 2018-2019-1 《程序設計與數據結構》實驗二報告

20172303 2018-2019-1 《程序設計與數據結構》實驗二報告

  • 課程:《程序設計與數據結構》
  • 班級: 1723
  • 姓名: 範雯琪
  • 學號:20172303
  • 實驗教師:王志強
  • 助教:張師瑜/張之睿
  • 實驗日期:2018年11月5日
  • 必修/選修: 必修

實驗內容

本次實驗主要是關於樹的應用, 涉及了二叉樹、決策樹、表達式樹、二叉查找樹、紅黑樹五種樹的類型,是對最近學習內容第十章和第十一章的一個總結。html

節點一

  • 參考教材P212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder),用JUnit或本身編寫驅動類對本身實現的LinkedBinaryTree進行測試。

節點二

  • 基於LinkedBinaryTree,實現基於(中序,先序)序列構造惟一一棵二㕚樹的功能,好比給出先序ABDHIEJMNCFGKL和中序HDIBEMJNAFCKGL,構造出附圖中的樹,用JUnit或本身編寫驅動類對本身實現的功能進行測試。

節點三

  • 本身設計並實現一顆決策樹。

節點四

  • 輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,並輸出後綴表達式和計算結果(若是沒有用樹,則爲0分)。

節點五

  • 完成PP11.3。

節點六

實驗過程及結果

節點一——實現二叉樹

  • getRight:getRight操做用於返回根的右子樹。當樹爲空時,拋出錯誤,當樹不爲空時,經過遞歸返回根的右子樹。
public LinkedBinaryTree2<T> getRight()
    {
        if(root == null) {
            throw new EmptyCollectionException("BinaryTree");
        }
        LinkedBinaryTree2<T> result = new LinkedBinaryTree2<>();
        result.root = root.getRight();
        return result;
    }
  • containscontains操做的實現有兩種方法:一種是直接借用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;
    }
  • toStringtoString方法我借用了ExpressionTree類中的PrintTree方法,具體內容曾在第七週博客中說過。
  • preorderpreorder方法因爲有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);
    }
}
  • postOrderpostOrder方法與preorder方法相似,惟一的區別是後序遍歷先訪問左孩子,再訪問右孩子,最後訪問根結點,代碼和上面差很少就不放了。

測試結果

節點二——中序先序序列構造二叉樹

  • 已知先序遍歷和中序遍歷獲得二叉樹有三個步驟:
    • (1)找到根結點。由於先序遍歷按照先訪問根結點再訪問左右孩子的順序進行的,因此先序遍歷的第一個結點就是二叉樹的根。
    • (2)區分左右子樹。在肯定了根結點以後,在中序遍歷結果中,根結點以前的就是左子樹,根結點以後的就是右子樹。若是跟結點前邊或後邊爲空,那麼該方向子樹爲空;若是根節點前邊和後邊都爲空,那麼根節點已經爲葉子節點。
    • (3)分別對左右子樹再重複第1、二步直至徹底構造出該樹。
  • 在清楚了構造的步驟以後,實現就比較簡單了,在實現的過程當中用了遞歸的方法。
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對調便可。二叉查找樹的刪除操做有三種狀況,要依據這三種狀況來實現代碼,我在第七週博客教材內容總結中已經分析過了,就不在這裏貼代碼了。
  • 實現了removeMinremoveMax後,其實findMinfindMax就很簡單了,由於在實現刪除操做時首先先要找到最大/最小值,所以只要把找到以後的步驟刪掉,返回找到的最大值或最小值的元素便可。
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;
}

測試結果

節點六——紅黑樹分析

  • 在jdk1.8版本後,java對HashMap作了改進,在鏈表長度大於8的時候,將後面的數據存在紅黑樹中,以加快檢索速度。而TreeMap的實現原理就是紅黑樹,所以分析紅黑樹時咱們要分析HashMap和TreeMap的源碼。

HashMap

  • HashMap是一種基於哈希表(hash table)實現的map,哈希表(也叫關聯數組)一種通用的數據結構,大多數的現代語言都原生支持,其概念也比較簡單:key通過hash函數做用後獲得一個槽(buckets或slots)的索引(index),槽中保存着咱們想要獲取的值,以下圖所示:
  • HashMap的方法較多,此處選擇構造函數、get操做和remove操做進行分析。
  • 構造函數
    • HashMap遵循集合框架的約束,提供了一個參數爲空的構造函數和有一個參數且參數類型爲Map的構造函數。除此以外,還提供了兩個構造函數,用於設置HashMap的容量(capacity)與平衡因子(loadFactor)(平衡因子=|右子樹高度-左子樹高度|)。
    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);
    }
  • get操做
    • get操做用於返回指定鍵所映射的值;若是對於該鍵來講,此映射不包含任何映射關係,則返回null。
    • 這裏須要說明兩個東西:Entry——Entry實現了單向鏈表的功能,用next成員變量來級連起來。table[ ]——HashMap內部維護了一個爲數組類型的Entry變量table,用來保存添加進來的Entry對象。
    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;
    }
  • remove操做
    • remove操做用於在指定鍵存在的狀況下,今後映射中移除指定鍵的映射關係。
    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;
    }
  • HashMap中的紅黑樹位於HashMap內部類TreeNode<K,V>的定義,它繼承了LinkedHashMap.Entry<K,V>,包括了左旋右旋等操做。
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;
        }
    }
    //與紅黑樹相關的操做(此處略)
  • treeifyuntreeify
    • 樹化和反樹化(鏈表化),樹化。當哈希桶中的鏈表長度超過閾值(默認爲8)的話,就會對鏈表進行樹化。當節點刪除時,紅黑樹的大小低於閾值(默認爲8),退化成鏈表。
    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;
    }

TreeMap

  • TreeMap是用紅黑樹做爲基礎實現的,該映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的Comparator進行排序,具體取決於使用的構造方法。
  • TreeMap的基本操做containsKey、get、put和remove的時間複雜度是 log(n) ,這些操做的原理都與紅黑樹的刪除、添加、檢索操做的原理相同
  • put操做
    • put操做實現了將 Entry 放入二叉查找樹中,其中Entry表明內部結點。
    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;   
    }
  • remove操做
    • remove操做用於刪除樹中的指定結點。
    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; 
            } 
        } 
     }
  • get操做
    • 當TreeMap根據key來取出value時,使用get操做,而這個get操做是經過getEntry()方法實現的。
    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; 
     }

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

  • 問題1:在實現節點一的時候,輸出的並非遍歷結果而是地址
  • 問題1解決方法:說實話這就是一個第十章沒學好的殘留問題,當時學的時候我就沒有把這一部分補充完整,對於迭代器的使用也不熟練,完成節點一的過程當中,我想到的解決方法是從新寫了一個ArrayUnorderedList類的公有方法,將該無序列表直接輸出(代碼在節點一的過程當中有)。後來實驗結束後詢問同窗學會了將迭代器方法的遍歷結果輸出。
//之後序遍歷爲例
String result = "";
Iterator itr = tree.iteratorPostOrder();
    while (itr.hasNext()){
        result += itr.next() + " ";
    }
return result;
  • 問題2:在實現節點二的時候沒法輸出構造好的樹。
  • 問題2解決方法:經過Debug,首先肯定樹是構造好的,沒有出現樹爲空的狀況。
  • 那麼問題就應該是出在toString方法中,後來發現緣由出在了root上,在toString方法中,root從一開始就是空的,並無獲取到我構造的樹的根結點。
  • 而後我嘗試在ReturnBinaryTree類中加入了一個獲取根的方法,結果最後輸出的是根的地址。
  • 最後參考了餘坤澎同窗的代碼,把ReturnBinaryTree類中的方法放的toString所在的LinkedBinaryTree類中,由於此時它可以獲取到構造的樹的根節點,所以就能正常輸出了。
  • 問題3:在實現決策樹的過程當中,文件裏的內容爲何以這樣的順序排列?
  • 問題3解決方法:這個要結合DecisionTree類來看,首先第一行的13表明了這顆決策樹中的節點個數,因此在DecisionTree類中的int numberNodes = scan.nextInt();一句其實就是獲取文件的第一行記錄節點個數的值。接下來文件中按照層序遍歷的順序將二叉樹中的元素一一列出來,最後文件中的幾行數字其實表明了每一個結點及其左右孩子的位置(仍然按照層序遍歷的順序),而且是從最後一層不是葉子結點的那一層的結點開始,好比[3,7,8]就表明了層序遍歷中第3個元素的左孩子爲第7個元素,右孩子爲第8個元素。
  • 我剛開始把根結點設置成第1個元素髮現怎麼都對不上,後來發現這裏定義了根結點爲第0個元素,因此最後一個元素爲第12個元素而不是第13個。

其餘(感悟、思考等)

  • 其實本次實驗總體上來講仍是比較簡單的,惟一有難度的可能只有節點四和節點六。在這個過程當中幫我複習了不少,並且逼着我去解決了一些曾經在教材學習中不肯面對的問題,nice~~

參考資料

相關文章
相關標籤/搜索