2018年學習總結博客總目錄:第一週 第二週 第三週 第四周 第五週 第六週
html
1.樹的概述及基本概念
(1)樹是一種非線性數據結構,其中的元素被組織成了一個層次結構.
(2)樹由一個包含結點和邊的集構成,其中元素被存儲在這些結點中,邊則將一個結點和另外一個結點鏈接起來。每一結點都位於該樹層次結構中的某一特頂層上。
(3)樹的根式位於該樹頂層的惟一結點。一棵樹只有一個根結點。
(4)位於樹中較底層的結點是上一層的孩子。一個結點只有一個雙親,但一個結點能夠有不少孩子。同一雙親的兩個結點互稱兄弟(sibling)
(5)根結點是惟一沒有雙親的結點。沒有任何孩子的結點稱爲葉子,一個至少有一個孩子的非根結點稱爲一個內部節點。
(6)一個結點在從根開始的路徑中位於另外一結點之上,那麼該結點就是它的祖先。根是樹中全部節點的最終祖先。沿着起始自某一特定結點的路徑能夠到達的結點是該結點的子孫(descendant)。
(7)結點的層也就是從根結點到該結點的路徑長度。經過計算從根到該結點所必須穿過的邊的數目,便是路徑長度。根位於0層。
(8)樹的高度是指從根到葉子之間最遠路徑的長度。
2.樹的分類
樹有多種分類方式,最重要的一個是樹中任一結點能夠具備的最大孩子的數目。這個值也稱爲樹的度(order)。java
一些概念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。二、左子樹和右子樹是有順序的,次序不能顛倒。三、即便某結點只有一個子樹,也要區分左右子樹。
二叉樹的性質:
性質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。
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()方法。
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是用做存儲各個元素它所對應的層數位置變量,currentLevel和previousLevel共同在決定着是否該換行 ,便是否該跳至下一行。第一個if-else語句中循環是用做控制着空格的打印,以及該空幾格。第二個if-else語句是在將該結點的子結點及其層數分別添加到nodes和levelList中去。
問題2:在運行背部疼痛診斷器時,老是出錯,後面才發現循環不能進行。
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行。
上週未進行考試
博客中值得學習的或問題:
結對學習內容:第十章——樹。
樹這一章能夠感受是到目前爲止學起來最爲困難的一章,一開始看錶達式樹時,整個都是混亂的,由於它調用後面的鏈表建立樹,包括表達式樹中的方法,理解起來都有困難。後面靠看着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 | 認識樹結構 |