坐下,这些都是二叉树的基本操作!

Posted SegmentFault

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了坐下,这些都是二叉树的基本操作!相关的知识,希望对你有一定的参考价值。

本篇为复习过程中遇到过的总结,同时也给准备面试的同学一份参考。另外,由于篇幅有限,本篇的重点在于二叉树的常见算法以及实现。

常见的二叉树实现代码

之前写过相关的文章,是关于如何创建及遍历二叉树的,这里不再赘述。提供链接给各位感兴趣的小伙伴,点此跳转。

翻转二叉树

对于一棵二叉树,翻转它的左右子树,如下图所示:

下面来分析具体的实现思路:

对于根结点为空的情况

这种情况需要排除,因为null不是一个对象,不可能存在左右子树并且可以翻转的情况。

对于一棵只有一个根结点的二叉树

emmm,这种情况也可以翻转,因为此时根结点左右子树为null,交换左右子树其实也就是在交换两个null,理论上是翻转了,但实际上我们看到的和没有翻转之前的结果是一样的。

对于一棵具有两个或两个以上结点的二叉树

此时二叉树可以表示为如下的图像:

坐下,这些都是二叉树的基本操作!

可以看出,无论是只有左子树还是只有右子树都可以进行翻转。这句话等价于,为空的子树可以和不为空的子树进行交换,也就是不对为空的子树进行特殊处理

分析过程

其实这样我们还是不知道二叉树是如何翻转的,我们可以用第一张图的二叉树为例子,看一下翻转的具体过程。

1、首先我们需要对根结点进行判空处理,在根结点不为空的情况下存在左右子树(即使左右子树为空),然后交换左右子树:

坐下,这些都是二叉树的基本操作!

2、把根结点的左子树当成左子树的根结点,对当前根结点进行判空处理,不为空时交换左右子树:

坐下,这些都是二叉树的基本操作!

坐下,这些都是二叉树的基本操作!

3、把根结的右子树当成右子树的根结点,对当前根结点进行判空处理,不为空时交换左右子树:

坐下,这些都是二叉树的基本操作!

4:重复步骤2、3,最后二叉树变为原来的镜像结构,结果可以参考文章第一张示意图。

示例代码

根据上面的推理过程我们可以得出如下的代码:

 
   
   
 
  1. function reverseTree(root){

  2.    if( root !== null){

  3.        [root.left, root.right] = [root.right, root.left]

  4.        reverseTree(root.left)

  5.        reverseTree(root.right)

  6.    }

  7. }

虽然推理过程比较复杂(也可能是写的比较啰嗦。。),但是仔细观察代码,这和遍历的代码似乎也没多大差别,只是把输出结点变为了交换结点。

判断二叉树是否完全对称

一棵左右完全对称的二叉树是这样的:

坐下,这些都是二叉树的基本操作!

那到底如何判断呢?

  • 根结点为空时,此时为一棵空二叉树,满足对称条件(-_-||)

  • 只有一个根结点时,左右子树都为null,满足左右对称条件

  • 只有两个结点时,此时左右子树必定有一个为空,不可能存在对称的情况

  • 结点数在三个及三个以上时,二叉树有对称的可能。

按照我们正常的思维,看对称与否,首先看左边,然后看右边,最后比较左右是否相等。同时我们注意到,在二叉树深度比较大的时候,我们光是比较左右是不够的。可以观察到,我们比较完左右以后还需要比较左的左和右的右,比较左的右和右的左。

分析过程

这么看是比较绕,接下来我们来看图分析:

1、先比较根结点左右孩子:

坐下,这些都是二叉树的基本操作!

2、将左子树根结点的左孩子与右子树根结点的右孩子进行比较:

坐下,这些都是二叉树的基本操作!

3、将左子树根结点的右孩子与右子树根结点的左孩子进行比较:

坐下,这些都是二叉树的基本操作!

4、重复以上过程...

示例代码
 
   
   
 
  1. function isSymmetrical(pRoot)

  2. {

  3.    // write code here

  4.    if(!pRoot){

  5.        return true

  6.    }

  7.    return funC(pRoot.left, pRoot.right)

  8. }

  9. function funC(left, right){

  10.    if(!left){

  11.        return right === null

  12.    }

  13.    if(!right){

  14.        return false

  15.    }

  16.    if(left.val !== right.val){

  17.        return false

  18.    }

  19.    return funC(left.right, right.left) && funC(left.left, right.right)

  20. }

求二叉树的深度

分析过程
  • 只有一个根结点时,二叉树深度为 1

  • 只有左子树时,二叉树深度为左子树深度加 1

  • 只有右子树时,二叉树深度为右子树深度加 1

  • 同时存在左右子树时,二叉树深度为左右子树中深度最大者加 1

示例代码
 
   
   
 
  1. function deep(root){

  2.    if(!root){

  3.        return 0

  4.    }

  5.    let left = deep(root.left)

  6.    let right = deep(root.right)

  7.    return left > right ? left + 1 : right + 1

  8. }

求二叉树的宽度

二叉树的宽度是啥?我把它理解为具有最多结点数的层中包含的结点数,比如下图所示的二叉树,其实它的宽度就是为 4:

坐下,这些都是二叉树的基本操作!

分析过程

根据上图,我们如何算出二叉树的宽度呢?其实有个很简单的思路:

  1. 算出第一层的结点数,保存

  2. 算出第二层的结点数,保存一二层中较大的结点数

  3. 重复以上过程

示例代码

根据分析过程,我们可以利用队列这种数据结构来实现这个算法,代码如下:

 
   
   
 
  1. function width(root){

  2.    if(!root){

  3.        return 0

  4.    }

  5.    let queue = [root], max = 1, deep = 1

  6.    while(queue.length){

  7.        while(deep--){

  8.            let temp = queue.shift()

  9.            if(temp.left){

  10.                queue.push(temp.left)

  11.            }

  12.            if(temp.right){

  13.                queue.push(temp.right)

  14.            }

  15.        }

  16.        deep = queue.length

  17.        max = max > deep ? max : deep

  18.    }

  19.    return max

  20. }

重建二叉树

常见的遍历

前序遍历

前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。

中序遍历

中序遍历首先访问左子树然后遍历根节点,最后遍历右子树。

后序遍历

后序遍历首先遍历左子树,然后遍历右子树,最后访问根结点。

题目描述

根据前序遍历产生的序列和中序遍历产生的序列生成一颗二叉树。

思路分析

假如有这么一棵二叉树:

可以看出它前序遍历序列为:8 6 5 7 10 9 11,中序遍历序列为:5 6 7 8 9 10 11。

其中有个很明显的特征,根结点的值为前序遍历序列的第一个值,而且我们在中序遍历序列中很容易看出,根结点左右两边的结点分别为构成左子树和右子树的结点,所以我们可以得到一种解决问题的思路:

  1. 获取前序遍历的第一个值,构建根结点。

  2. 生成左子树的前序遍历序列和中序遍历序列。

  3. 生成右子树的前序遍历序列和中序遍历序列。

  4. 重复以上过程...

示例代码
 
   
   
 
  1. function reConstructBinaryTree(pre, vin)

  2. {

  3.    if(!pre || !vin || !pre.length || !vin.length){

  4.        return null

  5.    }

  6.    let root = new TreeNode(pre[0]),

  7.        tIndex = vin.indexOf(pre[0]),

  8.        leftIn = [],leftPre = [],rightIn = [],rightPre = []

  9.    for(let i = 0; i < tIndex; i++){

  10.        leftIn.push(vin[i])

  11.        leftPre.push(pre[i+1])

  12.    }

  13.    for(let i = tIndex+1; i < pre.length; i++){

  14.        rightIn.push(vin[i])

  15.        rightPre.push(pre[i])

  16.    }

  17.    //递归

  18.    root.left = reConstructBinaryTree(leftPre, leftIn)

  19.    root.right = reConstructBinaryTree(rightPre, rightIn)

  20.    return root

  21. }

以上思路、代码有错漏请在评论区指出!

总结

代码部分来自牛客网--剑指offer(https://www.nowcoder.com/ta/coding-interviews),相应的题目也都可以在上面找到。


以上是关于坐下,这些都是二叉树的基本操作!的主要内容,如果未能解决你的问题,请参考以下文章

二叉树遍历-力扣题解

C++面试笔记--树

考研计算机 | 二叉树

本周小结!(二叉树系列四)

数据结构:二叉树

程序员面试之必考题:平衡二叉树的基本概念