数据结构基础——二叉树 (java实现)

数据结构基础 二叉树 (java)


本文介绍数据结构中的二叉树及其在Java中的具体实现。

二叉树的特殊形态

  1. 空二叉树
  2. 只有一个根节点
  3. 只有左子树
  4. 只有右子树
  5. 完全二叉树
  6. 满二叉树

二叉树的基本性质

  1. 二叉树的第i层最多有2i-1(i>1)个节点。
  2. 深度为h的二叉树最多有2h 个节点。
  3. 若在任意一棵二叉树中,有n个叶子节点,有n2 个度为2的节点则有 n = n2 + 1。
  4. 具有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;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 什么是二叉树? 二叉树是一种树形结构,其中每个节点最多有两个子节点。一个节点的左子节点比该节点小,右子节点比该节点大。二叉树通常用于搜索和排序。 2. 二叉树的遍历方法有哪些? 二叉树的遍历方法包括前序遍历、中序遍历和后序遍历。前序遍历是从根节点开始遍历,先访问根节点,再访问左子树,最后访问右子树。中序遍历是从根节点开始遍历,先访问左子树,再访问根节点,最后访问右子树。后序遍历是从根节点开始遍历,先访问左子树,再访问右子树,最后访问根节点。 3. 二叉树的查找方法有哪些? 二叉树的查找方法包括递归查找和非递归查找。递归查找是从根节点开始查找,如果当前节点的值等于要查找的值,则返回当前节点。如果要查找的值比当前节点小,则继续在左子树中查找;如果要查找的值比当前节点大,则继续在右子树中查找。非递归查找可以使用栈或队列实现,从根节点开始,每次将当前节点的左右子节点入栈/队列,直到找到要查找的值或者栈/队列为空。 4. 二叉树的插入与删除操作如何实现? 二叉树的插入操作是将要插入的节点与当前节点的值进行比较,如果小于当前节点的值,则继续在左子树中插入;如果大于当前节点的值,则继续在右子树中插入。当找到一个空节点时,就将要插入的节点作为该空节点的子节点。删除操作需要分为三种情况:删除叶子节点、删除只有一个子节点的节点和删除有两个子节点的节点。删除叶子节点很简单,只需要将其父节点的对应子节点置为空即可。删除只有一个子节点的节点,需要将其子节点替换为该节点的位置。删除有两个子节点的节点,则可以找到该节点的后继节点(即右子树中最小的节点),将其替换为该节点,然后删除后继节点。 5. 什么是平衡二叉树? 平衡二叉树是一种特殊的二叉树,它保证左右子树的高度差不超过1。这种平衡可以确保二叉树的查找、插入和删除操作的时间复杂度都是O(logn)。常见的平衡二叉树包括红黑树和AVL树。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值