數據結構學習之:樹
一、樹的概念
1、定義
我們先來看看樹的形式化定義:
算法的集合樹(Tree)是由一個或多個結點組成的有限集合T,其中有一個特定的稱爲根的結點;其餘結點可分爲(m≥0)個互不相交的有限集T1,T2,T3,…,Tm,每一個集合本身又是一棵樹,且稱爲根的子樹。
按照自己的理解,我們可以認爲樹其實是一種抽象的數據結構,數據之間的關係就像是樹的形狀一樣如下圖所示:從A(根節點出發),可以散發子節點(B、C、D),子節點又可以散發子節點,直到散發到葉子節點(E、F、G、H、I、J)結束。
2、樹的專用術語
1、根節點
節點的最頂層我們稱之爲樹的根節點,如下圖所示:A就是這棵樹的根節點
2、雙親節點
有兩個子節點的父節點我們稱之爲雙親節點(A、B、C)就是雙親節點
3、路徑
訪問目標節點經過的路徑,如訪問E節點,那麼該路徑爲:A-B-E
4、節點的度
節點的子節點,例如A的度是2,因爲它有兩個子節點
5、節點的權
節點的值我們稱之爲權,假設在F點中的值是5,那麼我們稱節點F的權爲5
6、葉子節點:
葉子節點,我們可以理解爲樹結構最後的分支(其實就是葉子)即沒有子節點的節點,我們稱爲葉子節點,例如:E、F、G就是葉子節點
二、二叉樹
1、概述
二叉樹是樹結構的一種,只是二叉樹有一些額外的要求,如下圖所示就是一個二叉樹的結構。顧名思義,二叉樹的節點肯定是最多分二個分支,所以二叉樹概念如下:
1、任何一個節點的子節點最大節點數爲2。
2、二叉樹的節點分爲左子節點和右子節點。(例如:F的左節點爲C,右節點爲E)
2、二叉樹遍歷
二叉樹的遍歷方式有三種:前序遍歷、中序遍歷、後序遍歷。在這裏我們所說的遍歷方式都是相對與根節點的位置來實現的。下面我們根據下圖來介紹這三種遍歷方式。
2.1、前序遍歷
前序遍歷其實就是將我們的父節點放在前面,所以遍歷的順序是:父節點、左節點、右節點。
所以上圖的遍歷順序就是:FC(AD)E(HG)
2.2、中序遍歷
中序遍歷就是將我們的父節點放在中間,遍歷順序爲:左節點、父節點、右節點
上圖的遍歷順序爲:(ACD) F (HEG)
2.3、後序遍歷
同理,後序遍歷就是將我們的父節點放在最後,遍歷順序變成:左節點、右節點、父節點
上圖遍歷順序爲: (ADC) (HGE) F
3、滿二叉樹與完全二叉樹
在我們的二叉樹結構中,設計到兩種特殊的二叉樹:滿二叉樹、完全二叉樹。因這兩種數據結構規律都是可控的,可計算的,後面的很多二叉樹都會依賴這兩種數據結構。
3.1、滿二叉樹
滿二叉樹:1、所有的葉子節點都在同一層 ;2、節點的總數爲2n-1。像下圖所示就是一個滿二叉樹,最後一層都是葉子節點,且節點總數爲23-1 = 7。
3.2 完全二叉樹
完全二叉樹:所有葉子節點都在最後一層,或者倒數第二層、並且最後一層的葉子節點左連續,倒數第二層的葉子節點右連續
意思就是說我們的葉子節點如果是在最後一層的話,那麼最後一層的葉子節點的左節點必須是連續的;如果在倒數第二層存在葉子節點,那麼第二層的葉子節點必須右連續。像下圖就是完全二叉樹:
像下圖最後一層左節點不連續的,就是非完全二叉樹:
4、二叉樹的代碼實現
1、鏈表結構實現
package tree;
/**
* 二叉樹對象
*/
public class BinaryTree<T> {
// 用於存放節點的權
private T value;
// 左節點
private BinaryTree<T> leftNode;
// 右節點
private BinaryTree<T> rightNode;
public BinaryTree(T value) {
this.value = value;
}
public BinaryTree<T> getLeftNode() {
return leftNode;
}
public void setLeftNode(BinaryTree<T> leftNode) {
this.leftNode = leftNode;
}
public BinaryTree<T> getRightNode() {
return rightNode;
}
public void setRightNode(BinaryTree<T> rightNode) {
this.rightNode = rightNode;
}
/**
* 前序遍歷
*/
public void frontShow() {
if (this == null) {
return;
}
//打印根節點
System.out.println(this.value);
if (this.leftNode != null) {
this.leftNode.frontShow();
}
if (this.rightNode != null) {
this.rightNode.frontShow();
}
}
/**
* 中序遍歷
*/
public void middelShow() {
if (this == null) {
return;
}
if (this.leftNode != null) {
this.leftNode.middelShow();
}
//打印根節點
System.out.println(this.value);
if (this.rightNode != null) {
this.rightNode.middelShow();
}
}
/**
* 後續遍歷
*/
public void afterShow() {
if (this == null) {
return;
}
if (this.leftNode != null) {
this.leftNode.afterShow();
}
if (this.rightNode != null) {
this.rightNode.afterShow();
}
//打印根節點
System.out.println(this.value);
}
/**
* 前序查找
*
* @param value
*/
public BinaryTree frontSearch(T value) {
BinaryTree treeNode = null;
if (this.value == value) {
return this;
}
if (this.leftNode != null) {
treeNode = this.leftNode.frontSearch(value);
}
if (treeNode != null) {
return treeNode;
}
if (this.rightNode != null) {
treeNode = this.rightNode.frontSearch(value);
}
return treeNode;
}
/**
* 前序查找
*
* @param value
*/
public BinaryTree middleSearch(T value) {
BinaryTree treeNode = null;
if (this.leftNode != null) {
treeNode = this.leftNode.frontSearch(value);
}
if (treeNode != null) {
return treeNode;
}
if (this.value == value) {
return this;
}
if (this.rightNode != null) {
treeNode = this.rightNode.frontSearch(value);
}
return treeNode;
}
/**
* 前序查找
*
* @param value
*/
public BinaryTree afterSearch(T value) {
BinaryTree treeNode = null;
if (this.leftNode != null) {
treeNode = this.leftNode.frontSearch(value);
}
if (treeNode != null) {
return treeNode;
}
if (this.rightNode != null) {
treeNode = this.rightNode.frontSearch(value);
}
if (treeNode != null) {
return treeNode;
}
if (this.value == value) {
return this;
}
return treeNode;
}
}
2、順序存儲結構實現
順序存儲我們可以用數組來表示二叉樹,如圖所示:
順序存儲的二叉樹必須是一顆完全二叉樹的形態,對於不是完全二叉樹轉數組時,需要在數組中補null佔位,如下圖所示:
所以順序存儲的二叉樹,我們一般只考慮完全二叉樹。接下來我們來介紹一下順序存儲的性質:
1、第N個元素的左子節點:2N+1
2、第N個元素的右子節點:2N+2
3、第N個元素的父節點: (n-1)/2
package tree;
/**
* 順序存儲的二叉樹
*/
public class ArrayBinaryTree<T> {
private T[] value;
public ArrayBinaryTree(T[] value) {
this.value = value;
}
public void frontShow(){
frontShow(0);
}
public void middelShow(){
middleShow(0);
}
public void afterShow(){
afterShow(0);
}
private void frontShow(int start){
if (value == null|| value.length==0){
return;
}
if (start<value.length){
//先遍歷當前節點的內容
System.out.println(value[start]);
}
if (2*start+1<value.length){
//遍歷左節點的內容
frontShow(2*start+1);
}
if (2*start+2<value.length){
//遍歷右節點的內容
frontShow(2*start+2);
}
}
private void middleShow(int start){
if (value == null|| value.length==0){
return;
}
if (2*start+1<value.length){
//遍歷左節點的內容
frontShow(2*start+1);
}
if (start<value.length){
//先遍歷當前節點的內容
System.out.println(value[start]);
}
if (2*start+2<value.length){
//遍歷右節點的內容
frontShow(2*start+2);
}
}
private void afterShow(int start){
if (value == null|| value.length==0){
return;
}
if (2*start+1<value.length){
//遍歷左節點的內容
frontShow(2*start+1);
}
if (2*start+2<value.length){
//遍歷右節點的內容
frontShow(2*start+2);
}
if (start<value.length){
//先遍歷當前節點的內容
System.out.println(value[start]);
}
}
}