一.封装二叉树及插入操作
树:
树中每个元素都叫做节点,位于树顶部的节点叫作根节点,一个节点可以有祖先节点和子节点,根节点没有父节点。
二叉树:
字面意思:两个分叉的树。二叉树中的节点最多只能有两个子节点,上面的树其实就是二叉树
二叉搜索树(BST):
BinarySearchTree简称BST。二叉搜索树是二叉树的子集,不同的是,节点的左测节点的值比父节点的值大,节点的右侧节点的值比父节点小,上面那个图也是一个二叉搜索树。
BST的查找和插入的效率很高,类似于二分查找的思想,可以快速的定位元素
封装一个二叉搜索树:
// 树的结点
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
// BST
class BinarySearchTree {
constructor() {
this.root = null;
}
//insert(key):向树中插入一个新的键
insert(key) {
//1.根据key创建Node节点
const newNode = new Node(key);
//2.如果原来的树是一颗空树
if (this.root === null) {
this.root = newNode;
} else {
//递归插入
this.insertNode(this.root, newNode);
}
return this.root
}
// 插入的递归函数
insertNode(root, node) {
//判断新节点和当前节点的大小
if (node.key < root.key) {
if (root.left === null) {
root.left = node
} else {
// 插入节点比根节点小,就继续比较根节点的左节点
this.insertNode(root.left, node)
}
} else {
if (root.right === null) {
root.right = node
} else {
this.insertNode(root.right, node)
}
}
}
测试代码:
const bst = new BinarySearchTree();
//插入数据
bst.insert(4)
bst.insert(2)
bst.insert(3)
bst.insert(1)
bst.insert(6)
bst.insert(5)
bst.insert(7)
console.log(bst);
至此,就封装好了一个简单的二叉搜索树,经测试,没有问题
二.递归遍历
接下来就进行遍历操作,首先封装递归的写法:
- 先序遍历:根左右
先序遍历简单地说就是,传入一个节点,如果节点为空就直接返回,不为空,就递归遍历它的子节点,直到遍历完
//先序遍历
preOrderTraverse() {
// 传入根节点
// 简单地说就是,传入一个节点,如果节点为空就直接返回,不会空,就递归遍历它的子节点,直到遍历完
this.preOrderTraverseNode(this.root)
}
//先序遍历的递归函数
preOrderTraverseNode(node) {
// 递归终止的条件
if (node === null) return
console.log(node.key)
// 处理子节点
this.preOrderTraverseNode(node.left)
this.preOrderTraverseNode(node.right)
}
- 中序遍历:左根右,中序遍历的顺序就是升序的顺序
中序遍历,不同的是,先处理左节点,遍历完左节点,再处理根节点,右节点
//中序遍历
inOrderTraverse() {
//传入根节点
this.inOrderTraverseNode(this.root)
}
inOrderTraverseNode(node) {
if (node === null) return
//递归操作, 首先处理左节点,再处理根节点,再处理右节点
this.inOrderTraverseNode(node.left)
console.log(node.key)
this.inOrderTraverseNode(node.right)
}
- 后序遍历:左右根
后序遍历,先处理左节点,再处理右节点,最后处理根节点
//后序遍历
postOrderTraverse() {
this.postOrderTraverseNode(this.root)
}
postOrderTraverseNode(node) {
if (node === null) return
//递归操作, 首先处理左节点,再处理右节点,再处理根节点
this.postOrderTraverseNode(node.left)
this.postOrderTraverseNode(node.right)
console.log(node.key)
}
测试一下递归遍历:
const bst = new BinarySearchTree();
//插入数据
bst.insert(4)
bst.insert(2)
bst.insert(3)
bst.insert(1)
bst.insert(6)
bst.insert(5)
bst.insert(7)
//先序遍历
bst.preOrderTraverse() // 4 2 1 3 6 5 7
//中序遍历
bst.inOrderTraverse() // 1 2 3 4 5 6
//后序遍历
bst.postOrderTraverse() // 1 3 2 5 7 6 4
三.迭代法
搞懂了基本的遍历,我们不难发现,二叉树的遍历采用的是深度优先的遍历,
因此迭代法的思路也是:深度优先遍历,比如先序遍历,需要先遍历完左子树,再遍历右子树。
迭代法需要借助一个辅助栈(其实递归跟栈联系的很紧密),有一个遍历一颗二叉树,并且是先左子树,再右子树的通用模板,如下:
let Traversal = function(root) {
//辅助栈
const stack = [];
//循环遍历二叉树
while (root || stack.length){
//非空节点入栈,深度优先遍历左子树
while(root){
stack.push(root);
root = root.left;
}
// 出现空节点说明遍历到最深处,出栈操作,遍历右子树
root = stack.pop();
root = root.right;
}
return res;
};
结合图片发现这个遍历产生的整体压栈的顺序为:
- 4,2,1入栈
- 1出栈
- 2出栈
- 3入栈
- 3出栈
- 4出栈
- 6入栈
- 5入栈
- 5出栈
- 6出栈
- 7入栈
- 7出栈
上述入栈顺序排列:4、2、1、3、6、5、7 ,就是先序排列的顺序!
上述出栈顺序排列:1、2、3、4、5、6、7,就是中序排列的顺序!
具体的代码如下:
// 先序遍历(迭代法)
preTraverse(root) {
// 结果集
const res = []
// 辅助栈
const stack = []
// 循环遍历二叉树
while (root || stack.length) {
// 非空节点入栈,深度优先遍历左子树
while (root) {
// res 记录入栈顺序
res.push(root.key)
stack.push(root)
root = root.left
}
// 出现空节点说明遍历到最深处,出栈操作,遍历右子树
root = stack.pop()
root = root.right
}
// 入栈顺序就是先序遍历的顺序
return res
}
//中序遍历(迭代法)
inTraverse(root) {
// 结果集
const res = []
// 辅助栈
const stack = []
// 循环遍历二叉树
while (root || stack.length) {
// 非空节点入栈,深度优先遍历左子树
while (root) {
stack.push(root)
root = root.left
}
root = stack.pop()
// res 记录出栈顺序
res.push(root.key)
// 出现空节点说明遍历到最深处,出栈操作,遍历右子树
root = root.right
}
// 出栈顺序就是中序遍历的顺序
return res
}
测试代码:
const bst = new BinarySearchTree();
//插入数据
bst.insert(4)
bst.insert(2)
bst.insert(3)
bst.insert(1)
bst.insert(6)
bst.insert(5)
bst.insert(7)
//测试迭代遍历
// 先序遍历
bst.preTraverse() // 4 2 1 3 6 5 7
// 中序遍历
bst.inTraverse() // 1 2 3 4 5 6
后序遍历有点不太一样,但是思想是一样的,反着来,就可以了。传入根节点我们需要先遍历右子树,再遍历左子树。
入栈顺序为:
- 4入栈
- 6入栈
- 7入栈
- 5入栈
- 2入栈
- 3入栈
- 1入栈
将入栈顺序倒着排列,就是后序遍历的结果了:1、3、2、5、7、6、4
//后序遍历 (迭代法)
postTraverse(root) {
// 结果集
const res = []
// 辅助栈
const stack = []
// 循环遍历二叉树
while (root || stack.length) {
while (root) {
stack.push(root)
// 用unshift 在插入的时候直接插入到数组前面。当然也可以最后再reverse
res = res.unshift(root.key)
// 先遍历右子树
root = root.right
}
// 出现空节点说明遍历到最深处,出栈操作,遍历左子树
root = stack.pop()
root = root.left
}
return res
}
后序遍历测试代码:
const bst = new BinarySearchTree();
//插入数据
bst.insert(4)
bst.insert(2)
bst.insert(3)
bst.insert(1)
bst.insert(6)
bst.insert(5)
bst.insert(7)
// 后序遍历
console.log(bst.postTraverse(bst.root)); // [1, 3, 2, 5, 7, 6, 4]
四.题目实战
leetCode第144题:先序遍历
leetCode第94题:中序遍历
leetCode第145题:后序遍历
四.全篇完整代码:
// 树的结点
class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
class BinarySearchTree {
constructor() {
//初始化实例对象时是空树
this.root = null;
}
//insert(key):向树中插入一个新的键
insert(key) {
//1.根据key创建Node节点
const newNode = new Node(key);
//2.如果原来的树是一颗空树,当前节点赋给根节点
if (this.root === null) {
//创建根节点
this.root = newNode;
} else {
//递归插入
this.insertNode(this.root, newNode);
}
return this.root
}
// 插入的递归函数
insertNode(root, node) {
//判断新节点和当前节点的大小
if (node.key < root.key) {
if (root.left === null) {
root.left = node
} else {
// 插入节点比根节点小,就继续比较根节点的左节点
this.insertNode(root.left, node)
}
} else {
if (root.right === null) {
root.right = node
} else {
this.insertNode(root.right, node)
}
}
}
//先序遍历
preOrderTraverse() {
// 传入根节点
// 简单地说就是,传入一个节点,如果节点为空就直接返回,不会空,就递归遍历它的子节点,直到遍历完
this.preOrderTraverseNode(this.root)
}
//先序遍历的递归函数
preOrderTraverseNode(node) {
// 递归终止的条件
if (node === null) return
console.log(node.key)
// 处理子节点
this.preOrderTraverseNode(node.left)
this.preOrderTraverseNode(node.right)
}
//中序遍历
inOrderTraverse() {
//传入根节点
this.inOrderTraverseNode(this.root)
}
//中序遍历的递归函数
inOrderTraverseNode(node) {
if (node === null) return
//递归操作, 首先处理左节点,再处理根节点,再处理右节点
this.inOrderTraverseNode(node.left)
console.log(node.key)
this.inOrderTraverseNode(node.right)
}
//后序遍历
postOrderTraverse() {
this.postOrderTraverseNode(this.root)
}
//后序遍历的递归函数
postOrderTraverseNode(node) {
if (node === null) return
//递归操作, 首先处理左节点,再处理右节点,再处理根节点
this.postOrderTraverseNode(node.left)
this.postOrderTraverseNode(node.right)
console.log(node.key)
}
// 先序遍历(迭代法)
preTraverse(root) {
// 结果集
const res = []
// 辅助栈
const stack = []
// 循环遍历二叉树
while (root || stack.length) {
// 非空节点入栈,深度优先遍历左子树
while (root) {
// res 记录入栈顺序
res.push(root.key)
stack.push(root)
root = root.left
}
// 出现空节点说明遍历到最深处,出栈操作,遍历右子树
root = stack.pop()
root = root.right
}
// 入栈顺序就是先序遍历的顺序
return res
}
//中序遍历(迭代法)
inTraverse(root) {
// 结果集
const res = []
// 辅助栈
const stack = []
// 循环遍历二叉树
while (root || stack.length) {
// 非空节点入栈,深度优先遍历左子树
while (root) {
stack.push(root)
root = root.left
}
root = stack.pop()
// res 记录出栈顺序
res.push(root.key)
// 出现空节点说明遍历到最深处,出栈操作,遍历右子树
root = root.right
}
// 出栈顺序就是中序遍历的顺序
return res
}
//后序遍历 (迭代法)
postTraverse(root) {
// 结果集
const res = []
// 辅助栈
const stack = []
// 循环遍历二叉树
while (root || stack.length) {
while (root) {
stack.push(root)
// 用unshift 在插入的时候直接插入到数组前面。当然也可以最后再reverse
res.unshift(root.key)
// 先遍历右子树
root = root.right
}
// 出现空节点说明遍历到最深处,出栈操作,遍历左子树
root = stack.pop()
root = root.left
}
return res
}
}
const bst = new BinarySearchTree();
//插入数据
bst.insert(4)
bst.insert(2)
bst.insert(3)
bst.insert(1)
bst.insert(6)
bst.insert(5)
bst.insert(7)
console.log(bst);
// 测试递归遍历
// 先序遍历
// bst.preOrderTraverse() // 4 2 1 3 6 5 7
// 中序遍历
// bst.inOrderTraverse() // 1 2 3 4 5 6
// 后序遍历
// bst.postOrderTraverse() // 1 3 2 5 7 6 4
// 测试迭代遍历
// 先序遍历
console.log(bst.preTraverse(bst.root)); // [4, 2, 1, 3, 6, 5, 7]
// 中序遍历
console.log(bst.inTraverse(bst.root)); // [1, 2, 3, 4, 5, 6, 7]
// 后序遍历
console.log(bst.postTraverse(bst.root)); // [1, 3, 2, 5, 7, 6, 4]