链表、栈以及队列都是线性的数据结构,元素存储起来较为简单,元素只存在一个对一个的关系,而树则是一种更为复杂的数据结构,这种结构元素存在一个对多个的关系,一个父节点可以包括多个子节点。二叉树是一种特殊的树,每个节点最多只有两个子树,而且子树区分是左节点和右节点,次序不能颠倒。
树和二叉树的区别如下所示:
与线性表的实现相似,二叉树也有两种实现方式,一种是基于数组的二叉树,另一种是基于链表的二叉树,但是基于数组的二叉树可能会产生一定的空间浪费,当二叉树为完全二叉树时,则不会存在浪费空间的问题了。而链式存储无论二叉树结构如何都不会存在这样的问题,所以二叉树的实现一般都采用基于链表的方式。
二叉树的常用操作如下所示:
1.基于数组的二叉树
按照完全二叉树的节点按层依次自左向右编号,若节点为空,则在数组中留空。
数组实现的二叉树如下所示:
public class ArrayTree {
private T[] data;
private int deep;
private int MaxSize;
private int size = 0;
public ArrayTree(int deep){
this.deep = deep;
//根据二叉树的深度计算元素的最大个数
MaxSize = (int)Math.pow(2, deep)-1;
data = (T[]) new Object[MaxSize];
}
/**
*
* @param value 根节点的值
*/
public void creatRoot(T value){
data[0] = value;
size++;
}
/**
* @param value 添加的节点的值
* @param index 父节点的索引
* @param left 是否添加到左节点,否则为右节点
* */
public void add(T value,int index,boolean left){
if(data[index] == null){
throw new RuntimeException(index + ": null");
}
if(2*index+1 >= MaxSize){
throw new RuntimeException("array full");
}
if(left){
data[2*index+1] = value;
size++;
}
else{
data[2*index+2] = value;
size++;
}
}
//根据根节点是否为空判断二叉树是否为空
public boolean isEmpty(){
return data[0]==null;
}
//二叉树中元素的个数
public int size(){
return this.size;
}
//打印二叉树
public String toString(){
String str = "";
for(int i=0; i" ";
}
return str;
}
public static void main(String args[]){
ArrayTree at = new ArrayTree(3);
/**
* 根节点
* 第二层左节点 第二层右节点
* 第三层右节点 第三层左节点
*/
at.creatRoot("根节点");
at.add("第二层左节点", 0, true);
at.add("第二层右节点", 0, false);
at.add("第三层右节点", 1, false);
System.out.println(at.toString());
at.add("第三层左节点", 2, true);
System.out.println(at.toString());
System.out.println("二叉树元素个数: " + at.size());
}
}
测试结果:
根节点 第二层左节点 第二层右节点 null 第三层右节点 null null
根节点 第二层左节点 第二层右节点 null 第三层右节点 第三层左节点 null
二叉树元素个数: 5
2.基于链表的二叉树
基于链表的二叉树需要在每个节点存储它的左节点和右节点,当然也可以再加上父节点。
链表实现的二叉树如下所示:
import java.util.ArrayList;
import java.util.List;
public class NodeTree<T> {
class Node<T>{
public Node left;//左子树
public Node right;//右子树
public T data;
//二叉树的每个节点
public Node(T data){
this.data = data;
this.left = null;
this.right = null;
}
public Node(){}
}
private Node root = null;
private int size = 0;
/**
* 创建二叉树的根节点
* @param value 根节点的值
*/
public void createRoot(T value){
if(value == null){
throw new RuntimeException("头节点为空");
}
Node newNode = new Node(value);
this.root = newNode;
size++;
}
/**
* 获取根节点
* @return 返回根节点root
*/
public Node getRoot(){
if(root == null){
throw new RuntimeException("头节点为空");
}
return this.root;
}
/**
* 判断二叉树是否为空
* @return
*/
public boolean isEmpty(){
return root==null;
}
/**
* 返回二叉树中元素的个数
* @return
*/
public int size(){
return size;
}
/**
* 添加节点
* @param n 父节点
* @param value 添加节点的元素值
* @param left 是否为左节点
* @return 返回添加的新节点
*/
public Node add(Node n,T value,boolean left){
if(value == null){
throw new RuntimeException("子节点为空");
}
if(n == null){
throw new RuntimeException("父节点为空");
}
Node newNode = new Node(value);
if(left){
if(n.left!=null){
throw new RuntimeException("已有左子节点");
}
n.left = newNode;
size++;
return newNode;
}
else{
if(n.right!=null){
throw new RuntimeException("已有右子节点");
}
n.right = newNode;
size++;
return newNode;
}
}
/**
* 返回树的深度
* @return
*/
public int height() {
return height(root);
}
public static void main(String args[]){
NodeTree nt = new NodeTree();
/**
* 根节点
* 第二层左节点 第二层右节点
* 第三层右节点 第三层左节点
* 第四层左节点
*/
nt.createRoot("根节点");
NodeTree.Node nt2 = nt.add(nt.root, "第二层左节点", true);
NodeTree.Node nt3 = nt.add(nt.root, "第二层右节点", false);
NodeTree.Node nt4 = nt.add(nt2, "第三层右节点", false);
NodeTree.Node nt5 = nt.add(nt3, "第三层左节点", true);
System.out.println("树的高度: " + nt.height());
NodeTree.Node nt6 = nt.add(nt4, "第四层左节点", true);
System.out.println("树的高度: " + nt.height());
System.out.println("元素个数: " + nt.size());
}
}
测试结果:
树的高度: 3
树的高度: 4
元素个数: 6
遍历二叉树是指按某种规律访问二叉树的每个节点,数组实现的二叉树直接打印数组即可,而链表实现的二叉树主要包括两类遍历方式:
深度优先的三种遍历方式命名都是针对根节点而言的,先处理根节点的称为先序遍历(根左右),其次处理根节点的为中序遍历(左根右),最后处理根节点的为后序遍历(左右根)。
1.先序遍历的实现
/**
* 先序遍历二叉树,采用根左右的顺序访问
* @return
*/
public List preIterator(){
return preIterator(root);
}
private List preIterator(Node n){
List list = new ArrayList();
list.add(n);
if(n.left!=null){
list.addAll(preIterator(n.left));
}
if(n.right!=null){
list.addAll(preIterator(n.right));
}
return list;
}
2.中序遍历的实现
/**
* 中序遍历二叉树,采用左根右的顺序访问
* @return
*/
public List inIterator(){
return inIterator(root);
}
public List inIterator(Node n){
List list = new ArrayList();
if(n.left!=null){
list.addAll(inIterator(n.left));
}
list.add(n);
if(n.right!=null){
list.addAll(inIterator(n.right));
}
return list;
}
3.后序遍历的实现
/**
* 后序遍历二叉树,采用左右根的顺序访问
* @return
*/
public List postIterator(){
return postIterator(root);
}
public List postIterator(Node n){
List list = new ArrayList();
if(n.left!=null){
list.addAll(postIterator(n.left));
}
if(n.right!=null){
list.addAll(postIterator(n.right));
}
list.add(n);
return list;
}
测试代码:
public static void main(String args[]){
NodeTree nt = new NodeTree();
/**
* 根节点
* 第二层左节点 第二层右节点
* 第三层右节点 第三层左节点
* 第四层左节点
*/
nt.createRoot("根节点");
NodeTree.Node nt2 = nt.add(nt.root, "第二层左节点", true);
NodeTree.Node nt3 = nt.add(nt.root, "第二层右节点", false);
NodeTree.Node nt4 = nt.add(nt2, "第三层右节点", false);
NodeTree.Node nt5 = nt.add(nt3, "第三层左节点", true);
NodeTree.Node nt6 = nt.add(nt4, "第四层左节点", true);
List.Node> prelist = new ArrayList.Node>();
System.out.print("先序遍历: ");
prelist = nt.preIterator();
for(NodeTree.Node n:prelist){
System.out.print(n.data + " ");
}
System.out.println();
System.out.print("中序遍历: ");
List.Node> inlist = new ArrayList.Node>();
inlist = nt.inIterator();
for(NodeTree.Node n:inlist){
System.out.print(n.data + " ");
}
System.out.println();
System.out.print("后序遍历: ");
List.Node> postlist = new ArrayList.Node>();
postlist = nt.postIterator();
for(NodeTree.Node n:postlist){
System.out.print(n.data + " ");
}
System.out.println();
}
测试结果:
先序遍历: 根节点 第二层左节点 第三层右节点 第四层左节点 第二层右节点 第三层左节点
中序遍历: 第二层左节点 第四层左节点 第三层右节点 根节点 第三层左节点 第二层右节点
后序遍历: 第四层左节点 第三层右节点 第二层左节点 第三层左节点 第二层右节点 根节点