5、二叉树(上)
树中节点的度数没限制,而二叉树中树的度数最大为2,无序树的节点无左右之分,而二叉树的节点有左右之分,也即二叉树是有序树。本文主要介绍二叉树的基本性质以及二叉树的顺序存储和链式存储的实现。
两种特殊的二叉树:
- 满二叉树:指的是深度为k且含有(2^k)-1个结点的二叉树
- 完全二叉树:树中所含的 n 个结点和满二叉树中编号为 1 至 n 的结点一一对应。(编号的规则为,由上到下,从左到右)
5.1 二叉树的性质
二叉树有如下几个重要性质:
- 二叉树的第i层的节点数目最多为2^(i-1),其中i≥1
- 深度为k的二叉树最多有2^k-1个节点,深度为k的二叉树每层的节点数的最大值为等比为2的等比数列,其节点总数最多为等比数列的前k项和
- 在任何一颗二叉树中,如果叶子节点的数量为n0,度为2的节点的数量为n2,n0 = n2 + 1
- 具有n个节点的完全二叉树的深度为log2(n)+1
- 若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点:
(1) 若 i=1,则该结点是二叉树的根,无双亲,否则,编号为 i/2的结点为其双亲结点;
(2) 若 2i>n,则该结点无左孩子,否则,编号为 2i 的结点为其左孩子结点;
(3) 若 2i+1>n,则该结点无右孩子结点,否则,编号为2i+1 的结点为其右孩子结点。
5.2 二叉树的顺序存储
顺序存储指的是充分利用满二叉树的性质,满二叉树的第K层有2^(K-1)个节点,则利用一个长度为2^K-1的数组便可以保存二叉树中所有的元素。
使用数组来存储二叉树中的节点可能会产生一定的空间浪费,如果该二叉树是完全二叉树,则不会有任何浪费;如果是一颗斜二叉树,则会产生一定的空间浪费。
顺序存储的二叉树,不管是遍历树中节点还是查询树中的节点,都可以非常高效的完成,唯一的缺点是空间浪费大。
二叉树的顺序存储实现如下:
import java.util.Arrays;
/**
* 二叉树的顺序存储
* @author Administrator
*
*/
public class ArrayBinTree<T> {
//使用数组来记录所有的节点
private Object[] datas;
//定义树的默认深度
private int DEFAULT_DEEP = 8;
//保存树的深度
private int deep;
//保存数组的长度
private int arraySize;
/**
* 以默认深度创建二叉树
*/
public ArrayBinTree(){
this.deep = DEFAULT_DEEP;
arraySize = (int) (Math.pow(2,deep) - 1);
datas = new Object[arraySize];
}
/**
* 以指定深度定义二叉树
* @param deep 深度
*/
public ArrayBinTree(int deep){
this.deep = deep;
arraySize = (int) Math.pow(2, deep) - 1;
datas = new Object[arraySize];
}
/**
* 以指定深度和指定根节点创建树
* @param deep 深度
* @param data 根
*/
public ArrayBinTree(int deep,T data){
this.deep = deep;
arraySize = (int) Math.pow(2, deep) - 1;
datas = new Object[arraySize];
datas[0] = data;
}
/**
* 为指定节点添加子节点
* @param index 指定节点在数组中的索引
* @param data 要添加的数据
* @param left 添加的子节点是否为左子节点
*/
public void add(int index,T data,boolean left){
//若index处的节点为空
if(datas[index] == null){
throw new RuntimeException( index+"处的节点为空,无法添加子节点");
}
if(index * 2 + 1 >= arraySize){
throw new RuntimeException( "树底层的数组已满,数组越界");
}
//添加左子节点
if(left){
datas[index * 2 + 1] = data;
}else{
datas[index * 2 + 2] = data;
}
}
/**
* 根据根元素判断树是否为空
* @return 根节点是否为空
*/
public boolean isEmpty(){
return datas[0] == null;
}
/**
* 获取根节点
* @return 返回根节点
*/
public T root(){
return (T) datas[0];
}
/**
* 获取指定节点的父节点
* @param index 指定节点的索引
* @return 返回父节点
*/
public T parent(int index){
if(index != 0)
return (T) datas[(index - 1) / 2];
return null;
}
/**
* 获得指定节点的左子节点,若左子节点不存在,返回null
* @param index 指定节点
* @return
*/
public T left(int index){
if(index * 2 + 1 >= arraySize){
throw new RuntimeException( "该节点为叶子节点,无子节点");
}
return (T) datas[index * 2 + 1];
}
//获得指定节点的右子节点,若左子节点不存在,返回null
public T right(int index){
if(index * 2 + 1 >= arraySize){
throw new RuntimeException( "该节点为叶子节点,无子节点");
}
return (T) datas[index * 2 + 2];
}
//获取二叉树的深度
public int deep(){
return deep;
}
//获得指定元素的位置
public int pos(T data){
//按照广度遍历二叉树中的元素
for (int i = 0; i < arraySize; i++) {
if(datas[i] == data){
return i;
}
}
return -1;
}
public String toString(){
return Arrays.toString(datas);
}
}
5.3 二叉树的链式存储
二叉树的链式存储比较常用的是二叉链表存储(分别用两个指针指向左孩子和右孩子)和三叉链表存储(三个指针分别指向父节点以及左右孩子节点)。
程序中采用链表来记录树中的数据,所以添加节点没有限制,而且不会像顺序存储那样浪费空间。
5.3.1 二叉链表实现
/**
* 二叉树的二叉链表实现
*
* @author Administrator
*
*/
public class TwoLinkBintree<E> {
/*
* 二叉树的节点定义
*/
public class TreeNode {
E data;
TreeNode left;
TreeNode right;
public TreeNode() {
}
public TreeNode(E data) {
this.data = data;
}
public TreeNode(E data, TreeNode left, TreeNode right) {
this.data = data;
this.left = left;
this.right = right;
}
}
private TreeNode root;
//以默认构造器创建树
public TwoLinkBintree() {
this.root = new TreeNode();
}
//以指定元素为根节点创建树
public TwoLinkBintree(E data){
this.root = new TreeNode(data);
}
/**
* 为指定节点添加子节点
* @param parent 指定节点
* @param data 新节点的数据
* @param left 是否为左子节点
* @return 返回新节点
*/
public TreeNode addNode(TreeNode parent,E data,boolean left){
if(parent == null){
throw new RuntimeException(parent +"节点为空节点,不能创建子节点");
}
if(left && parent.left != null){
throw new RuntimeException("节点已经存在左子节点,无法再添加左子节点");
}
if(!left && parent.right != null){
throw new RuntimeException("节点已经存在右子节点,无法再添加右子节点");
}
TreeNode newNode = new TreeNode(data);
if(left){
parent.left = newNode;
}else{
parent.right = newNode;
}
return newNode;
}
//判断二叉树是否为空
public boolean isEmpty(){
return root.data == null;
}
//获取根节点
public TreeNode root(){
if(isEmpty()){
throw new RuntimeException("树为空,无法访问根节点");
}
return root;
}
//返回指定节点的左子节点
public Object leftChild(TreeNode parent){
if(parent == null){
throw new RuntimeException("节点为空,无法添加左子节点");
}
return parent.left == null ? null : parent.left.data;
}
//返回指定节点的右子节点
public Object rightChild(TreeNode parent){
if(parent == null){
throw new RuntimeException("节点为空,无法添加右子节点");
}
return parent == null ? null : parent.right.data;
}
//获取二叉树的深度
public int deep(){
return deep(root);
}
//获取指定节点为跟的子树的深度
private int deep(TreeNode node) {
//若节点为空
if(node == null){
return 0;
}
//没有子树
if(node.left == null && node.right == null){
return 1;
}else{
int leftDeep = deep(node.left);
int rightDeep = deep(node.right);
//获得左右子树的最大深度
int max = leftDeep >= rightDeep ? leftDeep : rightDeep;
return max + 1;
}
}
}
5.3.2 三叉链表实现
二叉树的三叉链表实现是对二叉链表实现的一种改进,通过增加一个指向父节点的引用,使得二叉树既可以向下遍历,又可以向上遍历。
/**
* 二叉树的三叉链表表示
*
* @author Administrator
*
*/
public class ThreeLinkBinTree<E> {
/**
* 二叉树的节点类
*
* @author Administrator
*
*/
public class TreeNode {
E data;
TreeNode left;
TreeNode right;
TreeNode parent;
public TreeNode() {
}
public TreeNode(E data) {
this.data = data;
}
public TreeNode(E data, TreeNode left, TreeNode right, TreeNode parent) {
this.data = data;
this.left = left;
this.right = right;
this.parent = parent;
}
}
private TreeNode root;
// 以默认的构造器创建二叉树
public ThreeLinkBinTree() {
this.root = new TreeNode();
}
// 以指定根节点创建二叉树
public ThreeLinkBinTree(E data) {
this.root = new TreeNode(data);
}
/**
* 为指定节点添加子节点
*
* @param parent
* 指定节点
* @param data
* 新节点的数据
* @param left
* 是否为左子节点
* @return 返回新节点
*/
public TreeNode addNode(TreeNode parent, E data, boolean left) {
if (parent == null) {
throw new RuntimeException(parent + "节点为空节点,不能创建子节点");
}
if (left && parent.left != null) {
throw new RuntimeException("节点已经存在左子节点,无法再添加左子节点");
}
if (!left && parent.right != null) {
throw new RuntimeException("节点已经存在右子节点,无法再添加右子节点");
}
// 创建新节点
TreeNode newNode = new TreeNode(data);
if (left) {
parent.left = newNode;
} else {
parent.right = newNode;
}
newNode.parent = parent;
return newNode;
}
// 判断二叉树是否为空
public boolean isEmpty() {
return root.data == null;
}
// 获取根节点
public TreeNode root() {
if (isEmpty()) {
throw new RuntimeException("树为空,无法访问根节点");
}
return root;
}
// 获取指定节点的父节点
public E parent(TreeNode node) {
if (node == null) {
throw new RuntimeException("节点为空,无法访问其父节点");
}
return (E) node.parent.data;
}
// 返回指定节点的左子节点
public E leftChild(TreeNode parent) {
if (parent == null) {
throw new RuntimeException("节点为空,无法添加左子节点");
}
return parent == null ? null : parent.left.data;
}
// 返回指定节点的右子节点
public Object rightChild(TreeNode parent) {
if (parent == null) {
throw new RuntimeException("节点为空,无法添加右子节点");
}
return parent == null ? null : parent.right.data;
}
// 获取二叉树的深度
public int deep() {
return deep(root);
}
//获取指定节点为跟的子树的深度
private int deep(TreeNode node) {
if(node == null){
return 0;
}
if(node.left == null && node.right == null){
return 1;
}
else{
int leftDeep = deep(node.left);
int rightDeep = deep(node.right);
int max = leftDeep >= rightDeep ? leftDeep : rightDeep;
return max + 1;
}
}
}