定義
一個(gè)有窮的結(jié)點(diǎn)集合,可以為空。若不為空,則它是由根結(jié)點(diǎn)和稱為其左子樹(shù)和右子樹(shù)的兩個(gè)互不相交的二叉樹(shù)組成。
- 二叉樹(shù)的五種基本形態(tài):

- 二叉樹(shù)的子樹(shù)是有順序之分的,稱為左子樹(shù)和右子樹(shù)

- 幾種特殊的二叉樹(shù)
- 斜二叉樹(shù)

- 完美二叉樹(shù)(滿二叉樹(shù))

- 完全二叉樹(shù)
有n個(gè)結(jié)點(diǎn)的二叉樹(shù),對(duì)樹(shù)中結(jié)點(diǎn)按從上之下,從左至右的順序進(jìn)行編號(hào),編號(hào)為i(1<=1<=n)結(jié)點(diǎn)與滿二叉樹(shù)中編號(hào)為i結(jié)點(diǎn)在二叉樹(shù)中位置相同
二叉樹(shù)的幾個(gè)重要性質(zhì):
- 在二叉樹(shù)的第i層上最多有2 i-1 個(gè)節(jié)點(diǎn) 。(i>=1)
- 二叉樹(shù)中如果深度為k,那么最多有2k-1個(gè)節(jié)點(diǎn)。(k>=1)
- 對(duì)任何非空二叉樹(shù)T,若n0表示度數(shù)為0的節(jié)點(diǎn) n2表示度數(shù)為2的節(jié)點(diǎn),那么n0=n2+1;
- 具有n個(gè)結(jié)點(diǎn)的完全二叉樹(shù)的深度為 log2 n + 1
這里在補(bǔ)充一下樹(shù)的其他一些性質(zhì)和概念:
- 結(jié)點(diǎn)的度:結(jié)點(diǎn)所擁有的子樹(shù)的個(gè)數(shù)稱為結(jié)點(diǎn)的度;
- 樹(shù)的度:樹(shù)中各節(jié)點(diǎn)的度的最大值;因此,二叉樹(shù)的度最大為2;
- 結(jié)點(diǎn)的層數(shù):規(guī)定根節(jié)點(diǎn)的層數(shù)為1,其余結(jié)點(diǎn)的層數(shù)為他的雙親結(jié)點(diǎn)層數(shù)加1
- 輸?shù)纳疃龋簶?shù)中所有結(jié)點(diǎn)的最大層數(shù)。
二叉樹(shù)的抽象數(shù)據(jù)類型(ADT)
對(duì)于二叉樹(shù)的元素,主要的操作包括:
- 判別二叉樹(shù)是否為空
- 遍歷二叉樹(shù),按特定的順序訪問(wèn)每個(gè)結(jié)點(diǎn)
- 前序遍歷:根節(jié)點(diǎn)-->左子樹(shù)-->右子樹(shù)
- 中序遍歷:左子樹(shù)-->根節(jié)點(diǎn)-->右子樹(shù)
- 后序遍歷:左子樹(shù)-->右子樹(shù)-->根節(jié)點(diǎn)
- 層序遍歷:從上至下,從左至右。
- 創(chuàng)建一個(gè)二叉樹(shù)
二叉樹(shù)的存儲(chǔ)結(jié)構(gòu)
順序存儲(chǔ)結(jié)構(gòu)

使用順序存儲(chǔ)結(jié)構(gòu),對(duì)完全二叉樹(shù)這種結(jié)構(gòu)是非常合適的??梢园凑諒纳现?,從左至右順序存儲(chǔ)n個(gè)結(jié)點(diǎn)的完全二叉樹(shù)的結(jié)點(diǎn)父子關(guān)系。

完全二叉樹(shù)的這種存儲(chǔ)結(jié)構(gòu),有以下特點(diǎn)
- 非根節(jié)點(diǎn)(序號(hào)i>1)的父節(jié)點(diǎn)序號(hào)(數(shù)組下標(biāo))是 i/2 (取整)。
- 結(jié)點(diǎn)(序號(hào)為i)的左孩子結(jié)點(diǎn)的序號(hào)是2i,如果2i>n,則沒(méi)有左孩子;
- 結(jié)點(diǎn)(序號(hào)為i)的右孩子結(jié)點(diǎn)的序號(hào)是2i+1,如果2i+1>n,則沒(méi)有右孩子。
一般普通的二叉樹(shù),在其空余位置補(bǔ)充控制,當(dāng)做是完全二叉樹(shù),采用數(shù)組結(jié)構(gòu)存儲(chǔ),將導(dǎo)致存儲(chǔ)空間的浪費(fèi)。
鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)
二叉樹(shù)的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)中,每一個(gè)結(jié)點(diǎn)包含三個(gè)關(guān)鍵屬性:指向左子樹(shù)的指針,數(shù)據(jù)域,指向右子樹(shù)的指針;根據(jù)這個(gè)敘述,我們可以按如下結(jié)構(gòu)定義結(jié)點(diǎn)。

結(jié)點(diǎn)定義
/**
* Created by engineer on 2017/10/23.
* <p>
* 二叉樹(shù)結(jié)點(diǎn)定義
*/
public class TreeNode<T> {
// 數(shù)據(jù)域
private T data;
// 左子樹(shù)
private TreeNode<T> leftChild;
// 右子樹(shù)
private TreeNode<T> rightChild;
public TreeNode(T data) {
this(null, data, null);
}
public TreeNode(TreeNode<T> leftChild, T data, TreeNode<T> rightChild) {
this.leftChild = leftChild;
this.data = data;
this.rightChild = rightChild;
}
public T getData() {
return data;
}
public TreeNode<T> getLeftChild() {
return leftChild;
}
public TreeNode<T> getRightChild() {
return rightChild;
}
}
二叉樹(shù)初始化
我們就以下圖為例,構(gòu)造一顆二叉樹(shù)。
/**
* 構(gòu)建二叉樹(shù)
*
* @return 樹(shù)根
*/
TreeNode CreateTree() {
TreeNode<String> nodeH = new TreeNode<>("H");
TreeNode<String> nodeG = new TreeNode<>("G");
TreeNode<String> nodeF = new TreeNode<>(nodeH, "F", null);
TreeNode<String> nodeE = new TreeNode<>(nodeG, "E", null);
TreeNode<String> nodeD = new TreeNode<>("D");
TreeNode<String> nodeC = new TreeNode<>(null, "C", nodeF);
TreeNode<String> nodeB = new TreeNode<>(nodeD, "B", nodeE);
TreeNode<String> nodeA = new TreeNode<>(nodeB, "A", nodeC);
return nodeA;
}
這樣,我們就按上圖所示構(gòu)建了一顆二叉樹(shù),返回二叉樹(shù)的根結(jié)點(diǎn)。
二叉樹(shù)的遍歷
二叉樹(shù)的遍歷是二叉樹(shù)最要的操作,也是二叉樹(shù)的核心。從二叉樹(shù)的定義我們可以得知,二叉樹(shù)是一種遞歸形式的數(shù)據(jù)結(jié)構(gòu),根結(jié)點(diǎn)下的左右子樹(shù)又分別是二叉樹(shù);因此這使得二叉樹(shù)的遍歷離不開(kāi)遞歸這種思想。
很顯然,對(duì)于二叉樹(shù)的三種遍歷,我們就可以借助其自身的特性,通過(guò)遞歸實(shí)現(xiàn)。
- 二叉樹(shù)的遞歸遍歷實(shí)現(xiàn)
/**
* 訪問(wèn)每個(gè)結(jié)點(diǎn)
*
* @param node
*/
private void visitNode(TreeNode node) {
System.out.print(node.getData().toString());
System.out.print(" ");
}
/**
* 前序遍歷-遞歸實(shí)現(xiàn)
*
* @param node
*/
void preTraversal(TreeNode node) {
if (node != null) {
visitNode(node);
preTraversal(node.getLeftChild());
preTraversal(node.getRightChild());
}
}
/**
* 中序遍歷-遞歸實(shí)現(xiàn)
*
* @param node
*/
void traversal(TreeNode node) {
if (node != null) {
traversal(node.getLeftChild());
visitNode(node);
traversal(node.getRightChild());
}
}
/**
* 后序遍歷-遞歸實(shí)現(xiàn)
* @param node
*/
void postTraversal(TreeNode node) {
if (node != null) {
postTraversal(node.getLeftChild());
postTraversal(node.getRightChild());
visitNode(node);
}
}
可以看到,使用遞歸實(shí)現(xiàn)二叉樹(shù)的遍歷十分簡(jiǎn)單,但我們也可以考慮使用非遞歸的形式,使用棧。
嚴(yán)格來(lái)說(shuō),使用棧實(shí)現(xiàn)二叉樹(shù)的遍歷,其實(shí)還是遞歸思想,只不過(guò)是我們自己用棧完成了遞歸實(shí)現(xiàn)中系統(tǒng)幫我們完成的工作。
本質(zhì)上來(lái)說(shuō),二叉樹(shù)這種遞歸的數(shù)據(jù)結(jié)構(gòu),他的遍歷是離不開(kāi)遞歸思想的,只不過(guò)看我們?cè)趺慈ダ斫膺f歸的實(shí)現(xiàn)了。
- 二叉樹(shù)的非遞歸實(shí)現(xiàn)
/**
* 前序遍歷-迭代實(shí)現(xiàn)
* @param node
*/
void preTraversalIteration(TreeNode node) {
// 創(chuàng)建一個(gè)棧
Stack<TreeNode> mStack = new Stack<>();
while (true) {
while (node != null) { // 非葉子結(jié)點(diǎn)的子樹(shù)
// 前序遍歷,先訪問(wèn)根結(jié)點(diǎn)
visitNode(node);
// 將當(dāng)前結(jié)點(diǎn)壓入棧
mStack.push(node);
// 對(duì)左子樹(shù)繼續(xù)進(jìn)行前序遍歷
node=node.getLeftChild();
}
if (mStack.isEmpty()) {
//所有元素已遍歷完成
break;
}
// 彈出棧頂結(jié)點(diǎn)
node=mStack.pop();
// 右子樹(shù)前序遍歷
node=node.getRightChild();
}
}
/**
* 中序遍歷-迭代實(shí)現(xiàn)
* @param node
*/
void TraversalIteration(TreeNode node) {
// 創(chuàng)建一個(gè)棧
Stack<TreeNode> mStack = new Stack<>();
while (true) {
while (node != null) { // 非葉子結(jié)點(diǎn)的子樹(shù)
// 將當(dāng)前結(jié)點(diǎn)壓入棧
mStack.push(node);
// 對(duì)左子樹(shù)繼續(xù)進(jìn)行中序遍歷
node=node.getLeftChild();
}
if (mStack.isEmpty()) {
//所有元素已遍歷完成
break;
}
// 彈出棧頂結(jié)點(diǎn)
node=mStack.pop();
// 中序遍歷,訪問(wèn)根結(jié)點(diǎn)
visitNode(node);
// 右子樹(shù)中序遍歷
node=node.getRightChild();
}
}
/**
* 后序遍歷-迭代實(shí)現(xiàn)
* @param node
*/
void postTraversalIteration(TreeNode node) {
// 創(chuàng)建一個(gè)棧
Stack<TreeNode> mStack = new Stack<>();
while (true) {
if (node != null) {
//當(dāng)前結(jié)點(diǎn)非空,壓入棧
mStack.push(node);
// 左子樹(shù)繼續(xù)遍歷
node=node.getLeftChild();
}else {
// 左子樹(shù)為空
if(mStack.isEmpty()){
return;
}
if (mStack.lastElement().getRightChild() == null) {
// 棧頂元素右子樹(shù)為空,則當(dāng)前結(jié)點(diǎn)為葉子結(jié)點(diǎn),輸出
node=mStack.pop();
visitNode(node);
while (node == mStack.lastElement().getRightChild()) {
visitNode(mStack.lastElement());
node=mStack.pop();
if (mStack.isEmpty()) {
break;
}
}
}
if (!mStack.isEmpty()) {
node=mStack.lastElement().getRightChild();
}else {
node=null;
}
}
}
}
可以看到,雖說(shuō)是非遞歸實(shí)現(xiàn),但本質(zhì)上還是依靠棧先進(jìn)后出的特性,實(shí)現(xiàn)了遞歸訪問(wèn)每個(gè)結(jié)點(diǎn)的操作,無(wú)非就是在前、中、后三種順序下,訪問(wèn)結(jié)點(diǎn)的時(shí)機(jī)不同而已。這里,前序和中序遍歷的實(shí)現(xiàn)其實(shí)很容易理解,后續(xù)遍歷的實(shí)現(xiàn)很考究對(duì)棧的使用理解。
- 層序遍歷
最后,再來(lái)說(shuō)一說(shuō)層序遍歷。顧名思義,層序遍歷就是從上到下按層,從左至右依次訪問(wèn)每個(gè)結(jié)點(diǎn)。這種遍歷非常用規(guī)律,就是從根節(jié)點(diǎn)下一層開(kāi)始,優(yōu)先訪問(wèn)每一層所有的雙親結(jié)點(diǎn),然后依次訪問(wèn)每個(gè)結(jié)點(diǎn)的左右兒子。也就是說(shuō),從上到下,先遇見(jiàn)到結(jié)點(diǎn)先訪問(wèn),后遇到的結(jié)點(diǎn)后訪問(wèn),這典型的就是隊(duì)列的思想,因此我們可以使用隊(duì)列實(shí)現(xiàn)二叉樹(shù)的層序遍歷。
/**
* 層序遍歷
* @param node
*/
void levelTraversal(TreeNode node) {
//創(chuàng)建隊(duì)列
Queue<TreeNode> mNodeQueue = new LinkedList<>();
// 根結(jié)點(diǎn)加入隊(duì)列
mNodeQueue.add(node);
TreeNode temp;
while (!mNodeQueue.isEmpty()) {
//元素出隊(duì)列
temp=mNodeQueue.poll();
//輸出
visitNode(temp);
if (temp.getLeftChild() != null) {
// 左子樹(shù)入隊(duì)列
mNodeQueue.add(temp.getLeftChild());
}
if (temp.getRightChild() != null) {
//右子樹(shù)入隊(duì)列
mNodeQueue.add(temp.getRightChild());
}
}
}
測(cè)試二叉樹(shù)的實(shí)現(xiàn)
最后,用一個(gè)測(cè)試類測(cè)試一下我們對(duì)二叉樹(shù)的實(shí)現(xiàn)。
/**
* Created by engineer on 2017/10/24.
*/
public class BinaryTreeTest {
public static void main(String[] args) {
BinaryTree mBinaryTree = new BinaryTree();
TreeNode root = mBinaryTree.CreateTree();
System.out.print("前序遍歷-遞歸實(shí)現(xiàn):");
mBinaryTree.preTraversal(root);
System.out.print("\n中序遍歷-遞歸實(shí)現(xiàn):");
mBinaryTree.traversal(root);
System.out.print("\n后序遍歷-遞歸實(shí)現(xiàn):");
mBinaryTree.postTraversal(root);
System.out.println();
System.out.print("\n前序遍歷-迭代實(shí)現(xiàn):");
mBinaryTree.preTraversalIteration(root);
System.out.print("\n中序遍歷-迭代實(shí)現(xiàn):");
mBinaryTree.TraversalIteration(root);
System.out.print("\n后序遍歷-迭代實(shí)現(xiàn):");
mBinaryTree.postTraversalIteration(root);
System.out.println();
System.out.print("\n層序遍歷:");
mBinaryTree.levelTraversal(root);
}
}
得到輸出:
前序遍歷-遞歸實(shí)現(xiàn):A B D E G C F H
中序遍歷-遞歸實(shí)現(xiàn):D B G E A C H F
后序遍歷-遞歸實(shí)現(xiàn):D G E B H F C A
前序遍歷-迭代實(shí)現(xiàn):A B D E G C F H
中序遍歷-迭代實(shí)現(xiàn):D B G E A C H F
后序遍歷-迭代實(shí)現(xiàn):D G E B H F C A
層序遍歷:A B C D E F G H
嗯,和預(yù)期想象的一致。
好了,二叉樹(shù)的存儲(chǔ)結(jié)構(gòu)和遍歷就到這里了。