20172314 2018-2019-1《程序設計與數據結構》第六週學習總結

教材學習內容總結

概述

  • 樹是一種非線性結構,其元素被組織成一個層次結構。即n個結點組成的有限集合。
  • 樹由結點和邊構成,樹的根位於樹頂層的惟一結點。
  • 位於樹中較低層的結點是上一層的孩子html

    同一雙親的兩個節點稱爲兄弟node

    沒有任何孩子的結點稱爲葉子git

    一個至少含有一個孩子的非根結點稱爲一個內部結點數組

    根是樹中全部結點的祖先,沿着某一特定結點的路徑可到達的結點是該結點的子孫。函數

  • 結點的層就是從根節點到該結點的路徑長度。從根到該結點 的邊數目就是其路徑長度。
    學習

  • 樹的高度是指從根到葉子之間最遠路徑的長度。測試

    樹中任一結點能夠具備的最大孩子數目爲該樹的度。this

    對結點所含有的孩子數目無限制的樹稱爲廣義樹。lua

    每一個節點限制爲不超過n個孩子的樹稱爲一棵n元樹。.net

    節點最多含有兩個孩子的樹稱爲二叉樹。

    樹的全部葉子都位於同一層或相差不超過兩層,就稱該樹是平衡的。

    若是樹是平衡的且底層全部葉子都位於樹的左邊,則認爲該樹是徹底的。(即滿二叉樹去掉最下層右邊的若干個結點)。

    若是一個n元樹的全部葉子都位於同一層且每一結點要麼是一片葉子要麼正好具備n個孩子,則稱該樹是滿的。(滿二叉樹是特殊的徹底二叉樹)。

  • 二叉樹的第i層最多有2^(i-1)個結點
  • 深度爲k的二叉樹最多有(2^k)-1個結點
  • 具備n個結點的徹底二叉樹的高度爲(log2n)+1。

實現樹的策略

  • 計算策略
    • 將元素n的左孩子置於位置(2n+1),將元素的右孩子置於(2(n+1))。就是從根節點開始,往下依次分別計算左右孩子的位置,而後填入數組中。
  • 連接策略
    • 按照先來先服務的基準連續分配數組位置,而不是經過其在樹中的定位將樹元素指派到數組位置上,且不用考慮該樹的徹底性。每一結點存儲的是每個孩子的數組索引。數組中元素的順序僅僅由它們進入該樹的順序決定。假設下圖進入順序爲A C B E D F,那麼其數組索引依次是0 1 2 3 4 5,A的孩子是B C,因此A中儲存2 1 ,以此類推。
  • 二者的選擇:若是樹不徹底或只是相對徹底,計算策略會爲不包含數據的樹位置分配空間 ,容易浪費大量空間;模擬策略不會浪費空間,容許連續分配數組位置,可是增長了刪除樹中元素的成本,由於須要對剩餘元素進行移位或保留一個空閒列表。
  • 一棵含有m個元素的平衡n元樹具備的高度爲lognm。
  • 樹能夠比線性結構更有效,但樹有其特有的成本,n相對較小時,樹和線性結構並不存在顯著差異,當n愈來愈大時,樹的效率就顯現出來。

樹的遍歷

  • 前序遍歷
    • 從根結點開始,訪問每一結點及其孩子。沿某一路徑訪問直到其沒有孩子時,返回訪問其兄弟。
    • 順序如圖爲:A B D E C
  • 中序遍歷
    • 從根開始訪問結點左邊的孩子,而後是結點,再而後是任何剩餘結點。他是從中間開始訪問的,而且當某一結點沒有任何剩餘孩子時,會返回到前一節點。
    • 如圖順序爲:D B E A C
  • 後序結點
    • 從根結點開始,訪問結點的孩子,而後是該結點。首先訪問的是沒有任何孩子的結點,而後逐步向前返回訪問其餘的孩子。
    • 如圖順序爲D E B C A
  • 層序遍歷
    • 從根結點開始,訪問每一層的全部結點,一次一層。每一層先訪問左孩子後訪問右孩子。
    • 如圖順序爲:A B C D E

二叉樹

  • 二叉樹的操做
    • getRoot:返回指向二叉樹根的引用
    • ieEmpty:判斷該樹是否爲空
    • size:判斷樹中元素數目
    • contains:判斷指定目標是否在樹中
    • find:若是找到該元素,則返回指向其的引用
    • toString:返樹的字符串表示
    • iteratorInOrder:爲樹的中序遍歷返回一個迭代器
    • iteratorPreOrder:爲樹的前序遍歷返回一個迭代器
    • iteratorPostOrder:爲樹的後序遍歷返回一個迭代器
    • iteratorLevelOrder:爲樹的層序遍歷返回一個迭代器
  • BinaryTreeADT接口

使用二叉樹:表達式樹

  • 表達式樹的根及其內部結點包含着操做,且全部葉子也包含着操做數。對錶達式樹的求值是從下往上的。

  • Postfix類和PostfixEvaluator類的理解圖示更清楚,對於操做數,創建一個新的ExpressionTreeObj,構造一個ExoressionTree壓入棧中,遇到操做符,彈出棧頂的兩個ExpressionTrees,使用該操做符創建一個新的ExpressionTree壓入棧中。而且須要注意的是,該表達式樹棧的棧頂位於右邊。

    Postfix類的UML描述

背部疼痛診斷器

  • 決策樹:結點表示決策點,子結點表示在該決策點的可選項。決策樹的葉結點表示可能的推斷,這些推斷是基於答案得出的。
  • 簡單決策樹(只有是和否)能夠用二叉樹來建模。進行一個診斷時,從根結點的問題開始,根據答案直到到達葉結點爲止。
  • 決策樹有時候用做專家系統的基礎,專家系統是一種軟件,用於嘗試表示某個領域的專家知識。
  • 背部疼痛診斷決策樹

用鏈表實現二叉樹

  • 構造函數應處理兩種狀況:
    • 建立一棵空二叉樹
    • 用單個元素(但沒有孩子)建立一棵二叉樹
  • BinaryTreeNode類負責跟蹤儲存在每一個位置上的元素,以及指向每一個結點的左右子樹或孩子的指針。
  • 實現樹結點或二叉樹結點類的其餘可能:
    • 用包含方法來測試某結點是否爲葉子或內部結點(有沒有至少一個孩子),測試從根到該結點的深度,或計算左右子樹的高度。
    • 使用多態性,建立各類實現,如emptyTreeNode,innerTreeNode,leafTreeNode,他們能夠區分各類可能性。
  • find方法
    • 經過使用存儲在樹中的類的equals方法來斷定等同性,來遍歷該樹。
    • findAgain方法能夠用來區分find方法的第一個實例和隨後的每一個調用。
  • iteratorInOrder方法
    容許一個用戶類在中序遍歷中單步遍歷樹的元素,提供了使用一個集合來構建另外一個集合的另外一個例子。

教材學習中的問題和解決過程

  • 問題一:用鏈表實現二叉樹的find方法和findAgain書上說能夠區分find方法的第一個實例和隨後的每一個調用,並非很理解。
  • 問題一解決:因爲findAgain方法使用了遞歸,他就須要使用一個私有支持方法,由於第一個調用和隨後每一個調用的簽名和行爲多是不相同的。那就是說,若是沒有findAgain方法中的遞歸部分,那麼find方法就只能用來查找根結點,若是要查找內部結點的話,就須要使用遞歸部分,單純用find方法須要很複雜的程序實現。

  • 問題二:書上代碼ExpressionTree中的printTree有點複雜,較難理解。
  • 問題二解答:首先上網查找了一下部分代碼但願能找到解釋,而後就找到了侯澤洋同窗的博客...裏面有一樣的問題。而後他給我講解了一下具體思路。我把個人理解在代碼中進行了註釋。

    public String printTree() {
        UnorderedListADT<BinaryTreeNode<ExpressionTreeOp>> nodes = new ArrayUnorderedList<BinaryTreeNode<ExpressionTreeOp>>();
        UnorderedListADT<Integer> levelList = new ArrayUnorderedList<Integer>();
    
        BinaryTreeNode<ExpressionTreeOp> current;
        String result = "";
        int printDepth = this.getHeight();//樹的高度
        int possibleNodes = (int) Math.pow(2, printDepth + 1);//因爲根節點位置是0,最下層是第printDepth+1層,這個二叉樹可能的總結點數就是2的printDepth+1次方
        int countNodes = 0;//結點數目
    
        nodes.addToRear(root);//在無序列表中添加根部結點
        Integer currentLevel = 0;//當前層
        Integer previousLevel = -1;//前一層
        levelList.addToRear(currentLevel);//把當前層添加到層數鏈表中
    
        while (countNodes < possibleNodes) {//此樹不滿時
            countNodes = countNodes + 1;//從0開始加
            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 + " ";//每一層加空格,第n層有2^(n-1)個數
            } 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 {//若是當前結點爲空,給他分配空間,存一個null進去
                nodes.addToRear(null);
                levelList.addToRear(currentLevel + 1);
                nodes.addToRear(null);
                levelList.addToRear(currentLevel + 1);
                result = result + " ";
            }
        }
        return result;
        }

代碼調試中的問題和解決過程

  • 問題一:實例化LinkedBinaryTree時,若是使用構造函數

    public LinkedBinaryTree(T element, LinkedBinaryTree<T> left, LinkedBinaryTree<T> right) {
        root = new BinaryTreeNode<T>(element);
        root.setLeft(left.root);
        root.setRight(right.root);
    }

    當某個參數爲空時,如圖,就不能運行,產生空指針的錯誤提示。

  • 問題一解決:我開始的時候認爲參數有null能夠是一個空位,但運行出現空指針錯誤,而後把參數改成肯定的數後就不出錯了...實際上是一個很低級的錯誤,參數爲空的話就沒有指針了,固然不能運行(▼ヘ▼#)

  • 問題二:當樹中沒有要查找的結點時,因爲個人contain方法中使用到了find方法,當找不到時,就拋出異常,程序終止了。
    public boolean contains(T targetment) { if(find(targetment) != null) return true; return false; }
    如圖

  • 問題二解決:首先想到把find方法中的
    if (current == null) throw new ElementNotFoundException("LinkedBinaryTree");

    改成
    if (current == null) return null;
    這樣正好符合contains方法的需求,可是有一點小問題就是更改了find方法後,若是單純的使用find方法去查找一個不存在的數,那麼獲得的是一個null,程序並不會終止,但我感受問題不大,嘿嘿。

  • 問題三:不知道如何打印一棵樹
  • 問題三解決:首先想到在表達樹的代碼中有printTree方法,原理應該是如出一轍的,可是不能直接拿來用,由於它在打印表達樹時,UnorderedListADT<BinaryTreeNode<ExpressionTreeOp>> nodes = new ArrayUnorderedList<BinaryTreeNode<ExpressionTreeOp>>();
    BinaryTreeNode<ExpressionTreeOp> current;
    參數均爲ExpressionTreeOp,而其中既有操做數又有操做符,而在二叉樹的打印中只有數字結點,且有如圖衝突

    因此只須要把他改成泛型便可打印任意樹。
    UnorderedListADT<BinaryTreeNode<T>> nodes = new ArrayUnorderedList<BinaryTreeNode<T>>();
    BinaryTreeNode<T> current;

  • 問題四:在作哈希表的實驗時,鏈表出現錯誤

  • 問題四解決:首先在每次插入鏈表後進行輸出,找到了錯誤的地方

    如圖可見,當出現衝突的時候,會丟失本來在那個位置的數,因此就是insert方法中處理衝突的代碼有錯,檢查代碼後發現

    public void insert(LinkHash link) {
            int data = link.getData();
            LinkHash previous = null;
            LinkHash current = first;
            while(current!=null&&data>current.getData())
                previous = current;
                current = current.next;
                count++;
                alltime++;
            }
            if (previous == null) {
                first = link;
                alltime++;
            } else {
                previous.next = link;
                link.next = current;
                //count++;
                alltime++;
            }
        }

    while循環的條件while(current!=null&&data>current.getData())是錯誤的,有衝突的那個位置造成的鏈表是按前後順序排的,並不須要從小到大,因此說當1進去時,他比11小,因此直接執行else操做,就丟失原來的11了。應把條件改成while (current != null)便可。

代碼託管

上週考試錯題總結

沒有錯題

結對及互評

  • 20172305譚鑫譚鑫的博客一如既往的優秀,堅持不懈,每次記錄的都很詳細,對問題解決過程的每一步都有分析,給大佬抱拳。
  • 20172323王禹涵王禹涵的博客跟我有同樣的問題,就是那個printTree方法的理解,看他寫的很詳細。博客中關於解決問題的過程愈來愈詳細了,博客質量穩步提高。還有就是忽然發現王禹涵愈來愈專業了xswl

其餘

這周對於樹的學習屬於撥雲見日吧。想起來剛開始學Java的時候,我就在博客中寫過,單純的看書看完後容易一臉懵逼,這時候就選擇直接分析代碼,如今我又回到了當時的狀態,剛看完書的時候怎麼也理解不了一些方法,後來直接作pp,嘗試摸索一下就理解不少知識點。哎,Java這東西不能太較真,隨他去吧。

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積)
目標 5000行 30篇 400小時
第一週 0/0 1/1 8/8
第二週 1163/1163 1/2 15/23
第三週 774/1937 1/3 12/50
第四周 3596/5569 2/5 12/62
第五週 3329/8898 2/7 12/74
第六週 4541/13439 3/10 12/86

參考:

相關文章
相關標籤/搜索