这篇博客写了二叉树具体实现,如果没有看过树的基础知识建议大家先查看树的基础知识后再来看这篇文章,会更容易理解。数据结构-树
二叉树定义
- 二叉树特点定义
- 每个结点最多有两个子树,既结点度不大于2;
- 左树和右树是有有顺序的,位置不能颠倒;
- 即使结点只有一颗子树,也到区别左树还是右树;
- 特殊二叉树
- 满二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,且所有叶子结点都在一层上,那么这样的二叉树为满二叉树。
- 完全二叉树:对一棵有n个结点的二叉树按层序编号,如果编号i的结点(0<i<n)与同样深度的满二叉树中结点i处在同样的位置,则这棵二叉树为完全二叉树。
- 二叉树性质(3+2)
- 二叉树i层至多2^(i-1)个结点
- 深度为k的二叉树至多有2^k -1个结点
- 对任何一棵二叉树T,如果其终端结点为n0,度为2的结点数n2,则n=n2+1;
终端结点就是叶子结点,而一棵二叉树除了叶子结点之外就只有度为1和2的结点了。
设二叉树总结点数为n,终端叶子结点数为n0,度为1结点数为n1,度为2结点数为n2,则n=n0+n1+n2;
上图结点数不同颜色为度0,1,2 结点,加起来就是二叉树的结点数。
- 对于特殊二叉树(满二叉树、完全二叉树)还有下面两个性质
-
对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2(i=1 时为根,除外)。
二叉树存储结构
顺序线性存储:
二叉链表:
二叉树数据结构
- 二叉树的结点类
class BtNode {
private BtNode leftChild=null;//设置值只是因为调用无参构造时用处
private BtNode rightChild=null;
private char data='a';
public BtNode() {
}
public BtNode(char data) {
this.data = data;
}
public BtNode(BtNode leftChild, BtNode rightChild, char data) {
this.leftChild = leftChild;
this.rightChild = rightChild;
this.data = data;
}
public BtNode getLeftChild() {
return leftChild;
}
public void setLeftChild(BtNode leftChild) {
this.leftChild = leftChild;
}
public BtNode getRightChild() {
return rightChild;
}
public void setRightChild(BtNode rightChild) {
this.rightChild = rightChild;
}
public char getData() {
return data;
}
public void setData(char data) {
this.data = data;
}
}
- 二叉树类
class BinaryTree{
BtNode root=null; //根节点
public BinaryTree() {
}
}
二叉树的遍历
二叉树的遍历是从根结点出发,按照某种次序一次访问二叉树的所有结点。使每个结点被访问一次,且仅被访问一次。
树的遍历大体分为两类深度优先遍历和广度优先遍历。而深度优先遍历里面又有前序中序后序遍历,广度优先遍历里面是层序遍历。
前序遍历
- 若二叉树为空,则退出;
- 否则访问根节点
- 递归访问左子树
- 递归访问右子树
- 实现代码
/**
* 先序遍历
* @param ptr
*/
public void preOrder(BtNode ptr){
if (ptr != null){
System.out.print(ptr.getData());//输出根节点
preOrder(ptr.getLeftChild());//递归访问左子树
preOrder(ptr.getRightChild());//递归访问右子树
}
}
中序遍历
- 若二叉树为空,则退出;
- 递归中序遍历左子树
- 访问根节点
- 递归中序遍历右子树
- 实现代码
/**
* 中序遍历
* @param ptr
*/
public void middleOrder(BtNode ptr){
if (ptr!=null){
middleOrder(ptr.getLeftChild());
System.out.print(ptr.getData());
middleOrder(ptr.getRightChild());
}
}
后序遍历
- 若二叉树为空,则退出;
- 递归访问左子树
- 递归访问右子树
- 访问根节点
- 实现代码
/**
* 后序遍历
* @param ptr
*/
public void pastOrder(BtNode ptr){
if (ptr != null){
pastOrder(ptr.getRightChild());
pastOrder(ptr.getLeftChild());
System.out.print(ptr.getData());
}
}
层序遍历
看完四种遍历方式,我们可能会问,为什么需要这样遍历这么麻烦。我们要知道从图中我们肉眼能够很轻松的看到整颗树中的结点,但是计算机只能循环、遍历。所以我们需要把二叉树变成某种线性结构,方便程序遍历。
- 题目扩展:推到遍历结果
构建二叉树
我们构建二叉树是用前序遍历的思想来构建二叉树
如果我们要建立一个如下图左图中的二叉树,为了能让每个结点确认是否有左右孩子,我们需要对左图二叉树进行扩展,如果空结点我们为了让每个创建到某个结点时知道,我们可以使用一个特殊字符作为标记,我这里使用#。我们称右图为左图的扩展二叉树。
- 下面我们开始构建一颗二叉树:
假设我们输入下列一串前序遍历的序列,无子节点地方输入#,程序为我们自动构建二叉树,并将二叉树前序,中序,后序输出。
ABC##DE##F##G#H##
- 需要思考
这里我们想要知道,构建二叉树最好就是输入前序遍历序列来构建。中序和后序序列无法用来构建二叉树。但是后面我们还可以通过任意两个序列来构建二叉树。
- 实现代码
/**
* 创建树
*/
public void creatTreeByPreOrder(){
root=creatBtTree();
}
/**
* 根据键盘的输入创建树
* 用先序遍历思想进行创建二叉树
* @return nowNode
*/
private BtNode creatBtTree() {
char item;
item=new Scanner(System.in).next().charAt(0);
BtNode nowNode=null;
if (item != '#'){
nowNode=new BtNode(item);
nowNode.setLeftChild(creatBtTree());
nowNode.setRightChild(creatBtTree());
}
return nowNode;
}
public static void main(String[] args) {
BinaryTree binaryTree=new BinaryTree();
binaryTree.creatTree();
binaryTree.preOrder(binaryTree.root);
binaryTree.middleOrder(binaryTree.root);
binaryTree.pastOrder(binaryTree.root);
}