二叉树是树形结构的一种重要类型,在实际应用中具有十分重要的意义。从许多实际问题中抽象出来的数据结构往往是二叉树的形式,即使是一般的树也能够简单地转换为二叉树形式,而且二叉树的存储结构及其算法都比较简单,因此我们将详细地讨论关于二叉树的存储、运算及应用。
二叉树(Binary)是n(n≥0)个结点的有限集合,它的每个结点至多只有两棵子树。它或是空集,或是由一个根结点及两棵不相交的分别称作这个根的左子树和右子树的二叉树组成。
二 二叉树的性质
二叉树具有几个非常重要的性质,它们是:
性质1 二叉树的第i层上至多有2i-1个结点(i≥1)。
证明:用数学归纳法证明如下:
① 当i=1时,只有一个根结点,即2i-1=20=1,命题成立。
② 假设对所有的j(1≤j<i),命题成立,即第j层最多有2j-1第个结点。
③ 根据归纳假设,第i-1层上最多有2i-2个结点。由于二叉树的每个结点的度至多为2,因此在第i层上的结点数至多是第i-1层上最大结点数的2倍,即为2×2i-2=2i-1。
性质2 深度为k的二叉树至多有2k-1个结点。
证明:深度为k的二叉树最多含有的结点数,应是二叉树中每一层上最多含有结点数的和,由性质1可见,深度为k的二叉树的最大结点数为:
20 + 21 + … + 2k-1= 2k-1
满二叉树和完全二叉树是两种特殊情形的二叉树。
一棵深度为k且有2k-1个结点的二叉树称为满二叉树。这种树的特点是每一层上的结点数都达到最大值,因此不存在度数为1的结点,且所有叶子结点都在第k层上。
若一棵二叉树至多只有最下面两层上结点的度数小于2,并且最下一层上的结点都集中在该层最左边的若干位置上,则此二叉树称为完全二叉树。显然满二叉树是完全二叉树,但完全二叉树不一定是满二叉树。
三 二叉树存储
一般二叉树的存储有顺序存储和链式存储两种方式,由于顺序存储在二叉树非完全的时候浪费空间,并且考虑移动删除二叉树的时候需要移动大量的节点,因此一般情况下多数用链式存储方式存储二叉树
四 二叉树遍历
遍历一棵非空二叉树的问题可分解为三个子问题:即访问根结点、遍历左子树和遍历右子树。若分别用D、L和R表示以上三个问题,则有DLR、LDR、LRD和DRL、RDL、RLD六种次序遍历方案。其中前三种方案是按先左后右次序遍历根的两棵子树,而后三种则是按先右后左次序遍历两棵子树。由于两者对称,因此我们在这里仅讨论前三种次序的遍历方案。
在遍历方案DLR中,因为访问根结点的操作在遍历左、右子树之前,故称之为前序(Preorder)遍历或先根遍历;类似地,在方案LDR中,访问根结点的操作在遍历左子树之后和遍历右子树之前,故称之为中序(Inorder)遍历或中根遍历;在方案LRD中,因为访问根结点的操作在遍历左、右子树之后,故称之为后序(Postorder)遍历或后根遍历。显然,遍历左、右子树的问题仍然是遍历二叉树的问题,当二叉树为空时递归遍历结束,所以很容易给出以上三种遍历的递归算法定义。
由于自己目前从事java android开发,因此自己也简单的实现了一下这三种遍历的非递归方式的算法。
package com.example.javaapitest;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.java.api.test.BinaryTree;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startTest(View view) {
BinaryTree.BinaryTreeNode nodeA = new BinaryTree.BinaryTreeNode();
nodeA.data = "A";
BinaryTree.BinaryTreeNode nodeB = new BinaryTree.BinaryTreeNode();
nodeB.data = "B";
BinaryTree.BinaryTreeNode nodeC = new BinaryTree.BinaryTreeNode();
nodeC.data = "C";
BinaryTree.BinaryTreeNode nodeD = new BinaryTree.BinaryTreeNode();
nodeD.data = "D";
BinaryTree.BinaryTreeNode nodeE = new BinaryTree.BinaryTreeNode();
nodeE.data = "E";
BinaryTree.BinaryTreeNode nodeF = new BinaryTree.BinaryTreeNode();
nodeF.data = "F";
BinaryTree.BinaryTreeNode nodeG = new BinaryTree.BinaryTreeNode();
nodeG.data = "G";
BinaryTree.BinaryTreeNode nodeH = new BinaryTree.BinaryTreeNode();
nodeH.data = "H";
BinaryTree.BinaryTreeNode nodeI = new BinaryTree.BinaryTreeNode();
nodeI.data = "I";
BinaryTree.BinaryTreeNode nodeJ = new BinaryTree.BinaryTreeNode();
nodeJ.data = "J";
nodeA.leftChildNode = nodeB;
nodeA.rightChildNode = nodeC;
nodeB.leftChildNode = nodeD;
nodeB.rightChildNode = nodeE;
nodeC.leftChildNode = nodeF;
nodeC.rightChildNode = nodeG;
nodeD.leftChildNode = nodeH;
nodeD.rightChildNode = null;
nodeE.leftChildNode = null;
nodeE.rightChildNode = null;
nodeF.leftChildNode = nodeI;
nodeF.rightChildNode = null;
nodeG.leftChildNode = null;
nodeG.rightChildNode = nodeJ;
nodeH.leftChildNode = null;
nodeH.rightChildNode = null;
nodeI.leftChildNode = null;
nodeI.rightChildNode = null;
nodeJ.leftChildNode = null;
nodeJ.rightChildNode = null;
BinaryTree.inOrder(nodeA);
Log.d("Util_Test", "");
BinaryTree.preOrder(nodeA);
Log.d("Util_Test", "");
BinaryTree.postOrder(nodeA);
}
/*
01-09 13:08:20.260 8421 8421 D Util_Test: H
01-09 13:08:20.260 8421 8421 D Util_Test: D
01-09 13:08:20.260 8421 8421 D Util_Test: B
01-09 13:08:20.260 8421 8421 D Util_Test: E
01-09 13:08:20.260 8421 8421 D Util_Test: A
01-09 13:08:20.261 8421 8421 D Util_Test: I
01-09 13:08:20.261 8421 8421 D Util_Test: F
01-09 13:08:20.261 8421 8421 D Util_Test: C
01-09 13:08:20.261 8421 8421 D Util_Test: G
01-09 13:08:20.261 8421 8421 D Util_Test: J
01-09 13:08:20.261 8421 8421 D Util_Test:
01-09 13:08:20.261 8421 8421 D Util_Test: A
01-09 13:08:20.261 8421 8421 D Util_Test: B
01-09 13:08:20.261 8421 8421 D Util_Test: D
01-09 13:08:20.261 8421 8421 D Util_Test: H
01-09 13:08:20.261 8421 8421 D Util_Test: E
01-09 13:08:20.261 8421 8421 D Util_Test: C
01-09 13:08:20.261 8421 8421 D Util_Test: F
01-09 13:08:20.261 8421 8421 D Util_Test: I
01-09 13:08:20.261 8421 8421 D Util_Test: G
01-09 13:08:20.261 8421 8421 D Util_Test: J
01-09 13:08:20.261 8421 8421 D Util_Test:
01-09 13:08:20.261 8421 8421 D Util_Test: H
01-09 13:08:20.261 8421 8421 D Util_Test: D
01-09 13:08:20.261 8421 8421 D Util_Test: E
01-09 13:08:20.261 8421 8421 D Util_Test: B
01-09 13:08:20.261 8421 8421 D Util_Test: I
01-09 13:08:20.261 8421 8421 D Util_Test: F
01-09 13:08:20.261 8421 8421 D Util_Test: J
01-09 13:08:20.261 8421 8421 D Util_Test: G
01-09 13:08:20.261 8421 8421 D Util_Test: C
01-09 13:08:20.261 8421 8421 D Util_Test: A
* */
}
package com.java.api.test;
import java.util.Stack;
/**
* Created by abm on 18-7-3.
*/
public class BinaryTree {
/*
* 前序遍历DLR
* 1. 访问根结点
* 2. 访问左子树
* 3. 访问右子树
*
* */
public static void preOrder(BinaryTreeNode root) {
Stack<BinaryTreeNode> stack = new Stack<>();
BinaryTreeNode current = root;
while (current != null || stack.size() > 0) {
while (current != null) {
current.show();
stack.push(current);
current = current.leftChildNode;
}
if (stack.size() > 0) {
current = stack.pop();
current = current.rightChildNode;
}
}
}
/*
* 中序遍历DLR
* 1. 访问左子树
* 2. 访问根结点
* 3. 访问右子树
*
* */
public static void inOrder(BinaryTreeNode root) {
Stack<BinaryTreeNode> stack = new Stack<>();
BinaryTreeNode current = root;
while (current != null || stack.size() > 0) {
while (current != null) {
stack.push(current);
current = current.leftChildNode;
}
if (stack.size() > 0) {
current = stack.pop();
current.show();
current = current.rightChildNode;
}
}
}
/*
* 后序遍历LRD
* 1. 访问左子树
* 2. 访问右子树
* 3. 访问根结点
*
* */
public static void postOrder(BinaryTreeNode root) {
Stack<BinaryTreeNode> stack = new Stack<>();
BinaryTreeNode current = root;
BinaryTreeNode pre = null;
while (current != null || stack.size() > 0) {
while (current != null) {
stack.push(current);
current = current.leftChildNode;
}
if (stack.size() > 0) {
current = stack.peek().rightChildNode; //目前节点栈中的栈定情况分为1.叶子2.之前弹出叶子的双亲节点
//访问栈顶的右孩子,此时存在情况:1.叶子节点不存在右孩子2.父节点不存在或存在右节点1.1存在则重复遍历右节点树
if (current == null || current == pre) { /*当访问到叶子结点的时候并且该叶子结点没有孩子的时候,
从结点栈中弹出打印该节点并打印该结点数据,设置之前访问节点为当叶子结点,此时弹出当前叶子结点时,
节点栈中栈定节点为之前弹出节点的双亲节点*/
current = stack.pop();
current.show();
pre = current;
current = null;
}
}
}
}
public static class BinaryTreeNode {
public String data;
public BinaryTreeNode leftChildNode;
public BinaryTreeNode rightChildNode;
public void show() {
Util.d(data);
}
}
}