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

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

  • 課程:《程序設計與數據結構》
  • 班級: 1723
  • 姓名: 吳恆佚
  • 學號:20172321
  • 實驗教師:王志強
  • 實驗日期:2018年11月11日
  • 必修/選修: 必修

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、實驗過程及結果

實驗1

  • 本實驗要求實現getRight方法、contains方法、toString方法、preorder方法、postorder方法,此五個方法。

getRight方法

public LinkedBinaryTree1<T> getRight() {
        LinkedBinaryTree1 node = new LinkedBinaryTree1();
        node.root = root.getRight();
        return node;
    }

這個基本上是以前好久就實現的一個簡單的方法,先進行一個樹的初始化,能夠獲得一個新樹,而後經過下面一行的代碼獲得新的根,使咱們獲得合適的右子樹。html

contains方法

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

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

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

        return (current.getElement());
    }

這個方法其實自己沒有什麼東西,可是裏面須要用到find方法,首先用find方法查找這個結點,假如找到了這個結點就返回true,假如沒有找到就返回false。java

toString方法

public String toString() {
        UnorderedListADT<BinaryTreeNode<T>> nodes = new week7.jiumingdaima.ArrayUnorderedList<BinaryTreeNode<T>>();
        UnorderedListADT<Integer> levelList = new week7.jiumingdaima.ArrayUnorderedList<Integer>();
        BinaryTreeNode<T> current;
        String result = "";
        int printDepth = this.getHeight();
        int possibleNodes = (int) Math.pow(2, printDepth + 1);
        int countNodes = 0;

        nodes.addToRear(root);
        Integer currentLevel = 0;
        Integer previousLevel = -1;
        levelList.addToRear(currentLevel);

        while (countNodes < possibleNodes) {
            countNodes = countNodes + 1;
            current = nodes.removeFirst();
            currentLevel = levelList.removeFirst();
            if (currentLevel > previousLevel) {
                result = result + "\n\n";
                previousLevel = currentLevel;
                for (int j = 0; j < ((Math.pow(2, (printDepth - currentLevel))) - 1); j++)
                    result = result + " ";
            } else {
                for (int i = 0; i < ((Math.pow(2, (printDepth - currentLevel + 1)) - 1)); i++) {
                    result = result + " ";
                }
            }
            if (current != null) {
                result = result + (current.getElement()).toString();
                nodes.addToRear(current.getLeft());
                levelList.addToRear(currentLevel + 1);
                nodes.addToRear(current.getRight());
                levelList.addToRear(currentLevel + 1);
            } else {
                nodes.addToRear(null);
                levelList.addToRear(currentLevel + 1);
                nodes.addToRear(null);
                levelList.addToRear(currentLevel + 1);
                result = result + " ";
            }

        }

        return result;
    }

這個代碼直接使用的書上ExpressionTree類的代碼node

preorder方法

public Iterator<T> iteratorPreOrder() {
        ArrayUnorderedList<T> tempList = new ArrayUnorderedList<T>();
        preOrder(root, tempList);

        return new TreeIterator(tempList.iterator());
    }
    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) {
            // System.out.print(node.getElement()+" ");
            tempList.addToRear(node.getElement());
            preOrder(node.getLeft(), tempList);
            preOrder(node.getRight(), tempList);
        }
    }

這個方法要用到的迭代器方法和自己都是已經給出了的,我加了一段方便方法直接使用express

postorder方法

public ArrayUnorderedList postorder() {
        ArrayUnorderedList<T> tempList = new ArrayUnorderedList<T>();
        postOrder(root, tempList);
        return tempList;
    }

和前序遍歷同樣都是現成的方法,只是加了一段數組

結果

實驗2

  • 用給出的中序和先序來構建二叉樹

//根據前序和中序序列,創建二叉樹
    public int findroot(String[] S, String s, int begin, int end) {
        for (int i = begin; i <= end; i++) {
            if (S[i] == s) {
                return i;
            }
        }
        return -1;
    }

    public BinaryTreeNode getAtree(String[] preSort, int prestart, int preend, String[] inSort, int instart, int inend) {
        if (prestart > preend || instart > inend) {
            return null;
        }
        if (preSort.length != inSort.length) {
            try {
                throw new Exception("不知足條件的非法輸入!");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        BinaryTreeNode treeroot;
        String rootData = preSort[prestart];
        treeroot = new BinaryTreeNode(rootData);

        int rwhere = findroot(inSort, rootData, instart, inend);//找根節點的位置
        treeroot.left = getAtree(preSort, prestart + 1, prestart + rwhere - instart, inSort, instart, rwhere - 1);//左子樹
        treeroot.right = getAtree(preSort, prestart + rwhere - instart + 1, preend, inSort, rwhere + 1, inend);//右子樹

        return treeroot;

    }

    public void getAtree(String[] preOrder, String[] inOrder) {
        this.root = getAtree(preOrder, 0, preOrder.length - 1, inOrder, 0, inOrder.length - 1);
    }
  • 在上個實驗的二叉樹代碼中添加getAtree方法,結合前序和中序序列,找到根結點和左右子樹,而後對左右子樹分別遞歸使用加getAtree方法,逐步往下創建樹。
  • 已知先序遍歷和中序遍歷獲得二叉樹有三個步驟:數據結構

    找到根結點。由於先序遍歷按照先訪問根結點再訪問左右孩子的順序進行的,因此先序遍歷的第一個結點就是二叉樹的根。框架

    區分左右子樹。在肯定了根結點以後,在中序遍歷結果中,根結點以前的就是左子樹,根結點以後的就是右子樹。若是跟結點前邊或後邊爲空,那麼該方向子樹爲空;若是根節點前邊和後邊都爲空,那麼根節點已經爲葉子節點。ide

    分別對左右子樹再重複第1、二步直至徹底構造出該樹。函數

結果

實驗3

  • 本身寫一個決策樹,基本上就是仿造書上代碼。
  • 代碼就是書上的例題,若是要偷懶的話,直接把input.txt每一個選項改一下就行了,我改了一下二叉樹的形狀,結果下面那堆數字就要所有改一下了
5 9 10
7 11 12
8 13 14
3 5 6
4 7 8
2 3 4
0 1 2

結果

實驗4

  • 第四個實驗要求輸入中綴表達式,使用樹將中綴表達式轉換爲後綴表達式,並輸出後綴表達式和計算結果
  • 我以爲思路很簡單,就是先模仿以前的一個書上代碼——用後綴表達式獲得二叉樹,咱們就用中綴表達式獲得二叉樹,
public int evaluate(String expression) throws EmptyCollectionException {
        ExpressionTree operand1, operand2;
        char operator;
        String tempToken;

        Scanner parser = new Scanner(expression);

        while (parser.hasNext()) {
            tempToken = parser.next();
            operator = tempToken.charAt(0);

            if ((operator == '+') || (operator == '-') || (operator == '*') || (operator == '/'))
            {
                operand1 = getOperand(treeStack);
                operand2 = getOperand(treeStack);
                treeStack.push(new ExpressionTree(new ExpressionTreeOp(1, operator, 0), operand2, operand1));

            } else
            {
                treeStack.push(new ExpressionTree(new ExpressionTreeOp(2, ' ',
                        Integer.parseInt(tempToken)), null, null));
            }

        }

        return (treeStack.peek()).evaluateTree();
    }
  • 而後把這個二叉樹後序遍歷一遍就能夠了。
public void posorder() {
        System.out.println("後綴表達式爲: ");
        posOrder(root);
        System.out.println("");
    }

    public void posOrder(BinaryNode node) {
        if (node != null) {
            posOrder(node.getLeft());
            posOrder(node.getRight());
            System.out.print(node.getData() + " ");
        }
    }

結果

實驗5

  • 就是把之前的pp在運行一遍

removeMax方法

public T removeMax() throws EmptyCollectionException {
        // To be completed as a Programming Project
        T result = null;

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

            modCount--;
        }

        return result;
    }

findMin方法

public T findMin() throws EmptyCollectionException {
        // To be completed as a Programming Project
        T result = null;

        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;
    }

結果

實驗6

HashMap

- HashMap能夠說是Java中最經常使用的集合類框架之一,是Java語言中很是典型的數據結構,咱們總會在不經意間用到它,很大程度上方便了咱們平常開發。
- HashMap 是基於哈希表的 Map 接口的實現。此實現提供全部可選的映射操做,並容許使用 null 值和 null 鍵。(除了非同步和容許使用 null 以外,HashMap 類與 Hashtable 大體相同。)此類不保證映射的順序,特別是它不保證該順序恆久不變。

- HashMap 的實例有兩個參數影響其性能:初始容量 和加載因子。容量 是哈希表中桶的數量,初始容量只是哈希表在建立時的容量。加載因子 是哈希表在其容量自動增長以前能夠達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行 rehash 操做(即重建內部數據結構),從而哈希表將具備大約兩倍的桶數。

構造函數和關鍵方法

  • HashMap遵循集合框架的約束,提供了一個參數爲空的構造函數和有一個參數且參數類型爲Map的構造函數。除此以外,還提供了兩個構造函數,用於設置HashMap的容量(capacity)與平衡因子(loadFactor)(平衡因子=|右子樹高度-左子樹高度|)。
// 默認構造函數。
public HashMap() {
    // 設置「加載因子」
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    // 設置「HashMap閾值」,當HashMap中存儲數據的數量達到threshold時,就須要將HashMap的容量加倍。
    threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
    // 建立Entry數組,用來保存數據
    table = new Entry[DEFAULT_INITIAL_CAPACITY];
    init();
}

// 指定「容量大小」和「加載因子」的構造函數
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    // HashMap的最大容量只能是MAXIMUM_CAPACITY
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);

    // Find a power of 2 >= initialCapacity
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;

    // 設置「加載因子」
    this.loadFactor = loadFactor;
    // 設置「HashMap閾值」,當HashMap中存儲數據的數量達到threshold時,就須要將HashMap的容量加倍。
    threshold = (int)(capacity * loadFactor);
    // 建立Entry數組,用來保存數據
    table = new Entry[capacity];
    init();
}

// 指定「容量大小」的構造函數
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

// 包含「子Map」的構造函數
public HashMap(Map<? extends K, ? extends V> m) {
    this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                  DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
    // 將m中的所有元素逐個添加到HashMap中
    putAllForCreate(m);
}

  • containsKey()
    containsKey() 的做用是判斷HashMap是否包含key。
public boolean containsKey(Object key) {
    return getEntry(key) != null;
}

containsKey() 首先經過getEntry(key)獲取key對應的Entry,而後判斷該Entry是否爲null。
getEntry()的源碼以下:源碼分析

final Entry<K,V> getEntry(Object key) {
    // 獲取哈希值
    // HashMap將「key爲null」的元素存儲在table[0]位置,「key不爲null」的則調用hash()計算哈希值
    int hash = (key == null) ? 0 : hash(key.hashCode());
    // 在「該hash值對應的鏈表」上查找「鍵值等於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;
}

getEntry() 的做用就是返回「鍵爲key」的鍵值對,它的實現源碼中已經進行了說明。
這裏須要強調的是:HashMap將「key爲null」的元素都放在table的位置0處,即table[0]中;「key不爲null」的放在table的其他位置!

  • put()
    put() 的做用是對外提供接口,讓HashMap對象能夠經過put()將「key-value」添加到HashMap中。
public V put(K key, V value) {
    // 若「key爲null」,則將該鍵值對添加到table[0]中。
    if (key == null)
        return putForNullKey(value);
    // 若「key不爲null」,則計算該key的哈希值,而後將其添加到該哈希值對應的鏈表中。
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        // 若「該key」對應的鍵值對已經存在,則用新的value取代舊的value。而後退出!
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    // 若「該key」對應的鍵值對不存在,則將「key-value」添加到table中
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
  • 若要添加到HashMap中的鍵值對對應的key已經存在HashMap中,則找到該鍵值對;而後新的value取代舊的value,並退出!
  • 若要添加到HashMap中的鍵值對對應的key不在HashMap中,則將其添加到該哈希值對應的鏈表中,並調用addEntry()。
  • 說到addEntry(),就不得不說另外一個函數createEntry()。

    addEntry()通常用在 新增Entry可能致使「HashMap的實際容量」超過「閾值」的狀況下。例如,咱們新建一個HashMap,而後不斷經過put()向HashMap中添加元素;put()是經過addEntry()新增Entry的。在這種狀況下,咱們不知道什麼時候「HashMap的實際容量」會超過「閾值」;所以,須要調用addEntry()

createEntry() 通常用在 新增Entry不會致使「HashMap的實際容量」超過「閾值」的狀況下。例如,咱們調用HashMap「帶有Map」的構造函數,它繪將Map的所有元素添加到HashMap中;但在添加以前,咱們已經計算好「HashMap的容量和閾值」。也就是,能夠肯定「即便將Map中的所有元素添加到HashMap中,都不會超過HashMap的閾值」。此時,調用createEntry()便可。

  • get()
    get() 的做用是獲取key對應的value,它的實現代碼以下:
public V get(Object key) {
    if (key == null)
        return getForNullKey();
    // 獲取key的hash值
    int hash = hash(key.hashCode());
    // 在「該hash值對應的鏈表」上查找「鍵值等於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.equals(k)))
            return e.value;
    }
    return null;
}
  • remove()
    remove() 的做用是刪除「鍵爲key」元素
public V remove(Object key) {
    Entry<K,V> e = removeEntryForKey(key);
    return (e == null ? null : e.value);
}


// 刪除「鍵爲key」的元素
final Entry<K,V> removeEntryForKey(Object key) {
    // 獲取哈希值。若key爲null,則哈希值爲0;不然調用hash()進行計算
    int hash = (key == null) ? 0 : hash(key.hashCode());
    int i = indexFor(hash, table.length);
    Entry<K,V> prev = table[i];
    Entry<K,V> e = prev;

    // 刪除鏈表中「鍵爲key」的元素
    // 本質是「刪除單向鏈表中的節點」
    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)
                table[i] = next;
            else
                prev.next = next;
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }

    return e;
}

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

  • 問題1:在實現決策樹的時候,我發現了這堆看起來夏姬霸亂寫的東西他們實際上是有排列順序的,而後在我想順便改改就行時,居然不行!!!

  • 解決方案: 首先咱們能夠很容易的發現第一行的那個13是表明了方塊的個數。

由於這一大堆英語並非很直觀,我在他們前面進行了編號。而後在我本身試着寫的時候發現0 1 2必須在最下面一排,1是左孩子,2是右孩子,再上面一排數字必須是先安排右孩子和他的孩子們,就是說必須是

2 5 6
1 3 4

而不是

1 3 4
2 5 6

除非沒有右孩子

4、感想

  • 腦闊有點疼,好像每次寫實驗報告都是大過節的吧,上次國慶來着,不過每次實驗都能找出之前代碼裏的許多瑕疵和一些知識上的盲區,尤爲是此次的實驗4和實驗6,我又學到了好多新知識呢。。。滿臉開心

5、參考資料

相關文章
相關標籤/搜索