一個有窮的結點集合,能夠爲空。若不爲空,則它是由根結點和稱爲其左子樹和右子樹的兩個互不相交的二叉樹組成。java
這裏在補充一下樹的其餘一些性質和概念:node
對於二叉樹的元素,主要的操做包括:數組
使用順序存儲結構,對徹底二叉樹這種結構是很是合適的。能夠按照從上之下,從左至右順序存儲n個結點的徹底二叉樹的結點父子關係。bash
徹底二叉樹的這種存儲結構,有如下特色數據結構
通常普通的二叉樹,在其空餘位置補充控制,當作是徹底二叉樹,採用數組結構存儲,將致使存儲空間的浪費。post
二叉樹的鏈式存儲結構中,每個結點包含三個關鍵屬性:指向左子樹的指針,數據域,指向右子樹的指針;根據這個敘述,咱們能夠按以下結構定義結點。測試
/**
* Created by engineer on 2017/10/23.
* <p>
* 二叉樹結點定義
*/
public class TreeNode<T> {
// 數據域
private T data;
// 左子樹
private TreeNode<T> leftChild;
// 右子樹
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;
}
}複製代碼
咱們就如下圖爲例,構造一顆二叉樹。ui
/** * 構建二叉樹 * * @return 樹根 */
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;
}複製代碼
這樣,咱們就按上圖所示構建了一顆二叉樹,返回二叉樹的根結點。this
二叉樹的遍歷是二叉樹最要的操做,也是二叉樹的核心。從二叉樹的定義咱們能夠得知,二叉樹是一種遞歸形式的數據結構,根結點下的左右子樹又分別是二叉樹;所以這使得二叉樹的遍歷離不開遞歸這種思想。spa
很顯然,對於二叉樹的三種遍歷,咱們就能夠藉助其自身的特性,經過遞歸實現。
/** * 訪問每一個結點 * * @param node */
private void visitNode(TreeNode node) {
System.out.print(node.getData().toString());
System.out.print(" ");
}
/** * 前序遍歷-遞歸實現 * * @param node */
void preTraversal(TreeNode node) {
if (node != null) {
visitNode(node);
preTraversal(node.getLeftChild());
preTraversal(node.getRightChild());
}
}
/** * 中序遍歷-遞歸實現 * * @param node */
void traversal(TreeNode node) {
if (node != null) {
traversal(node.getLeftChild());
visitNode(node);
traversal(node.getRightChild());
}
}
/** * 後序遍歷-遞歸實現 * @param node */
void postTraversal(TreeNode node) {
if (node != null) {
postTraversal(node.getLeftChild());
postTraversal(node.getRightChild());
visitNode(node);
}
}複製代碼
能夠看到,使用遞歸實現二叉樹的遍歷十分簡單,但咱們也能夠考慮使用非遞歸的形式,使用棧。
嚴格來講,使用棧實現二叉樹的遍歷,其實仍是遞歸思想,只不過是咱們本身用棧完成了遞歸實現中系統幫咱們完成的工做。
本質上來講,二叉樹這種遞歸的數據結構,他的遍歷是離不開遞歸思想的,只不過看咱們怎麼去理解遞歸的實現了。
/** * 前序遍歷-迭代實現 * @param node */
void preTraversalIteration(TreeNode node) {
// 建立一個棧
Stack<TreeNode> mStack = new Stack<>();
while (true) {
while (node != null) { // 非葉子結點的子樹
// 前序遍歷,先訪問根結點
visitNode(node);
// 將當前結點壓入棧
mStack.push(node);
// 對左子樹繼續進行前序遍歷
node=node.getLeftChild();
}
if (mStack.isEmpty()) {
//全部元素已遍歷完成
break;
}
// 彈出棧頂結點
node=mStack.pop();
// 右子樹前序遍歷
node=node.getRightChild();
}
}
/** * 中序遍歷-迭代實現 * @param node */
void TraversalIteration(TreeNode node) {
// 建立一個棧
Stack<TreeNode> mStack = new Stack<>();
while (true) {
while (node != null) { // 非葉子結點的子樹
// 將當前結點壓入棧
mStack.push(node);
// 對左子樹繼續進行中序遍歷
node=node.getLeftChild();
}
if (mStack.isEmpty()) {
//全部元素已遍歷完成
break;
}
// 彈出棧頂結點
node=mStack.pop();
// 中序遍歷,訪問根結點
visitNode(node);
// 右子樹中序遍歷
node=node.getRightChild();
}
}
/** * 後序遍歷-迭代實現 * @param node */
void postTraversalIteration(TreeNode node) {
// 建立一個棧
Stack<TreeNode> mStack = new Stack<>();
while (true) {
if (node != null) {
//當前結點非空,壓入棧
mStack.push(node);
// 左子樹繼續遍歷
node=node.getLeftChild();
}else {
// 左子樹爲空
if(mStack.isEmpty()){
return;
}
if (mStack.lastElement().getRightChild() == null) {
// 棧頂元素右子樹爲空,則當前結點爲葉子結點,輸出
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;
}
}
}
}複製代碼
能夠看到,雖然說是非遞歸實現,但本質上仍是依靠棧先進後出的特性,實現了遞歸訪問每一個結點的操做,無非就是在前、中、後三種順序下,訪問結點的時機不一樣而已。這裏,前序和中序遍歷的實現其實很容易理解,後續遍歷的實現很考究對棧的使用理解。
最後,再來講一說層序遍歷。顧名思義,層序遍歷就是從上到下按層,從左至右依次訪問每一個結點。這種遍歷很是用規律,就是從根節點下一層開始,優先訪問每一層全部的雙親結點,而後依次訪問每一個結點的左右兒子。也就是說,從上到下,先碰見到結點先訪問,後遇到的結點後訪問,這典型的就是隊列的思想,所以咱們可使用隊列實現二叉樹的層序遍歷。
/** * 層序遍歷 * @param node */
void levelTraversal(TreeNode node) {
//建立隊列
Queue<TreeNode> mNodeQueue = new LinkedList<>();
// 根結點加入隊列
mNodeQueue.add(node);
TreeNode temp;
while (!mNodeQueue.isEmpty()) {
//元素出隊列
temp=mNodeQueue.poll();
//輸出
visitNode(temp);
if (temp.getLeftChild() != null) {
// 左子樹入隊列
mNodeQueue.add(temp.getLeftChild());
}
if (temp.getRightChild() != null) {
//右子樹入隊列
mNodeQueue.add(temp.getRightChild());
}
}
}複製代碼
最後,用一個測試類測試一下咱們對二叉樹的實現。
/** * 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("前序遍歷-遞歸實現:");
mBinaryTree.preTraversal(root);
System.out.print("\n中序遍歷-遞歸實現:");
mBinaryTree.traversal(root);
System.out.print("\n後序遍歷-遞歸實現:");
mBinaryTree.postTraversal(root);
System.out.println();
System.out.print("\n前序遍歷-迭代實現:");
mBinaryTree.preTraversalIteration(root);
System.out.print("\n中序遍歷-迭代實現:");
mBinaryTree.TraversalIteration(root);
System.out.print("\n後序遍歷-迭代實現:");
mBinaryTree.postTraversalIteration(root);
System.out.println();
System.out.print("\n層序遍歷:");
mBinaryTree.levelTraversal(root);
}
}複製代碼
獲得輸出:
前序遍歷-遞歸實現:A B D E G C F H
中序遍歷-遞歸實現:D B G E A C H F
後序遍歷-遞歸實現:D G E B H F C A
前序遍歷-迭代實現:A B D E G C F H
中序遍歷-迭代實現:D B G E A C H F
後序遍歷-迭代實現:D G E B H F C A
層序遍歷:A B C D E F G H複製代碼
嗯,和預期想象的一致。
好了,二叉樹的存儲結構和遍歷就到這裏了。