二叉树的基本操作(遍历)

创建二叉树的前置说明

二叉树是由 递归方法 创建,但一上来就直接创建,难免会觉得抽象,所以我们先手动快速创建一棵简单的二叉树,先了解二叉树结构,之后再反过头来研究二叉树真正的创建方式

手动创建一棵二叉树

  • 穷举的方式 手动创建一棵如图所示的二叉树,树的表现形式用 —— 孩子表示法
    ![[Pasted image 20231016143942.png]]

  • 树的表现形式

static class BTNode {  
    // 孩子表示法  
    char val;  
    BTNode left; // 左子树  
    BTNode right; // 右子树  
    BTNode(char val) {  
        this.val = val;  
    }  
}
  • 手动创建一棵二叉树
public BTNode createTree() {  
    // 1.先创建结点,这与递归创建树逻辑顺序一致  
    BTNode A = new BTNode('A');  
    BTNode B = new BTNode('B');  
    BTNode C = new BTNode('C');  
    BTNode D = new BTNode('D');  
    BTNode E = new BTNode('E');  
    BTNode F = new BTNode('F');  
    BTNode G = new BTNode('G');  
    BTNode H = new BTNode('H');  
  
    // 2.将结点之间联系起来  
    A.left = B;  
    B.left = D;  
    B.right = E;  
    E.right = H;  
    A.right = C;  
    C.left = F;  
    C.right = G;  
  
    // 3.返回根结点  
    return A;  
}

实现树的遍历

理解树是怎么遍历的,可以很好地帮助我们去理解二叉树结构的运作方式。
遍历操作是二叉树上最重要的操作之一,是二叉树上进行其它运算的基础

  • 树的遍历分为四种
    1. 前序遍历 —— 访问根结点 —> 根的左子树 —> 根的右子树
    2. 中序遍历 —— 根的左子树 —> 访问根结点 —> 根的右子树
    3. 后序遍历 —— 根的左子树 —> 根的右子树 —> 访问根结点
    4. 层序遍历 —— 从上到下,从左到右,依次遍历

前序遍历

  • 图解:前序遍历就是 先打印根结点 —> 再到左结点 —> 最后右结点
  • 该图的前序遍历:ABDEHCFG

![[Pasted image 20231016150420.png]]

  • 记住一句话 —— 每一个结点都是 根结点 。只不过有些根结点比较特殊,它拥有父结点,根也有根。我们都是在根结点上进行操作

  • 算法

    • 递归实现,每遇见一个结点就打印
    • 先左后右,直到遇到 null 返回
void preOrder(BTNode root) {  
    if (root == null) {  
        return;  
    }  
    System.out.print(root.val + " ");  
    preOrder(root.left);  
    preOrder(root.right);  
}
关于递归的一些想法
  • 用个比喻来描绘递归:递归就像在一个公司里面,我是一个经理,老总下达任务给我,告诉了我一个 方向 ,我一个人肯定是干不完这么多东西的,但是我可以下发给我的下级,也告诉他们一个方向我不关心他们所做的过程,我只关心员工拿给我的成果。这个方向指向的就是我们最终的目的。
  • 下级肯定也有他的下级,就这样层层下达,总有一个最下级,等这个最下级的人完成任务,然后将任务成功传给他的上级,就这样层层上传,等成果传给我手上后,我发现方向没错,我把我的任务也做完,再传给老总,就这样完成了一个目的
  • 这就是我对递归的一些理解。上面的前序遍历,我告诉了我的员工应该走的方向,即 根 左 再右 ,没得打印了再反过头来递给我成果

中序遍历

  • 图解:中序遍历就是 先打印左子树 —> 再到根结点 —> 最后右子树

  • 该图的中序遍历:DBEHAFCG
    ![[Pasted image 20231016194334.png]]

  • 算法

    • 左边打印完,遇到null,才打印根结点
    • 先左后右,直到遇到 null 返回
void inOrder(BTNode root) {  
    if (root == null) {  
        return;  
    }  
    inOrder(root.left);  
    System.out.print(root.val + " ");  
    inOrder(root.right);  
}

后序遍历

  • 图解:后序遍历就是 先打印左子树 —> 再到右子树 —> 最后根结点

  • 该图的后序遍历:DHEBFGCA
    ![[Pasted image 20231016194900.png]]

  • 算法

    • 检测到根结点无左右孩子时,打印根结点
void postOrder(BTNode root) {  
    if (root == null) {  
        return;  
    }  
    postOrder(root.left);  
    postOrder(root.right);  
    System.out.print(root.val + " ");  
}

层序遍历(BFS广度优先搜索)

层序遍历:从上到下,从左到右,依次遍历
前中后序遍历其本质都是从上到下,层序的关键点是 从左到右

  • 思路
  • 队列 相结合,并加上一个辅助指针
    1. 结点进队列
    2. 当队列不为空时,出队,用辅助指针 cur 记录这个出队结点,再将这个出队结点的左右结点依次入队

图解:
![[4529bf559c6a2d84d550eebaee027c3b7ae25069e4ec91f27b29a4c6358d6662.gif]]

//层序遍历  
public void levelOrder(BTNode root) {  
    // 1.如果树为空,不打印  
    if (root == null) {  
        return;  
    } 
    // 2.与队列相结合。先将根结点入队  
    Deque<BTNode> deque = new LinkedList<>();  
    deque.offer(root);  
    // 3.当队列不为空时,进行出队操作  
    while (!deque.isEmpty()) {  
        // 4.使用辅助指针 cur 记录出队结点,并打印结点数值  
        BTNode cur = deque.poll();  
        System.out.print(cur.val + " ");  
        // 5.如果该结点有左子树,将左子树入队  
        if (cur.left != null) {  
            deque.offer(cur.left);  
        }  
          
        // 6.如果该结点有右子树,将右子树入队  
        if (cur.right != null) {  
            deque.offer(cur.right);  
        }  
    }  
}

![[Pasted image 20231017195017.png]]

![[Pasted image 20231017195023.png]]


二叉树的基本操作

获取树中结点的个数

方法一:遍历

思路

  • 前序遍历
  • 将计数器 nodeSize 定义到方法外部。如果放在方法内部,每次递归计数器都会重新赋值
// 获取树中节点的个数:遍历思路  
public static int nodeSize;  
public int size(BTNode root) {  
    if (root == null) {  
        return 0;  
    }  
    nodeSize++;  
    size(root.left);  
    size(root.right);  
    return nodeSize;  
}

方法二:化成子问题

思路

  • 整棵树的结点总数 = 左子树的结点个数 + 右子树的结点个数 + 1(根结点)
  • 如图:A 的左子树是 B,对 B 来说 B 本身也是一个根结点,B 的左子树是 D,不断细分,直到左边或者右边都没有结点
    ![[Pasted image 20231016214133.png]]

基于以上逻辑,代码如下:

// 获取节点的个数:子问题的思路  
public int size2(BTNode root) {  
    if (root == null) {  
        return 0;  
    }  
    return size2(root.left) + size2(root.right) + 1;  
}

获取叶结点个数

方法一:遍历

思路

  • 判断为叶结点,计数器才开始计数
// 获取叶子节点的个数:遍历思路  
public static int leafSize = 0;  
public int getLeafNodeCount1(BTNode root) {  
    // 空树没有结点,更别提叶子  
    if (root == null) {  
        return 0;  
    }  
    if(root.left == null && root.right == null) {  
        leafSize++;  
    }  
    getLeafNodeCount1(root.left);  
    getLeafNodeCount1(root.right);  
    return leafSize;  
}

方法二:化为子问题

思路

  • 整棵树的叶子结点 = 左子树叶子 + 右子树叶子
// 获取叶子节点的个数:子问题  
public int getLeafNodeCount2(BTNode root) {  
    if (root == null) {  
        return 0;  
    }  
    if (root.left == null && root.right == null) {  
        return 1;  
    }  
    return getLeafNodeCount2(root.left)   
            + getLeafNodeCount2(root.right);  
}

获取第 K 层 结点的个数

思路

  • 按照上面的 子问题思路 去解决这个问题,就非常好解决

  • 整棵树的第 K 层结点个数 = 左子树的第 K-1 层结点个数 + 右子树的第 K-1 层结点个数

  • 如图,假设求该图的第 1 层的结点个数,那肯定就是 1 个。

  • 现在求第 2 层结点个数,其实就是求第 2 层的根结点个数,相对 A 结点来说, B、C 是第 2 层,但相对 B、C 本身来说,他们就是第 1 层!

  • 所以,如何让计算机知道,这一层相对于它们本身来说是第 1 层,就是关键

  • 这个关键点就是所给的 KK 也是要进行递归的,在 A 的时候,K 为 2,到 B、C 这一层的时候,K-1 = 1 ,那就是第 1 层了,所以当 K == 1 时,就说明这层是要去求的层数结点
    ![[Pasted image 20231016215914.png]]

// 获取第K层节点的个数  
public int getKLevelNodeCount(BTNode root, int k) {  
    if (root == null) {  
        return 0;  
    }  
    // 当 K 等于1 时,说明这就是第一层  
    if (k == 1) {  
        return 1;  
    }  
    return getKLevelNodeCount(root.left,k-1)  
            + getKLevelNodeCount(root.right,k-1);  
}

求树的高度

思路

  • 还是化成 子问题思路,化成一棵一棵子树来看,子树也是一棵树,将大树分成一棵棵小树,也是递归的核心思想

  • 整棵树的高度 = max( 左子树高度,右子树高度) + 1

  • 图解
    ![[Pasted image 20231016220936.png]]

// 获取二叉树的高度  
// 时间复杂度:O(N)  
public int getHeight(BTNode root) {  
    if (root == null) {  
        return 0;  
    }  
    int leftHeight = getHeight(root.left);  
    int rightHeight = getHeight(root.right);  
    return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;  
}

检测值为 val 的元素是否存在

思路

  • 遍历树,找到 val 所在结点,返回这个结点
    • 空树返回 null
    • 找根,再往左子树找,随后再往右子树找,结点数据不是 val 的统统返回 null
// 检测值为value的元素是否存在  
public BTNode find(BTNode root, char val) {  
    if (root == null) {  
        return null;  
    }  
    if (root.val == val) {  
        return root;  
    }  
  
    BTNode left = find(root.left,val);  
    if (left != null) {  
        return left;  
    }  
  
    BTNode right = find(root.right,val);  
    if (right != null) {  
        return right;  
    }  
  
    return null;  
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行舟Yi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值