树
树是n(n>=0)个结点的有限集
- 它可以是一颗空树(n=0),空树不包括任何结点
- 或者是一颗非空树(n>0),此时有且只有一个特定的称为根的结点,当n>1时,其余结点可分为m个互不相交的有限集,其中每一个本身又是一棵树,称为根的子树(sub tree)
结点的度与树的度
- 结点拥有的子树的个数称为结点的度
- 度为0的结点称为叶子或者是终端结点
- 度不为0的结点称为非终端节点或者是分支结点,除了根之外的结点也成为内部结点
- 树内各结点的度的最大数称为树的度
如上图C中所示,结点B的度为2,C的度为1,D的度为3,则树的度为3.
结点的层次和树的深度:
- 结点层次从根开始定义,层数为1的结点是根结点,其子树的根的层次数为2。
- 树中结点的最大层次数称为树的深度或高度。
有序树、m叉树、森林
-
有序树: 如果将树中结点的各子树看成是从左到右次序的,则称该树为有序树,若不考虑子树的顺序则为无序树,对于有序树我们可以明确的定义每个结点的第一个孩子,第二个孩子直到最后一个孩子。
我们一般讨论的都是有序树。
-
m叉树: 树中所有结点最大度数为m的有序数称为m叉树
如上图所示,结点D的度为3,则称为三叉树 -
森林(forest):m(m>=0)棵互不相交的树的集合,对树中每个结点而言,其子树的集合为森林。
树和森林的概念相近,去除一棵树的根,就得到一个森林,反之亦然。
把A结点去除,B,C,D各自又是一颗树,三棵树的集合称为森林
二叉树
- 每个结点的度都不超过2的有序数,成为二叉树(binary tree)
- 与树的递归定义相似,二叉树的递归定义如下:
二叉树或者是一个空树,或者是一颗由一个根节点和两颗互不相交的分别为根的左子树和根的右子树组成的非空树。
由以上定义可得: - 二叉树的每个结点的孩子只能是0、1、2个,并且每个孩子都有左右之分。
- 以左孩子为根的子树叫做左子树,以右孩子为根的子树叫做右子树
满二叉树
- 高度为k并且有2的k+1次方-1个结点的二叉树
- 在满二叉树,每层结点都到最大数,每层结点都是满的,因此成为满二叉树
完全二叉树
- 若在一颗满二叉树中,在最下层的最右侧起去掉相邻的叶子结点,得到的二叉树为完全二叉树
满二叉树必为完全二叉树,完全二叉树不一定为满二叉树
二叉树的性质:
- 性质1:在二叉树的第i层上最多有2的i次方个结点。
- 性质2:高度为h 二叉树至多有2的h次方-1结点
- 性质3:对于任何一个二叉树,其终端节点为n0,度数为2的节点数为n1,则n0=n1+1
- 性质4::有n个结点的完全二叉树的高度为log2n+1,其中log2n是向下取整
- 性质5:含有n>=1个结点的二叉树高度至多为n-1,高度至少为log2n+1,其中log2n是向下取整
- 性质6:如果对一个有n个结点的完全二叉树的结点进行编号,则对其任一结点(1 =< i =< n):
1.如果i=1,则结点i是二叉树的根,如果i>1,则结点i的父节点为i/2
2.如果2i>0,则结点i无左孩子,否则其左孩子是结点2i
熟悉一下二叉树的性质,对于后面了解二叉树的存储结构有一定的帮助。
二叉树有两种存储结构:顺序存储和链式存储
接下来看看吧~~~
顺序存储结构
- 对于满二叉树和完全二叉树来说,可以将其数据元素逐层放到一组连续的存储单元中,
用一维数组来实现顺序存储结构时,将二叉树编号为 i的结点存放到数组中的第i个分量中,
如此根据二叉树性质,可以到结点i的父节点,左右孩子分别存放在2i及2i+1的分量中。
这种存储方式对于满二叉树和完全二叉树来说是非常适合和非常高效的。 因为满二叉树和完全二叉树采用顺序存储结构既不浪费空间,也可以根据公式很快的定位结点之间的关系。 但是对于一般的二叉树,必须用虚节点将一颗二叉树补成一颗完全二叉树来存储,否则无法确定结点之间的前驱后继关系,但是这样一来机会造成空间的浪费。如下图所示:
链式存储结构
- 设计不同的节点结构而构成不同的链式存储结构
在二叉树中每个结点都有两个孩子,则可以设计每个结点至少包括三个域:数据域,左孩子域,右孩子域 - 数据域存放数据元素,左孩子域存放指向做孩子的指针,右孩子域存放指向有孩子的指针,利用此结点结构得到的二叉树存储结构称为二叉链表。
- 为了方便找到父节点,可以在上述结点结构中增加一个指针域,指向结点的父节点,利用此节点的结点结构得到的二叉树存储结构称为三叉链表。
三叉链表相对于二叉链表多了一个指针域用于指向父节点,这样查找一个节点父节点就不需要从根节点依次查找,提升了查找的效率,但是相对于二叉链表占用了更多的空间,拿空间换时间以提升效率。
二叉树的遍历
遍历(Traversal):
- 所谓遍历,是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,是二叉树上进行其它运算之基础。当然遍历的概念也适合于多元素集合的情况,如数组。
树的遍历可以看成将非线性结构线性化
将整个二叉树看成三部分:根 左子树 右子树
如果按照规定先遍历左子树再遍历右子树
那么根据根的遍历顺序就有三种遍历方式(层序遍历先不说,在实际开发中用到的并不多):
- 先序遍历: 根 左子树 右子树
- 中序遍历: 左子树 根 右子树
- 后序遍历: 左子树 右子树 根
看张图先体会一下:
如果你认为你找到了它们的规律,那么这样的一个问题你可以解决吗?已知后序遍历132654,中序遍历123456,你可以得到它的先序遍历吗?如果你不知道,看完后面也许你就可以解答这个问题了~~~~(如果你已经知道答案,你可以设想一下在程序代码中,它的逻辑是怎样的呢?)
二叉树遍历实现
我们用链表来实现二叉树的遍历。
- 首先定义二叉链表的结点
/*
* 二叉链表的结点
*/
public class Node<E> {
E e;//结点值
Node leftChild;//左子树的引用
Node rightChild;//右子树的引用
public Node(E e) {
// TODO Auto-generated constructor stub
super();
this.e = e;
}
public Node(E e, Node leftChild, Node rightChild){
super();
this.e = e;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
@Override
public String toString() {
return "Node [e=" + e + ", leftChild="
+ leftChild + ", rightChild=" + rightChild + "]";
}
}
- 定义二叉树的接口
/**
* 二叉树接口
* 可以有不同的实现类,每个类可以使用不同的存储结构,比如顺序存储,链式存储
* @param <E>
*/
public interface BinaryTree<E> {
/**
* 是否是空树
* @return
*/
public boolean isEmpty();
/**
* 叔的结点的数量
* @return
*/
public int size();
/**
* 树的高度
* @return
*/
public int getHeight();
/**
* 查询指定值的结点
* @param e
* @return
*/
public Node findKey(E e);
/**
* 先序递归遍历
*/
public void preOrderTraverse();
/**
* 中序递归遍历
*/
public void inOrderTraverse();
/**
* 后序递归遍历
*/
public void postOrderTraverse();
/**
* 后序递归遍历
* @param 根结点
*/
public void postOrderTraverse(Node node);
/**
* 中序非递归遍历
* 1)对于任意结点current,若该节点不为空则将该节点压栈,并将左子树置为current,重复此操作,直到current为空。
* 2)若左子树为空,栈顶节点出栈,访问节点后将该节点的右子树置为current
* 3)重复1,2操作,直到current为空且栈内结点为空
*/
public void inOrderByStack();
/**
* 后序非递归遍历
* 1)对于任意节点current,若该节点不为空则访问该节点后再将节点压栈,并将左子树节点置为current,重复此操作,直到current为空。
* 2)若左子树为空,取栈顶节点的右子树,如果右子树为空或右子树刚访问过,则访问该节点,并将preNode置为该节点
* 3)重复1,2操作,直到current为空且栈内结点为空
*/
public void postOrderByStack();
/**
* 先序非递归遍历
* 1)对于任意节点current,若该节点不为空则访问该节点后再将节点压栈,并将左子树节点置为current,重复此操作,直到current为空。
* 2)若左子树为空,栈顶节点出栈,将该节点的右子树置为current
* 3)重复1,2操作,直到current为空且栈内结点为空
*/
public void preOrderByStack();
/**
* 按照层次遍历二叉树
*/
public void levelOrderByStack();
}
- 接口的实现(
重点
)
public class LinkedBinaryTree<E> implements BinaryTree<E> {
private Node root;//根结点
//private int size;
public LinkedBinaryTree() {
super();
}
public LinkedBinaryTree(Node root) {
super();
this.root = root;
}
public boolean isEmpty() {
return root == null;
}
public int size() {
System.out.println("二叉树结点个数:");
return this.size(root);
}
private int size(Node root) {
if(root == null){
return 0;
}else{
System.out.println(root);
//获取左子树的size
int nl = this.size(root.leftChild);
//获取右子树的size
int nr = this.size(root.rightChild);
//返回左子树、右子树size之和并加1
return nl+nr+1;
}
}
public int getHeight() {
System.out.println("二叉树的高度是:");
return this.getHeight(root);
}
private int getHeight(Node root){
if(root == null){
return 0;
}else{
//获取左子树的高度
int nl = this.getHeight(root.leftChild);
//获取右子树的高度
int nr = this.getHeight(root.rightChild);
//返回左子树、右子树较大高度并加1
return nl > nr ? nl+1:nr+1;
}
}
public Node findKey(E e) {
return this.findKey(e, root);
}
public Node findKey(E e,Node root) {
if(root == null){//递归结束条件1:结点为空,可能是整个树的根节点,也可能是递归调用中叶子节点中左孩子和右孩子
return null;
}else if(root != null && root.e == e){//递归的结束条件2:找到了
return root;
}else {//递归体
Node node1 = this.findKey(e, root.leftChild);
Node node2 = this.findKey(e, root.rightChild);
if(node1 != null && node1.e == e){
return node1;
}else if(node2 != null && node2.e == e){
return node2;
}else{
return null;
}
}
}
public void preOrderTraverse() {
if(root != null){
//1.输出根结点的值
System.out.print(root.e+" ");
//2.对左子树进行先序遍历
//构建一个二叉树,根是左子树的根
BinaryTree leftTree = new LinkedBinaryTree(root.leftChild);
leftTree.preOrderTraverse();
//对右子树进行先序遍历
//3.构建一个二叉树,根是左子树的根
BinaryTree rightTree = new LinkedBinaryTree(root.rightChild);
rightTree.preOrderTraverse();
}
}
public void inOrderTraverse() {
System.out.println("中序遍历");
this.inOrderTraverse(root);
System.out.println();
}
private void inOrderTraverse(Node root) {//node7
if(root != null){
//遍历左子树
this.inOrderTraverse(root.leftChild);//null
//输出根的值
System.out.print(root.e+" ");//7
//遍历右子树
this.inOrderTraverse(root.rightChild);//null
}
}
public void postOrderTraverse() {
System.out.println("后序遍历");
this.postOrderTraverse(root);
System.out.println();
}
public void postOrderTraverse(Node node) {
if(node != null){
//遍历左子树
this.postOrderTraverse(node.leftChild);
//遍历右子树
this.postOrderTraverse(node.rightChild);
//输出根的值
System.out.print(node.e+" ");
}
}
public void inOrderByStack() {
System.out.println("中序非递归遍历:");
// 创建栈
Deque<Node> stack = new LinkedList<Node>();
Node current = root;
while (current != null || !stack.isEmpty()) {
while (current != null) {
stack.push(current);
current = current.leftChild;
}
if (!stack.isEmpty()) {
current = stack.pop();
System.out.print(current.e + " ");
current = current.rightChild;
}
}
System.out.println();
}
public void preOrderByStack() {
// TODO Auto-generated method stub
}
public void postOrderByStack() {
// TODO Auto-generated method stub
}
public void levelOrderByStack() {
System.out.println("按照层次遍历二叉树");
if(root == null) return;
Queue<Node> queue = new LinkedList<Node>() ;
queue.add(root);
while(queue.size() != 0)
{
int len = queue.size();
for(int i=0;i <len; i++)
{
Node temp = queue.poll();
System.out.print(temp.e+" ");
if(temp.leftChild != null) queue.add(temp.leftChild);
if(temp.rightChild != null) queue.add(temp.rightChild);
}
}
System.out.println();
}
}
- 测试
以下图测试:
测试代码:
public class Test {
public static void main(String[] args) {
//创建一个二叉树
Node node5 = new Node(5, null, null);
Node node4 = new Node(4, null, node5);
Node node3 = new Node(3, null, null);
Node node7 = new Node(7, null, null);
Node node6 = new Node(6, null, node7);
Node node2 = new Node(2, node3, node6);
Node node1 = new Node(1,node4,node2);
BinaryTree btree = new LinkedBinaryTree(node1);
//BinaryTree btree = new LinkedBinaryTree();
//判断二叉树是否为空
System.out.println(btree.isEmpty());
//先序遍历递归 1 4 5 2 3 6 7
System.out.println("先序遍历");
btree.preOrderTraverse();
System.out.println();
//中序遍历递归 4 5 1 3 2 6 7
btree.inOrderTraverse();
//后序遍历递归 5 4 3 7 6 2 1
btree.postOrderTraverse();
//中序遍历非递归(借助栈) 4 5 1 3 2 6 7
btree.inOrderByStack();
//按照层次遍历(借助队列) 1 4 2 5 3 6 7
btree.levelOrderByStack();
//在二叉树中查找某个值
System.out.println(btree.findKey(1));
//二叉树的高度
System.out.println(btree.getHeight());
//二叉树的结点数量
System.out.println(btree.size());
}
}
本博客文章皆出于学习目的,个人总结或摘抄整理自网络。引用参考部分在文章中都有原文链接,如疏忽未给出请联系本人。另外,如文章内容有错误,欢迎各方大神指导交流。