数据结构基础 二叉树 (java)
文章目录
本文介绍数据结构中的二叉树及其在Java中的具体实现。
二叉树的特殊形态
- 空二叉树
- 只有一个根节点
- 只有左子树
- 只有右子树
- 完全二叉树
- 满二叉树
二叉树的基本性质
- 二叉树的第i层最多有2i-1(i>1)个节点。
- 深度为h的二叉树最多有2h 个节点。
- 若在任意一棵二叉树中,有n个叶子节点,有n2 个度为2的节点则有 n = n2 + 1。
- 具有n个节点的完全二叉树深为log2n + 1 (向下取整)。
二叉树的节点类
public class TreeNode<T> {
T value; //该节点的值
TreeNode<T> leftChild;
TreeNode<T> rightChild;
//构造函数
TreeNode(T value){
this.value = value;
}
TreeNode(){}
//添加左孩子
public void addLeft(T value){
TreeNode<T> leftChild = new TreeNode<T>(value);
this.leftChild = leftChild;
}
//添加右孩子
public void addRight(T value){
TreeNode<T> rightChild = new TreeNode<T>(value);
this.rightChild = rightChild;
}
//重写equals方法
@Override
public boolean equals(Object obj){
if(this == obj) return true;
if(!(obj instanceof TreeNode)){
return false;
}
//将obj强制转换成TreeNode<T>类型
return this.value.equals(((TreeNode<?>)obj).value);
}
//重写hasCode方法
@Override
public int hashCode(){
return this.value.hashCode();
}
//重写toString方法
@Override
public String toString(){
return this.value == null?"":this.value.toString();
}
}
判断树的节点个数
实现细节:返回 以左孩子为根节点的节点个数 + 以右孩子为根节点的节点个数 + 1 ; 为空返回0。
/**
* 判断以root为根节点的树的节点个数
* @param root
* @param <T>
* @return
*/
public static <T> int getTreeNum(TreeNode<T> root){
if(root == null){
return 0;
}
return getTreeNum(root.leftChild) + getTreeNum(root.rightChild) + 1;
}
返回树的深度
实现细节:返回 左孩子节点的深度+1或者 右孩子节点的深度+1,返回其中的较大者。
/**
* 返回以root为根节点的树的深度
* @param root
* @param <T>
* @return
*/
public static <T> int getTreeDepth(TreeNode<T> root){
if(root == null){
return 0;
}
int leftDepth = getTreeDepth(root.leftChild) + 1;
int rightDepth = getTreeDepth(root.rightChild) + 1;
return Math.max(leftDepth,rightDepth);
}
前中后序遍历
实现细节:前序遍历把visitNode放前边,中序放中间,后序放后边。
/**
* 对以root为根节点的树前序遍历
* @param root
* @param <T>
*/
public static <T> void preOrderTravel(TreeNode<T> root){
if(root == null){
return ;
}
visitNode(root);
preOrderTravel(root.leftChild);
preOrderTravel(root.rightChild);
}
/**
* 对以root为根节点的树中序遍历
* @param root
* @param <T>
*/
public static <T> void midOrderTravel(TreeNode<T> root){
if(root == null){
return;
}
midOrderTravel(root.leftChild);
visitNode(root);
midOrderTravel(root.rightChild);
}
/**
* 对以root为根节点的树后序遍历
* @param root
* @param <T>
*/
public static <T> void backOrderTravel(TreeNode<T> root){
if(root == null){
return;
}
backOrderTravel(root.leftChild);
backOrderTravel(root.rightChild);
visitNode(root);
}
/**
* 输出节点的值
* @param node
* @param <T>
*/
private static <T> void visitNode(TreeNode<T> node){
System.out.print(node.value + "\t");
}
层序遍历
实现细节:利用队列,根节点入队;当队列非空时,节点出队,输出,该节点的左右孩子进队。
/**
* 层序遍历
* @param root
* @param <T>
*/
public static <T> void levelTravel(TreeNode<T> root){
Queue<TreeNode<T>> q = new LinkedList<>();
q.offer(root);
while(!q.isEmpty()){
TreeNode<T> temp = q.poll();
visitNode(temp);
if(temp.leftChild != null){
q.offer(temp.leftChild);
}
if(temp.rightChild != null){
q.offer(temp.rightChild);
}
}
}
返回第k层节点个数
实现细节:返回左孩子的第k-1层节点个数 + 右孩子的第k-1层节点个数
/**
* 返回以root为根节点的树的第k层节点个数
* @param root
* @param k
* @param <T>
* @return
*/
public static <T> int getNumForKlevel(TreeNode<T> root, int k){
if(root == null || k < 1){
return 0;
}
if( k == 1) {
return 1;
}
int leftNum = getNumForKlevel(root.leftChild , k - 1);
int rightNum = getNumForKlevel(root.rightChild, k - 1);
return leftNum + rightNum;
}
返回叶子节点个数
实现细节:返回 左孩子的叶子节点个数 + 右孩子的叶子节点个数 ;如果该节点没有左右孩子,返回1。
/**
* 返回以root为根节点的树的叶子节点数目
* @param root
* @param <T>
* @return
*/
public static <T> int getLeafNum(TreeNode<T> root){
if(root == null){
return 0;
}
if(root.leftChild == null && root.rightChild == null){
return 1;
}
int leftNum = getLeafNum(root.leftChild);
int rightNum = getLeafNum(root.rightChild);
return leftNum + rightNum;
}
根据前序和中序构建二叉树
实现细节:在中序序列中找到根节点,根节点前边是左子树的中序序列,后边是右子树的中序序列;在前序序列中根节点后边是左子树的前序序列,长度与左子树的中序序列相等,左子树的前序序列后边是右子树的前序序列。将左子树和右子树的前序和中序递归调用函数,直到序列只有一个元素时返回该元素。
举例:
前序:1 2 4 8 5 9 10 3 6 7
中序:8 4 2 9 5 10 1 6 3 7
中序的根节点“1”前面有6个元素:8 4 2 9 5 10,这六个元素对应树的左子树的中序序列。
取前序1后面的六个元素:2 4 8 5 9 10,这六个元素对应树的左子树的前序序列。
将这两个序列递归调用函数。
/**
* 根据前序和中序构建二叉树
* @param pre 前序序列
* @param mid 中序序列
* @param <T>
* @return TreeNode
*/
public static <T> TreeNode<T> getTreeFromPreAndMid(List<T> pre, List<T> mid){
//没有节点
if(pre == null || mid == null || pre.size()==0 || mid.size()==0){
return null;
}
//只有一个节点
if(pre.size() == 1){
return new TreeNode<T>(pre.get(0));
}
TreeNode<T> root = new TreeNode<T>(pre.get(0));
//找出根节点在中序中的位置
int index = 0;
while(!mid.get(index++).equals(pre.get(0))){}
//构建左子树的前序
List<T> preLeft = new ArrayList<T>(index);
//左子树的中序
List<T> midLeft = new ArrayList<T>(index);
for(int i = 1; i<index; i++){
preLeft.add(pre.get(i));
}
for(int i = 0; i<index-1; i++){
midLeft.add(mid.get(i));
}
//将左子树的前序和中序传入函数进行递归调用
root.leftChild = getTreeFromPreAndMid(preLeft,midLeft);
if(pre.size()-index > 0){
//右子树的前序
List<T> preRight = new ArrayList<T>(pre.size() - index );
//右子树的中序
List<T> midRight = new ArrayList<T>( mid.size() - index );
for(int i = 0; i < pre.size() - index ; i++){
preRight.add(pre.get(index + i));
}
for(int i = 0; i < pre.size() - index ; i++){
midRight.add(mid.get(index + i));
}
//将右子树的前序和中序传入函数进行递归调用
root.rightChild = getTreeFromPreAndMid(preRight, midRight);
}
return root;
}
判断两棵树是否相同
/**
* 查看node1和node2两棵树是否相等
* @param node1
* @param node2
* @param <T>
* @return boolean
*/
public static <T> boolean equals(TreeNode<T> node1, TreeNode<T> node2){
if(node1 == null && node2 == null){
return true;
}else if(node1 == null || node2 == null){
return false;
}
boolean isEqual = node1.value.equals(node2.value);
boolean isLeftEqual = equals(node1.leftChild, node2.leftChild);
boolean isRightEqual = equals(node1.rightChild,node2.rightChild);
return isEqual && isLeftEqual && isRightEqual;
}