题目: 【LC102】层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
关键思路: BFS实现,采用队列即可,每次把当前层的所有节点放到队列中,按层遍历即可。
var levelOrder = function (root) {
let res = [];
if (!root) {
return res;
}
const queue = [root]; //初始队列
while (queue.length) {
let len = queue.length; //当前层节点的数量
const tempArr = []; //新的层数组
for (let i = 0; i < len; i++) {
const node = queue.shift();
tempArr.push(node.val);
if (node.left) q.push(node.left); //检查左节点,存在左节点就继续加入队列
if (node.right) q.push(node.right); //检查左右节点,存在右节点就继续加入队列
}
res.push(tempArr) //推入当前层的数组
}
return res;
}
知识拓展:
(1)JS实现一棵BST
二叉树的一个节点Node由val,left,right组成,val保存本节点的值,left指向左节点,right指向右节点。
二叉树有一个特殊性:相对本节点较小的值保存在左节点,相对本节点较大的值保存在右节点
节点数据
function Node(val,left,right){
this.val = val;
this.left = left;
this.right = right;
}
定义BST
function BST(){
this.root = null;
this.insert = insert;
this.find = find;
this.remove = remove;
}
定义插入方法
function insert(data) {
const node = new Node(data, null, null);
if(!this.root) {
this.root = node;
return;
}
let curNode = this.root;
while(true) {
if(curNode.val > data) { // 往左侧插入
if(!curNode.left) {
curNode.left = node;
break;
}
curNode = curNode.left;// 继续往左找
} else {
if(!curNode.right) {
curNode.right = node;
break;
}
curNode = curNode.right;
}
}
}
定义二叉树查询方法
function find(data) {
let curNode = this.root;
while(true) {
if(curNode.val === data) {
return curNode;
}
curNode = curNode.val > data ? curNode.left : curNode.right;
if(!curNode) { //找不到的情况
return null;
}
}
}
定义二叉树删除:
二叉树删除节点有两个子节点的情况,比较复杂,重点来说下:
最难的操作就是下面四点,只要找到了下面四个节点,删除操作就游刃而解了
1、找到要删除的节点
2、找到要删除节点的父节点
3、找到要删除节点的前驱或后继节点(如下以找后继为例)
4、找到要删除的节点的前驱或后继节点的父节点(如下以找后继的父节点为例)
其中,1和2,只需要对树进行遍历,定义一个delparent来每次存储遍历到得节点的前一个节点,这样便能在找到要删除节点的同时,找到要删除节点的父节点。
这里找后继相对比较简单, 因为是找后继,所以要从删除节点的右孩子开始寻找,找出以右孩子为根节点的树中最小的那个节点 (同理找前驱则是以左孩子为根节点的树中最大的那个节点), 由于是找最小,所以一直遍历.left,就可以找出来了。
var backsuccussor = delNode.right;
var backsuccussorParent = delNode.right;
var backsuccussorRight = backsuccussor;
//寻找后继节点 & 后继节点父节点
while (backsuccussor.left != null) {
backsuccussorParent = backsuccussor;
backsuccussor = backsuccussor.left;
}
当然,前驱、后继都是按中序遍历。后继节点父节点(因为是寻找后继节点,所以后继节点一定为其父节点的左孩子,而后继节点如果有孩子就一定是有孩子)
11是10的后继节点(右树的最左侧)
将11的父节点的left,指向11的right。
把10节点删除,重新替换为11节点(其后继节点)
11节点的left指向10节点的left 11节点的right指向10节点的right
最后一步,把原来10节点的父节点(8节点)的left或者right指向11节点
整体回顾下有两个子节点的二叉树删除思路:
整体来看下删除方法:
function remove(data) {
let cur = this.root;
let delNode = null;
let delParent = null;
let isLeft = true; // delNode 在 delParent 的左支还是右支;
// 找出删除节点及其父节点
while (cur && cur.val !== data) {
delParent = cur;
if (cur.val > data) {
isLeft = true;
cur = cur.left;
} else {
isLeft = false;
cur = cur.right;
}
}
let isRoot = this.root == cur;
if (cur.val !== data) {
return false; //没找到当前值的节点,删除失败
}
delNode = cur;
// 缺少找不到的情况处理方案
// 1)删除节点没有子节点的情况
if (!delNode.left && !delNode.right) {
if (isRoot) {
this.root = null
} else {
if (isLeft) {
delParent.left = null
} else {
delParent.right = null
}
}
}
// 2)删除节点只有一个子节点
if (delNode.left ^ delNode.right) {
if (isLeft) {
delParent.left = delNode.left || delNode.right || null;
} else {
delParent.right = delNode.left || delNode.right || null;
}
}
// 3)删除节点有两个子节点
if (delNode.left && delNode.right) {
// 找其后继节点
let backsuccussor = delNode.right;
let backsuccussorParent = delNode.right;
while (backsuccussor.left) {
backsuccussorParent = backsuccussor;
backsuccussor = backsuccussor.left
};
//后继节点,其父一定有left,本身一定无左支。
backsuccussorParent.left = backsuccussor.right;
backsuccussor.left = delNode.left;
backsuccussor.right = delNode.right;
if (isRoot) {
this.root = backsuccussor;
} else if (isLeft) {
delParent.left = backsuccussor;
} else {
delParent.right = backsuccussor
}
}
return true;
}
二叉树的缺点:如果节点要频繁删除的话,不适合使用二叉树,特别是被删除的节点有两个子节点的;
二叉树的优点:查找无规律的数字或者字母的数据结构适合二叉树的使用场景
(2)二叉树的遍历
二叉树的先序,中序,后序遍历:(无非是什么时机输出root)
先序遍历表示先访问根节点,然后访问左节点,最后访问右节点。
中序遍历表示先访问左节点,然后访问根节点,最后访问右节点。
后序遍历表示先访问左节点,然后访问右节点,最后访问根节点。
//递归实现
function traversal(root) {
if (root) {
console.log(root.val) // 先序
traversal(root.left)
console.log(root.val) // 中序
traversal(root.right)
console.log(root.val) // 后序
}
}
非递归实现
非递归实现使用了栈的结构,通过栈的先进后出模拟递归实现。 以下是先序遍历代码实现。
//非递归实现,先序遍历
function preOrder(root) {
if (root) {
const stack = [root];
while (stack.length) {
const node = stack.pop();
console.log(node.val);
if (node.right) { //注意,先入右,再入左,出栈就是中左右的顺序了
stack.push(node.right)
} else if (node.left) {
stack.push(node.left)
}
}
}
}
中序遍历非递归实现思路:
注意,理解上,第一步可以先把树补全,即把叶子节点的左右都指向null
中序遍历是先左再根最后右
- 首先应该先把最左边节点遍历到底依次 push 进栈
- 当左边没有节点时,就打印栈顶元素,然后寻找右节点
- 对于最左边的叶节点来说,可以把它看成是两个 null 节点的父节点
- 左边打印不出东西就把父节点拿出来打印,然后再看右节点
function inOrder(root) {
if (root) {
let stack = [root];
while (stack.length) {
if (root.left) {
stack.push(root.left) // 左侧全部压栈
root = root.left;
} else {
//左侧栈顶元素没有再左的了,弹出栈顶元素
const node = stack.pop();
console.log(node.val)
// 如果当前栈顶元素有右侧分支
if (node.right) {
stack.push(node.right);
root = node.right; // 右侧分支的左分支全部压栈
}
// 没有的话会继续弹出栈顶节点
}
}
}
}
非递归形式实现后序遍历:
// 后序遍历是先左再右最后根
// 所以对于一个栈来说,应该先 push 根节点
// 然后 push 右节点,最后 push 左节点
function backOrder(root) {
if (root) {
let stack1 = [root];
let stack2 = [];
// 后序遍历是先左再右最后根
// 所以对于一个栈来说,应该先 push 根节点
// 然后 push 右节点,最后 push 左节点
while (stack1.length) {
let node = stack1.pop();
stack2.push(node.val);
if (node.left) { // 先放左再方右
stack1.push(node.left)
}
if (node.right) {
stack1.push(node.right)
}
}
while (stack2.length > 0) {
console.log(stack2.pop());
}
}
}
———— 前端、Javascript实现、算法、刷题、leetcode