再也不用怕面试问二叉树了
二叉树
二叉树是一种非常重要的数据结构。在算法题中经常会使用到,在面试中的占比也是非常大的。
先来说说树的定义。
树是由n(n>=1)个有限节点组成的一个由层级关系的集合。有以下特点:
- 每个节点有零个或多个子节点;
- 没有父节点的节点称为根节点;
- 每个非根节点有且只有一个父节点;
- 除根节点外,每个子节点可以分为多个不相交的子树。
简单来说,二叉树的就是每个节点最多有两个子节点的树。
在二叉树中,又有两种特殊形态:满二叉树和完全二叉树。
满二叉树简单的来说就是,除最后一层无子节点外,所有节点都有两个子节点的树。
完全二叉树简单的说就是,深度为h、(1~h-1)层的节点数都达最大个数且第h层所有的节点都连续集中在树的左边的树。
二叉搜索树
二叉搜索树的定义为二叉搜索树中的每个节点的值都比它左子树上的大,比它右子树上的小。
这种存储方式非常适合数据搜索,这是一种类似于折半查找的搜索方式,时间复杂度为O(logN)。
对二叉树有插入和删除节点操作。
插入节点比较简单,对比当前节点和插入节点的值,再从左或从右向下递归即可。
删除节点分为三种情况。
- 删除的节点没有子节点,也就是叶子节点;
- 删除的节点只有左子树或右子树;
- 删除的节点既有左子树又有右子树。
对于第一种情况,直接删除即可。第二种情况也相对简单,删除节点后直接用子树替换该节点。
主要是第三种情况比较复杂,这时候又出现两个概念,叫前驱节点和后继节点。
中序遍历中,某个节点之前的节点叫做前驱节点,之后的叫做后继节点。
在删除节点时,如果该节点既有左子树又有右子树,使用该节点的最小后继节点代替该节点即可。
然后讲树的遍历。树的遍历分为深度优先和广度优先两种,其中深度优先又分为先序遍历、中序遍历、后序遍历,广度优先主要是层次遍历。
常问的就是三种遍历的递归遍历和非递归遍历。先简单的说三种遍历方式的遍历顺序:
先序遍历:根、左、右;
中序遍历:左、根、右;
后续遍历:左、右、根。
二叉树的遍历
先序遍历的结果为:ABDECF。中序遍历的结果为:DBEAFC。后序遍历的结果为:DEBFCA。
层次遍历的结果为:ABCDEF。
递归遍历很简单,代码实现如下:
// 先序遍历
preTraversal() {
this._pre(this.root)
}
_pre(node) {
if (node) {
console.log(node.value)
this._pre(node.left)
this._pre(node.right)
}
}
// 中序遍历
midTraversal() {
this._mid(this.root)
}
_mid(node) {
if (node) {
this._mid(node.left)
console.log(node.value)
this._mid(node.right)
}
}
// 后序遍历
backTraversal() {
this._back(this.root)
}
_back(node) {
if (node) {
this._back(node.left)
this._back(node.right)
console.log(node.value)
}
}
非递归遍历需要使用到栈这种数据结构,每出栈一个节点就对其记录。
非递归先序遍历思路如下。先序遍历先访问左子树,而栈遵循的是先进后出的原则。所以顺序应该先入栈右节点后入栈左节点。大致顺序为,将根节点入栈,访问该根节点是否有子节点,如果有两个子节点,先入栈右节点,再入栈子节点,此时左节点在栈顶。访问该节点是否有子节点,重复上述操作(网上偷个图)。
先序非递归
非递归中序遍历思路。先将根节点入栈,然后一直访问左节点并入栈,直到为空。然后每出栈一个节点后,都对其访问,如果存在右节点则将其入栈,再向左访问子节点,并将其一一入栈,重复上述操作。
非递归后序遍历思路。将根节点入栈,并且一直向左访问子节点,一一入栈,直到为空。然后在出栈前对节点进行访问,如果有右节点,将其入栈,再一直向左访问子节点,重复上述操作。
中序和后序的区别就在于在根节点出栈前后访问入栈子节点。
前边说二叉树的时间复杂度为O(logN),这是指一般情况下,或者说是平均复杂度。**在极端情况下,二叉树可能会退化成链表,**链表查找的事件复杂度为O(logN)明显性能差了很多,所以为了解决这个问题,出现了平衡二叉树。
平衡二叉树
AVL是自平衡二叉搜索树,在AVL中任何节点的两个子树高度差最大为1。当增加或删除之后导致树不在平衡时,需要通过一次或多次旋转再平衡这个树。
简单的讲,左旋就是将自己变为右孩子的左孩子;右旋就是将自己变为左孩子的右孩子,两个互为逆操作。
图片的旋转
图左,将Q变为P的右节点,之后再将P原来的右节点变为Q的左节点。
图右,将P变为Q的左节点,之后在将Q原来的左节点变为P的右节点。
这样,在旋转之后中序遍历的结果不变。
前缀树
前缀树是一种有序树,用于保存关联数组,其中的键通常为字符串。
前缀树
可以用于找字符串的最长公共前缀。
- END -
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 安装Go运行环境
- Python 语法问题-module 'pip' has no attribute 'pep425tags',告诉你如何正确查看pip支持,32位、64位查看pip支持万能方法
- 使用matplotlib绘制3D图表
- 微服务的用户认证与授权杂谈(下)
- Python 库安装问题-用pip安装pyHook3报错,原因及解决办法
- 微服务的用户认证与授权杂谈(上)
- Python 技术篇-win32、amd64结尾的whl库该选哪个,如何查看python平台支持
- Python 基础篇-pip卸载python库方法,pip命令大全
- Python 技术篇-pip安装的python库缓存位置查看方法,如何查看python库源码
- Redis持久化 - RDB和AOF
- Python 技术篇-pip只下载python库不安装方法,pip命令大全
- Python 技术篇-将项目打包成whl文件,whl包的制作方法
- PowerBI 超级粘性用户计算 - 原理与实现
- Chrome 技术篇-常用web调试手法:清除缓存并硬性重新加载
- 数据库之索引模块