一个有穷的结点集合,能够为空。若不为空,则它是由根结点和称为其左子树和右子树的两个互不相交的二叉树组成。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复制代码
嗯,和预期想象的一致。
好了,二叉树的存储结构和遍历就到这里了。