一、树
树的概念
树是计算机中常用到的一种数据结构,是一种非线性的数据结构。它是由n(n>=0)个有限节点组成一个具有层次关系的集合,以分层的方式存储数据。比如文件系统中的文件;树还被用来存储有序列表。
如图所示:F为整个树的根节点,B,G可以看作两个子树的根节点
树的特点
- 每个节点有零个或多个子节点
- 没有父节点的节点称为根节点
- 每一个非根节点有且只有一个父节点
- 除了根节点外,每个子节点可以分为多个不相交的子树。
专业名词
- 节点的度:一个节点含有的子树的个数,叫做该节点的度。
- 叶节点和终端节点:度为零的节点。
- 双亲节点或父节点:如图,B为A的父节点。
- 孩子节点或子节点:如图,A为B的子节点。
- 兄弟节点:拥有相同父节点的节点称为兄弟节点。A和D
- 树的度:一棵树中最大的节点的度称为树的度。
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。
- 树的高度或深度:树中节点的最大层次,如图,高度为4。
- 祖先:从跟到该节点所经分支上的所有节点。F是所有节点的祖先。
二、二叉树
二叉树是一种特殊的树,表现在它的子节点个数不超过两个。且二叉树的子树有左右之分,其次序不能任意颠倒。(上图所示就是一个二叉树)
二叉树的基本形态
二叉树具有诸多优点。相对于链表来说,二叉树在进行查找时速度非常快,而相对于数组来说,为二叉树添加或删除元素也非常快。
三、树的遍历
数的遍历分为三种:前序遍历、中序遍历、后续遍历
前序遍历
首先访问根节点,然后遍历左子树,最后遍历右子树
递归解法
按照访问根节点——左子树——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质 时间复杂度O(n),n为节点个树,空间复杂度O(n),即递归的空间开销(树的高度)
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root,res = []) {
if(root == null) return []
res.push(root.val)
preorderTraversal(root.left,res)
preorderTraversal(root.right,res)
return res
}
迭代算法
利用栈的先进后出的特性遍历;先创建一个栈,将根节点入栈,再将根节点出栈,加入到结果集当中,再将右子节点入栈,再将左子节点入栈,空节点不入栈;再判断栈顶是否为空,为空则出栈,加入到结果集当中。
时间复杂度O(n),n为节点个树,空间复杂度O(n)
var preorderTraversal = function(root) {
let res = [] //结果集, stack = [] //栈
if(root == null) return []
stack.push(root)
while(stack.length){
const node = stack.pop() //出栈
res.push(node.val) //将根节点加入到结果集
if(node.right) {
stack.push(node.right) //右子节点入栈
}
if(node.left) {
stack.push(node.left)//左子节点入栈
}
}
return res
};
中序遍历
首先遍历左子树,然后访问根节点,然后遍历右子树
递归解法
思路与前序遍历一样,顺序是左——中——右
var inorderTraversal = function(root,res = []) {
if(root == null) return []
inorderTraversal(root.left,res)
res.push(root.val)
inorderTraversal(root.right,res)
return res
};
迭代算法
分别定义一个栈和一个数组,先将左子树压入到栈中,直到左子树为null,然后开始弹栈,将值push到数组中,再反过来将右子树压入到栈中, 再开始弹栈,将结果push到数组中。
var inorderTraversal = function(root) {
let res = [],stack = []
while(stack.length || root){
if(root){
stack.push(root)
root = root.left
}else{
const node = stack.pop()
res.push(node.val)
root = node.right
}
}
return res
};
后序遍历
后序遍历是先遍历左子树,然后遍历右子树,最后访问树的根节点。
递归解法
var postorderTraversal = function(root,res = []) {
if(root == null) return [] //先判断是否为空,千万不能忘
postorderTraversal(root.left,res)
postorderTraversal(root.right,res)
res.push(root.val)
return res
};
迭代算法
本质和前序遍历相同,正常前序是中-左-右,后序遍历 = reverse(中-右-左)
var postorderTraversal = function(root) {
let res = [],stack = []
if(root == null) return []
stack.push(root) //根节点入栈
while(stack.length){
const node = stack.pop() //弹出根节点
res.push(node.val)
if(node.left){ //左节点入栈
stack.push(node.left)
}
if(node.right){//右节点入栈
stack.push(node.right)
}
}
return res.reverse()
};
当删除树中的节点时,删除过程将按照后序遍历的顺序进行。 也就是说,当删除一个节点时,将首先删除它的左节点和它的右边的节点,然后再删除节点本身。
中序常用来在二叉搜索数中得到递增的有序序列;
后序可用于数学中的后缀表示法,结合栈处理表达式,每遇到一个操作符,就可以从栈中弹出栈顶的两个元素,计算并将结果返回到栈中;
层序遍历
层序遍历介绍
层序遍历就是逐层遍历树结构。
**广度优先搜索(BFS)**是一种广泛运用在树或图这类数据结构中,遍历或搜索的算法。 该算法从一个根节点开始,首先访问节点本身。 然后遍历它的相邻节点,其次遍历它的二级邻节点、三级邻节点,以此类推。
当我们在树中进行广度优先搜索时,我们访问的节点的顺序是按照层序遍历顺序的。
二叉树的层序遍历
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而是用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。 而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。
var levelOrder = function(root) {
let res = []
if(!root) return []
let queue = [root] //声明一个队列
while(queue.length){
let length = queue.length //记录当前层级节点数,一定要用固定的length,因为queue.length是不断变化的
let arr = [] //用来接收每一层的节点
for(let i=0;i<length;i++){ //同一层的所有节点
const node = queue.shift() //出列
arr.push(node.val) //放到新数组
// 存放当前层下一层的节点
if(node.left) queue.push(node.left)
if(node.right) queue.push(node.right)
}
res.push(arr)
}
return res
};