1. 程式人生 > >學號 2018-2019-1 《程序設計與數據結構》實驗二報告

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

分享 num cas http shc 復雜 java 比較 array

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

課程:《程序設計與數據結構》

班級: 1723
姓名: 康皓越
學號:20172326
實驗教師:王誌強
實驗日期:2018年11月10日
必修/選修: 必修

1.實驗內容

  1. 參考教材p212,完成鏈樹LinkedBinaryTree的實現(getRight,contains,toString,preorder,postorder)用JUnit或自己編寫驅動類對自己實現的LinkedBinaryTree進行測試,提交測試代碼運行截圖,要全屏,包含自己的學號信息。課下把代碼推送到代碼托管平臺
  2. 基於LinkedBinaryTree,實現基於(中序,先序)序列構造唯一一棵二?樹的功能,比如給出中序HDIBEMJNAFCKGL和後序ABDHIEJMNCFGKL,構造出附圖中的樹。用JUnit或自己編寫驅動類對自己實現的功能進行測試,提交測試代碼運行截圖,要全屏,包含自己的學號信息。課下把代碼推送到代碼托管平臺
  3. 自己設計並實現一顆決策樹,提交測試代碼運行截圖,要全屏,包含自己的學號信息,課下把代碼推送到代碼托管平臺
  4. 輸入中綴表達式,使用樹將中綴表達式轉換為後綴表達式,並輸出後綴表達式和計算結果(如果沒有用樹,則為0分),提交測試代碼運行截圖,要全屏,包含自己的學號信息,課下把代碼推送到代碼托管平臺
  5. 完成PP11.3,提交測試代碼運行截圖,要全屏,包含自己的學號信息,課下把代碼推送到代碼托管平臺
  6. 參考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)

2. 實驗過程及結果

實驗一

  • 實驗一要求實現鏈表二叉樹的部分功能,獲得右子樹,就是將根節點的右子樹返回即可,關鍵代碼如下
LinkedBinarySearchTree<T> result = new LinkedBinarySearchTree<>();
        result.root = root.getRight();
        return result;

contains方法為判斷是否存在目標元素,通過結合find方法,find方法是返回一個boolean值,在此基礎上,將找到的結點的元素值返回即可。部分關鍵代碼如下:

 public boolean contains(T targetElement)
    {
        if(find(targetElement)!=null)
            return true;
        else
            return false;
    }


    public T find(T targetElement) throws ElementNotFoundException
    {
        BinaryTreeNode<T> current = findNode(targetElement, root);

        if (current == null)
            throw new ElementNotFoundException("LinkedBinaryTree");

        return (current.getElement());
    }
    
private BinaryTreeNode<T> findNode(T targetElement,
                                        BinaryTreeNode<T> next)
    {
        if (next == null)
            return null;

        if (next.getElement().equals(targetElement))
            return next;

        BinaryTreeNode<T> temp = findNode(targetElement, next.getLeft());

        if (temp == null)
            temp = findNode(targetElement, next.getRight());

        return temp;
    }
  • toString方法,這個方法的實現參考了課本代碼,主要通過無序列表將樹中的元素進行逐層存儲,在結束了一層後,進行刪除。
  • 前序遍歷與後序遍歷。利用無序列表,前序的順序是根左右,後序是左右根,二者原理類似,以前序為例,從根開始,獲得每一個左結點。這是一個每步都相同,且重復的過程,因此使用遞歸實現。同時,值得註意的是,為了實現叠代器的接口,這些方法均是iterator型的。
public Iterator<T> iteratorPostOrder()
    {
        ArrayUnorderedList<T> tempList = new ArrayUnorderedList<T>();
        postOrder(root,tempList);

        return new TreeIterator(tempList.iterator());
    }


    protected void postOrder(BinaryTreeNode<T> node,
                             ArrayUnorderedList<T> tempList)
    {
        if (node !=null){
            postOrder(node.getLeft(),tempList);
            postOrder(node.getRight(),tempList);
            tempList.addToRear(node.getElement());
        }
    }
  • 技術分享圖片

實驗二 中序前序序列構造二叉樹

  • 我們知道,前(後)序定根,中序定序。如果只給出前序和後序是不能確定一棵二叉樹的。當通過後序確定了根後,中序對應的根的位置的左邊為左子樹,右邊為右子樹。此時繼續確定左子樹的根,以此類推。同樣,每步的具體內容相似,且重復,使用遞歸實現較為簡單。
public BinaryTreeNode initTree(String[] preorder, int s1, int e1, String[] inorder, int s2, int e2) {//s1是前序開始值,e1是結束值
        if (s1 > e1 || s2 > e2) {
            return null;
        }
        String rootE = preorder[s1];//前序定根
        BinaryTreeNode head = new BinaryTreeNode(rootE);
        int rootG = findRoot(inorder, rootE, s2, e2);//在中序中找到根的位置
        BinaryTreeNode left = initTree(preorder, s1 + 1, s1 + rootG - s2, inorder, s2, rootG - 1);//開始使用遞歸,通過根的索引值確定數組中各個子樹的位置,將其分割
        BinaryTreeNode right = initTree(preorder, s1 + rootG - s2 + 1, e1, inorder, rootG + 1, e2);
        head.setLeft(left);
        head.setRight(right);
        return head;
    }
  • 技術分享圖片

實驗三 自己設計並實現一顆決策樹

  • 設計其實很簡單,簡單的敲在txt文件裏就行。這個實驗設計到了這幾個點。文件輸入輸出流,將文件裏的內容轉化進入樹中,如何使得循環進行。
  • 通過Scanner將字符存入,再根據設計時自己規定的父結點與子結點將其逐行存入樹中。通過規定“N”為否定,其他均為肯定,所以經過與n比較判斷,來執行從父結點是到左子樹還是右子樹。從而達到決策效果。
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.getRight();
                else
                    current = current.getLeft();
            }

            System.out.println (current.getRootElement());
        }
  • 技術分享圖片
  • 技術分享圖片
  • 技術分享圖片

  • 技術分享圖片

實驗四 表達式樹

  • 輸入一個中綴表達式,通過將其添加入樹中,再通過後序遍歷輸出從而將其變為後綴表達式。主要問題是符號優先的順序。為了解決這個問題。利用列表,當遇到有*/號時,將符號前後的數字提取出來,單獨作為一個整體,存到樹中去
if(isOp(temp)&&isHighOp(temp)){//有高級符號
                BinaryTreeNode current = new BinaryTreeNode(temp);
                current.setLeft(numlist.remove(numlist.size()-1));
                num2 = scan.nextToken();
                current.setRight(new BinaryTreeNode(num2));
                numlist.add(current);
            }
  • 技術分享圖片

實驗五 完成pp11.3

  • removemax和removemin操作都較為簡單。因為根據查找二叉樹的性質。最小值在左子樹的最左端。最大值在右子樹的最右端。且刪除其值不需要考慮再平衡等問題。通過遍歷即可找到我們所需的值。
public T removeMin() throws EmptyCollectionException 
    {
        T result = null;

        if (isEmpty())
            throw new EmptyCollectionException("LinkedBinarySearchTree");
        else 
        {
            if (root.left == null) 
            {
                result = root.element;
                root = root.right;
            }
            else 
            {
                BinaryTreeNode<T> parent = root;
                BinaryTreeNode<T> current = root.left;
                while (current.left != null) 
                {
                    parent = current;
                    current = current.left;
                }
                result =  current.element;
                parent.left = current.right;
            }

            modCount--;
        }
  • 技術分享圖片

紅黑樹分析

TreeMap

TreeMap 是一個有序的key-value集合,它是通過紅黑樹實現的。
TreeMap 繼承於AbstractMap,所以它是一個Map,即一個key-value集合。
TreeMap 實現了NavigableMap接口,意味著它支持一系列的導航方法。比如返回有序的key集合。
TreeMap 實現了Cloneable接口,意味著它能被克隆。
TreeMap 實現了java.io.Serializable接口,意味著它支持序列化。
TreeMap基於紅黑樹(Red-Black tree)實現。該映射根據其鍵的自然順序進行排序,或者根據創建映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。
TreeMap的基本操作 containsKey、get、put 和 remove 的時間復雜度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的叠代器是fail-fastl的。

// 根據已經一個排好序的map創建一個TreeMap
    // 將map中的元素逐個添加到TreeMap中,並返回map的中間元素作為根節點。
    private final Entry<K,V> buildFromSorted(int level, int lo, int hi,
                         int redLevel,
                         Iterator it,
                         java.io.ObjectInputStream str,
                         V defaultVal)
        throws  java.io.IOException, ClassNotFoundException {

        if (hi < lo) return null;

      
        // 獲取中間元素
        int mid = (lo + hi) / 2;

        Entry<K,V> left  = null;
        // 若lo小於mid,則遞歸調用獲取(middel的)左孩子。
        if (lo < mid)
            left = buildFromSorted(level+1, lo, mid - 1, redLevel,
                   it, str, defaultVal);

        // 獲取middle節點對應的key和value
        K key;
        V value;
        if (it != null) {
            if (defaultVal==null) {
                Map.Entry<K,V> entry = (Map.Entry<K,V>)it.next();
                key = entry.getKey();
                value = entry.getValue();
            } else {
                key = (K)it.next();
                value = defaultVal;
            }
        } else { // use stream
            key = (K) str.readObject();
            value = (defaultVal != null ? defaultVal : (V) str.readObject());
        }

        // 創建middle節點
        Entry<K,V> middle =  new Entry<K,V>(key, value, null);

        // 若當前節點的深度=紅色節點的深度,則將節點著色為紅色。
        if (level == redLevel)
            middle.color = RED;

        // 設置middle為left的父親,left為middle的左孩子
        if (left != null) {
            middle.left = left;
            left.parent = middle;
        }

        if (mid < hi) {
            // 遞歸調用獲取(middel的)右孩子。
            Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel,
                           it, str, defaultVal);
            // 設置middle為left的父親,left為middle的左孩子
            middle.right = right;
            right.parent = middle;
        }

        return middle;
    }
  • 要理解buildFromSorted,重點說明以下幾點:
    第一,buildFromSorted是通過遞歸將SortedMap中的元素逐個關聯。
    第二,buildFromSorted返回middle節點(中間節點)作為root。
    第三,buildFromSorted添加到紅黑樹中時,只將level == redLevel的節點設為紅色。第level級節點,實際上是buildFromSorted轉換成紅黑樹後的最底端(假設根節點在最上方)的節點;只將紅黑樹最底端的階段著色為紅色,其余都是黑色。
  • TreeMap的put操作 TreeMap在進行put操作時,主要有以下步驟: (1)判斷樹是否是空的,如果是空的,直接將當前插入的k-v當做是根節點,完成了插入操作; (2)如果樹不是空的,獲取比較器(不管是自定義的比較器還是默認的比較器),對樹從根節點開始遍歷, (3)如果k小於結點的key,那麽開始遍歷左子節點,如果大於結點的key,開始遍歷右子節點,如果相等,說明k已經在TreeMap中存在了,就用新的value值覆蓋舊的value值,完成了插入操作; (4)如果k在TreeMap中不存在,將k插入到其相應的位置,此時由於樹的結構進行了變化,需要檢驗其是否滿足紅黑性的元素,調用fixAfterInsertion方法。

    HashMap

  • HashMap 是一個散列表,它存儲的內容是鍵值對(key-value)映射。
    HashMap 繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable接口。
    HashMap 的實現不是同步的,這意味著它不是線程安全的。它的key、value都可以為null。此外,HashMap中的映射不是有序的。

public V get(Object key) {

        if (key == null)

            return getForNullKey();

        int hash = hash(key.hashCode());

        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.equals(k)))
                return e.value;
        }
return null;

    }


    private V getForNullKey() {

        for (Entry<K,V> e = table[0]; e != null; e = e.next) {

            if (e.key == null)

                return e.value;

        }

        return null;

    }
  • HashMap的get操作相比put操作就簡單很多,首先是判斷key值是否為null,如果是null,則直接調用getForNummKey()方法去table[0]的位置查找元素,拿到table[0]處的鏈表進行遍歷,這裏只需要判斷鏈表中的key值是否是null,返回對應的value值。 如果key值不是null,則需要對key.hashCode再次計算hash值,調用indexFor方法拿到在table中的索引位置,獲取到對應的鏈表。拿到鏈表後,需要對鏈表進行遍歷,判斷hashCode值是否相等以及key值是否相等,共同判斷key值是否在HashMap中存在,從而拿到對應的value值。在這裏也就說明了為什麽把一個對象放入到HashMap的時候,最好是重寫hashCode()方法和equals方法,hashCode()可以確定元素在數組中的位置,而equals方法在鏈表比較的時候會用到。

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

  • 問題一:實驗三的問題
  • 技術分享圖片

  • 在用Scanner方法時,它是用從後往前的順序進行掃描的,註意這張圖,右邊比左邊多了一行,而且是空白的一行。我們先看一下nextLine的方法

    • nextLine
      public String nextLine()此掃描器執行當前行,並返回跳過的輸入信息。 此方法返回當前行的其余部分,不包括結尾處的行分隔符。當前位置移至下一行的行首。
      因為此方法會繼續在輸入信息中查找行分隔符,所以如果沒有行分隔符,它可能會緩沖所有輸入信息,並查找要跳過的行。
      返回:
      跳過的行
      拋出:
      NoSuchElementException - 如果未找到這樣的行
      IllegalStateException - 如果此掃描器已關閉
  • 那一行看似什麽都沒有,但實際上提供了一個換行符。正是有了這個換行符,掃描器可以繼續進行
  • 問題二:實驗四的問題
  • 技術分享圖片

  • 解決方案:轉化時輸出結果為null,
public void postfix(){
        LinkedBinaryTree lbt = new LinkedBinaryTree();
        lbt.root = btnode2;
        Iterator it = lbt.iteratorPostOrder();
        while(it.hasNext())
        System.out.print(it.next().toString()+" ");
    }

問題代碼,現在每次都將其實例化,但是卻並沒有得到存儲,隨著這個方法的每一次調用,都產生了一個新的樹,導致出現了空。

其他(感悟、思考等)

  • 本次實驗以最近的所學知識為基礎,並加以擴展。進一步加深了認識。

參考資料

  • http://www.cnblogs.com/rocedu/p/7483915.html

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