20172302 《Java軟件結構與數據結構》第六週學習總結


2018年學習總結博客總目錄:第一週 第二週 第三週 第四周 第五週 第六週
html


教材學習內容總結

1.樹的概述及基本概念
(1)樹是一種非線性數據結構,其中的元素被組織成了一個層次結構.
(2)樹由一個包含結點和邊的集構成,其中元素被存儲在這些結點中,邊則將一個結點和另外一個結點鏈接起來。每一結點都位於該樹層次結構中的某一特頂層上。
(3)樹的根式位於該樹頂層的惟一結點。一棵樹只有一個根結點。
(4)位於樹中較底層的結點是上一層的孩子。一個結點只有一個雙親,但一個結點能夠有不少孩子。同一雙親的兩個結點互稱兄弟(sibling)
(5)根結點是惟一沒有雙親的結點。沒有任何孩子的結點稱爲葉子,一個至少有一個孩子的非根結點稱爲一個內部節點。

(6)一個結點在從根開始的路徑中位於另外一結點之上,那麼該結點就是它的祖先。根是樹中全部節點的最終祖先。沿着起始自某一特定結點的路徑能夠到達的結點是該結點的子孫(descendant)。
(7)結點的層也就是從根結點到該結點的路徑長度。經過計算從根到該結點所必須穿過的邊的數目,便是路徑長度。根位於0層。
(8)樹的高度是指從根到葉子之間最遠路徑的長度。
2.樹的分類
樹有多種分類方式,最重要的一個是樹中任一結點能夠具備的最大孩子的數目。這個值也稱爲樹的度(order)。java

  • 按度來分類
    對結點所含有孩子數目無限制的樹稱爲廣義樹。
    將每一節點限制爲不超過n個孩子的樹稱爲n元樹。結點有不超過兩個孩子的樹稱爲二叉樹。
  • 按平衡來分類
    (1)平衡樹:樹的全部葉子位於同一層或至少是彼此相差不超過一個層。
    在此定義下,一個含有m個元素的平衡n元樹具備的高度爲log(n)m。
    (2)非平衡樹
  • 按有無序分類
    樹中任意一個結點的各子樹按從左到右是有序的,稱爲有序樹,不然稱爲無序樹。

一些概念node

  • 徹底樹
    若某樹是平衡的,且底層全部葉子都位於樹的左邊,則該樹是徹底的。git

  • 滿樹
    若是一棵n元樹的全部葉子都位於同一層且每一結點要麼是一片葉子要麼正好具備n個孩子,則該樹是滿的。算法

2.實現樹的策略
(1)樹的數組實現之計算策略
       對於二叉樹而言,一種策略是:對於任何存儲在數組位置n處的元素而言,該元素的左孩子存儲在位置(2n+1)處,右孩子存儲在(2(n+1))處。但這樣作會出現大量浪費存儲空間的情況,好比這棵樹不是徹底的或只是相對徹底的。數組

(2)樹的數組實現之連接策略
如圖:
數據結構

3.樹的遍歷
對於這樣一棵樹,咱們對它進行四種遍歷
函數

(1)前序遍歷:從根節點開始,訪問每一結點及其孩子
訪問順序爲:A→B→D→E→C
僞代碼爲學習

Visit node
Traverse(left child)
Traverse(right child)

(2)中序遍歷:從根結點開始,訪問結點的左孩子,而後是該結點,最後是任何剩餘結點
訪問順序爲:D→B→E→A→C
僞代碼爲:測試

Traverse(left child)
Visit node
Traverse(right child)

(3)後序遍歷:從根結點開始,訪問節點的孩子,而後是該結點
訪問順序爲:D→E→B→C→A
僞代碼爲:

Traverse(left child)
Traverse(right child)
Visit node

(4)層序遍歷:從根結點開始,訪問每一層的全部結點,一次一層
訪問順序爲:A→B→C→D→E

4.二叉樹
二叉樹是樹的特殊一種,具備以下特色:一、每一個結點最多有兩顆子樹,結點的度最大爲2。二、左子樹和右子樹是有順序的,次序不能顛倒。三、即便某結點只有一個子樹,也要區分左右子樹。

  • 特殊二叉樹
    • 斜樹:全部的結點都只有左子樹的二叉樹叫左斜樹。全部結點都是隻有右子樹的二叉樹叫右斜樹。這二者統稱爲斜樹,線性表結構其實能夠理解爲樹的一種樹表達形式。

    • 滿二叉樹:
      全部的分支結點都存在左子樹和右子樹,而且全部的葉子結點都在同一層上,這樣就是滿二叉樹。就是完美元滿的意思,關鍵在於樹的平衡。
    • 徹底二叉樹:
      對一棵具備n個結點的二叉樹按層序排號,若是編號爲i的結點與一樣深度的滿二叉樹編號爲i結點在二叉樹中位置徹底相同,就是徹底二叉樹。滿二叉樹必須是徹底二叉樹,反過來不必定成立。
  • 二叉樹的性質:
    性質1:在二叉樹的第i層上至多有2^i-1個結點(i>=1)。
    性質2:深度爲k的二叉樹至多有2^k-1個結點(k>=1)。
    性質3:對任何一顆二叉樹T,若是其終端結點數爲n0,度爲2的 結點 數爲n2,則n0 = n2+1.
    性質4:具備n個結點的徹底二叉樹深度爲[log2n]+1 ([x]表示不 大於 x的最大整數)。
    性質5:若是對一顆有n個結點的徹底二叉樹(其深度爲[log2n]+1) 的結點按層序編號(從第1層到第[log2n]+1層,每層從左到右),對任意一個結點i(1<=i<=n)有:
    1).若是i=1,則結點i是二叉樹的根,無雙親;若是i>1,則其雙親是結 點[i/2]
    2).若是2i>n,則結點i無左孩子(結點i爲葉子結點);不然其左孩 子是結點2i。
    3).若是2i+1>n,則結點i無右孩子;不然其右孩子是結點2i+1。

  • 二叉樹的接口類——BinaryTreeADT
import java.util.Iterator;

public interface BinaryTreeADT<T>
{
    public T getRootElement();//返回二叉樹根引用指向的元素
    public boolean isEmpty();//斷定該樹是否爲空    
    public int size();//斷定樹中元素數目
    public boolean contains(T targetElement);//斷定指定目標是否在該樹中
    public T find(T targetElement);//若是找到指定元素,則返回指向其的引用
    public String toString();//返回樹的字符串表示
    public Iterator<T> iterator();
    public Iterator<T> iteratorInOrder();//爲樹的中序遍歷返回一個迭代器
    public Iterator<T> iteratorPreOrder(); //爲樹的前序遍歷返回一個迭代器
    public Iterator<T> iteratorPostOrder();//爲樹的後序遍歷返回一個迭代器
    public Iterator<T> iteratorLevelOrder();//爲樹的層序遍歷返回一個迭代器
}

5.使用二叉樹:表達式樹、背部疼痛診斷器

6.用鏈表實現二叉樹
       先要構建一個BinaryTreeNode類,它負責跟蹤存儲在每一個位置上的元素,以及指向每一個結點的左右子樹或孩子的指針。而後構建實現了BinaryTreeADT接口的LinkedBinaryTree類,須要跟蹤位於樹的根節點,以及樹的元素數目。

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

  • 問題1:關於書上代碼很難理解,尤爲是Expressiontree類下的printTree()方法。

  • 問題1解決方案:這一章的代碼都不太容易看懂,關於printTree()方法我一開始想就瞭解它是打印樹的一個方法就算了,不太願意細扣它是怎麼輸出的,後面仍是拿着紙把它從頭至尾推了一遍,弄懂了這裏,這裏分享一下。
           拿最簡單的 1+1爲例,咱們知道,經過eveluate方法後,棧頂元素已是一個樹的結構了,其中存儲「+」的結點是爲根節點,而兩個1分別是+的左結點和右結點。初始構建兩個無序列表,分別存儲結點、層數。建立一個當前結點,和result結果,樹的高度爲1(由於根結點處於第0層,其兩個子結點處於第1層),可能的結點數爲2^2,由於二叉樹前n層有不高於2^(n+1)個元素。繼續定義當前結點數爲0,進入循環。
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()-1;
        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;
    }

       第一次循環:countNodes=1;current=「+」所在結點,currentLevel = 0,此時currentLevel>previousLevel,result = 「\n\n」,previousLevel = 0,進入循環,循環一次,result = 「\n\n空」,而後current不等於null,進入,得result=「\n\n空+」,nodes中元素1所在結點、1所在結點,levelList中元素一、1。
       第二次循環:countNodes=2;current=「1」所在結點,currentLevel = 1,此時currentLevel>previousLevel,result = 「\n\n\空+\n\n」,previousLevel = 1,不能進入循環,result = 「\n\n\n\n空+\n\n」,而後current不等於null,進入,得result=「\n\n\n\n空+\n\n1」,nodes中1所在結點、null、null,levelList中元素一、二、2。
       第三次循環:countNodes=3;current=「1」所在結點,currentLevel = 1,此時currentLevel不大於previousLevel,故調用else中代碼,循環次數1次,得result=「\n\n\n\n空+\n\n1空」,而後current不等於null,進入,得result=「\n\n\n\n空+\n\n1空1」,nodes中null、null、null、null,levelList中元素二、二、二、2。
       第四次循環:countNodes=4;current =null,currentLevel = 2,此時currentLevel>previousLevel,result=「\n\n空+\n\n1空1\n\n」,previousLevel = 2,不能進入循環,此時current等於null,故調用else中代碼,nodes中null、null、null、null、null,levelList中元素二、二、二、三、3。result=「\n\n空+\n\n1空1\n\n空」。
       第五次,不能進入循環,輸出結果:result=「\n\n空+\n\n1空1\n\n空」,即result=「\n\n +\n\n1 1\n\n 」。IDEA運行結果如圖,與計算一致。

       循環完成以後,就該試着解釋每一個變量的在整個打印樹中的做用,nodes是用做存儲樹中各個結點所指向的元素,levelList是用做存儲各個元素它所對應的層數位置變量,currentLevelpreviousLevel共同在決定着是否該換行 ,便是否該跳至下一行。第一個if-else語句中循環是用做控制着空格的打印,以及該空幾格。第二個if-else語句是在將該結點的子結點及其層數分別添加到nodes和levelList中去。

  • 問題2:在運行背部疼痛診斷器時,老是出錯,後面才發現循環不能進行。

  • 問題2解決方案:這裏進行了一次以後,循環就會跳出,因而我就設置一個顯示,顯示當前current的size(),發現if (scan.nextLine().equalsIgnoreCase("N"))current = current.getLeft(); else current = current.getRight();運行後current.size()變爲1,這裏能夠看出應該是getLeft和getRight方法有問題,因而找到LinkedBinaryTree中的對應方法:
public LinkedBinaryTree<T> getLeft()
    {
        return  new LinkedBinaryTree(this.root.getLeft());
    }
public LinkedBinaryTree<T> getRight()
    {
        return  new LinkedBinaryTree(this.root.getRight());
    }

       這個代碼是從那裏直接拿過來的,覺得它是對的,也就沒有改動,再看才發現問題,按照這個代碼至關因而在取出root的左右孩子的結點元素,而後再拿它建立一個新的LinkedBinaryTree類的對象,這就是沒有了以前的樹的結構了,只是單單取出這一個結點元素建立新對象返回。而這樣,顯然,它是沒有以前本該有的孩子了。知道了是這樣,就把這個方法作了修改,如圖,(是修改的那部分)

protected LinkedBinaryTree<T> left,right;//定義兩個LinkedBinaryTree類型的初始變量left、right

// 修改構造函數,初始化兩個變量
public LinkedBinaryTree(T element, LinkedBinaryTree<T> left,
                            LinkedBinaryTree<T> right)
    {
        root = new BinaryTreeNode<T>(element);
        root.setLeft(left.root);
        root.setRight(right.root);
        this.left = left;
        this.right = right;
    }
//修改後的方法
public LinkedBinaryTree<T> getLeft()
    {
        return left;
    }


    public LinkedBinaryTree<T> getRight()
    {
       return right;
    }

       改完以後,問題就解決了。

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

  • 問題1:書上的「使用二叉樹:表達式樹」這裏想要運行書上的代碼,須要本身在LinkedBinaryTree類中添加getHeight()方法,遇到的問題見下:

  • 問題1解決方案:在我補全了getHeight()方法後,再去運行程序,始終會拋出錯誤StackOverflowError,查API文檔

    注意到了「當應用程序遞歸太深而發生堆棧溢出時,拋出該錯誤」,但不明白什麼叫遞歸太深。就又搜索了這個錯誤。

    StackOverflowError是因爲當前線程的棧滿了 ,也就是函數調用層級過多致使。
    好比死遞歸。
    以上是網上查閱的資料。

       死遞歸,這個知道一些,就是遞歸沒法挑出來,if條件給的不對,因而就從遞歸下手,認真看了兩遍,找到了錯誤。

圈紅地方就是出錯的地方,每次的遞歸我都是在用root這個,根本沒法跳出遞歸,將root改成node,便可運行。

  • 問題2:在測試contains方法時,它始終的結果是false,明明包含該元素也會顯示爲false。

  • 問題2解決方案:如圖,進行了單步調試,能夠看出next.getElement()和targetElement中元素均爲「+」,但是卻斷定條件不成立。


           抱着試一試的心態,就把next.getElement()和targetElement都使用了String.valueOf(),輸出的就是true了,多是由於以前的元素類型不一樣吧,或者能夠在next.getElement()前加個強制類型轉換,轉換爲T,試了一下返回的結果又變回false了,有點奇怪。

代碼託管

       上週代碼行數爲11412行,如今爲12499行,本週共1087行。

上週考試錯題總結

上週未進行考試

結對及互評

  • 本週結對學習狀況
    • 20172308
    • 博客中值得學習的或問題:

    • 結對學習內容:第十章——樹。

其餘(感悟、思考等)

感悟

       樹這一章能夠感受是到目前爲止學起來最爲困難的一章,一開始看錶達式樹時,整個都是混亂的,由於它調用後面的鏈表建立樹,包括表達式樹中的方法,理解起來都有困難。後面靠看着UML類圖,對整個結構纔算明白了一些,而後纔去試着把每一個類下面的方法弄明白。最爲困惑的就是背部疼痛診斷器,它是從文件中讀取,而後輸出,但是我弄了很久它就只能輸出一個問題,循環不了,這裏仍是有問題。下一章依然與樹有關,但願能把樹這一部分徹底弄懂。
       同時本週對博客園的博客樣式進行了較大的改動。不改感受很差看,改起來又很麻煩,最後改這一次了。

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 0/0 1/1 15/15
第二週 572/572 1/2 16/31
第三週 612/1184 1/3 13/44
第四周 1468/2652 2/5 13/57
第五週 1077/3729 1/6 14/71 初步理解各個排序算法
第六週 1087/4816 1/7 17/88 認識樹結構

參考資料

相關文章
相關標籤/搜索