春招来了,辞了职在家里准备再找份实习工作。相信大家,尤其是大三、大四的同学都经常在招聘要求上看到这样一条要求:熟悉常见的数据结构与算法。常见的数据结构通常有:链表、二叉树,如果要求再高点,可能会让你实现红黑树、AVL树这种高级的数据结构。由此可见,数据结构与算法还是比较重要的,最近也是在复习这方面的知识。本篇为复习过程中遇到过的总结,同时也给各位跟我一样准备面试的同学一份参考。另外,由于篇幅有限,本篇的重点在于二叉树的常见算法以及实现。
常见的二叉树实现代码
之前写过相关的文章,是关于如何创建及遍历二叉树的,这里不再赘述。提供链接给各位感兴趣的小伙伴,点此
翻转二叉树
对于一棵二叉树,翻转它的左右子树,如下图所示:
下面来分析具体的实现思路:- 对于根结点为空的情况 这种情况需要排除,因为null不是一个对象,不可能存在左右子树并且可以翻转的情况
- 对于一棵只有一个根结点的二叉树 emmm,这种情况也可以翻转,因为此时根结点左右子树为null,交换左右子树其实也就是在交换两个null,理论上是翻转了,但实际上我们看到的和没有翻转之前的结果是一样的
- 对于一棵具有两个或两个以上结点的二叉树,此时二叉树可以表示为如下的图像:
分析过程
其实这样我们还是不知道二叉树是如何翻转的,我们可以用第一张图的二叉树为例子,看一下翻转的具体过程。
- 首先我们需要对根结点进行判空处理,在根结点不为空的情况下存在左右子树(即使左右子树为空),然后交换左右子树;
示例代码
根据上面的推理过程我们可以得出如下的代码:
function reverseTree(root){ if( root !== null){ [root.left, root.right] = [root.right, root.left] reverseTree(root.left) reverseTree(root.right) }}复制代码
虽然推理过程比较复杂(也可能是写的比较啰嗦。。),但是仔细观察代码,这和遍历的代码似乎也没多大差别,只是把输出结点变为了交换结点。
判断二叉树是否完全对称
一棵左右完全对称的二叉树是这样的:
那到底如何判断呢?- 根结点为空时,此时为一棵空二叉树,满足对称条件(-_-||)
- 只有一个根结点时,左右子树都为null,满足左右对称条件
- 只有两个结点时,此时左右子树必定有一个为空,不可能存在对称的情况
- 结点数在三个及三个以上时,二叉树有对称的可能。
按照我们正常的思维,看对称与否,首先看左边,然后看右边,最后比较左右是否相等。同时我们注意到,在二叉树深度比较大的时候,我们光是比较左右是不够的。可以观察到,我们比较完左右以后还需要比较左的左和右的右,比较左的右和右的左
分析过程
这么看是比较绕,接下来我们来看图分析:
- 先比较根结点左右孩子
- 将左子树根结点的左孩子与右子树根结点的右孩子进行比较
- 将左子树根结点的右孩子与右子树根结点的左孩子进行比较
- 重复以上过程...
示例代码
function isSymmetrical(pRoot){ // write code here if(!pRoot){ return true } return funC(pRoot.left, pRoot.right)} function funC(left, right){ if(!left){ return right === null } if(!right){ return false } if(left.val !== right.val){ return false } return funC(left.right, right.left) && funC(left.left, right.right)}复制代码
求二叉树的深度
分析过程
- 只有一个根结点时,二叉树深度为1
- 只有左子树时,二叉树深度为左子树深度加1
- 只有右子树时,二叉树深度为右子树深度加1
- 同时存在左右子树时,二叉树深度为左右子树中深度最大者加1
示例代码
function deep(root){ if(!root){ return 0 } let left = deep(root.left) let right = deep(root.right) return left > right ? left + 1 : right + 1}复制代码
求二叉树的宽度
二叉树的宽度是啥?我把它理解为具有最多结点数的层中包含的结点数,比如下图所示的二叉树,其实它的宽度就是为4:
分析过程
根据上图,我们如何算出二叉树的宽度呢?其实有个很简单的思路:
- 算出第一层的结点数,保存
- 算出第二层的结点数,保存一二层中较大的结点数
- 重复以上过程
示例代码
根据分析过程,我们可以利用队列这种数据结构来实现这个算法,代码如下:
function width(root){ if(!root){ return 0 } let queue = [root], max = 1, deep = 1 while(queue.length){ while(deep--){ let temp = queue.shift() if(temp.left){ queue.push(temp.left) } if(temp.right){ queue.push(temp.right) } } deep = queue.length max = max > deep ? max : deep } return max}复制代码
重建二叉树
常见的遍历
-
前序遍历: 前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。
-
中序遍历: 中序遍历首先访问左子树然后遍历根节点,最后遍历右子树。
-
后序遍历: 后序遍历首先遍历左子树,然后遍历右子树,最后访问根结点
题目描述
根据前序遍历产生的序列和中序遍历产生的序列生成一颗二叉树
思路分析
假如有这么一棵二叉树:
可以看出它 前序遍历序列为: 8 6 5 7 10 9 11, 中序遍历序列为: 5 6 7 8 9 10 11 其中有个很明显的特征,根结点的值为 前序遍历序列的第一个值,而且我们在 中序遍历序列中很容易看出,根结点左右两边的结点分别为构成 左子树和 右子树的结点,所以我们可以得到一种解决问题的思路:- 获取前序遍历的第一个值,构建根结点
- 生成左子树的前序遍历序列和中序遍历序列
- 生成右子树的前序遍历序列和中序遍历序列
- 重复以上过程...
示例代码
function reConstructBinaryTree(pre, vin){ if(!pre || !vin || !pre.length || !vin.length){ return null } let root = new TreeNode(pre[0]), tIndex = vin.indexOf(pre[0]), leftIn = [],leftPre = [],rightIn = [],rightPre = [] for(let i = 0; i < tIndex; i++){ leftIn.push(vin[i]) leftPre.push(pre[i+1]) } for(let i = tIndex+1; i < pre.length; i++){ rightIn.push(vin[i]) rightPre.push(pre[i]) } //递归 root.left = reConstructBinaryTree(leftPre, leftIn) root.right = reConstructBinaryTree(rightPre, rightIn) return root}复制代码
以上思路、代码有错漏请在评论区指出!
总结
代码部分来自,相应的题目也都可以在上面找到。不过在这期间,我也是找到了份实习工作,年后就要去搬砖了。既然找到了,春招就不参与了(春招难度比秋招难太多了)希望看这篇文章的同学们也能找到份合适的工作。