1. 什么是二叉树?
- 简单来说二叉树是一个特殊的树,每个节点最多有两个节点
- 二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。
2. 如何新增二叉树节点?
- 二叉树插入节点,比根节点小的插入左子树,比根节点大的插入右子树
//节点类
class Node {
constructor(key) {
this.left = null
this.right = null
this.key = key
}
}
//二叉树类
class BinaryTree {
constructor() {
// 根节点
this.root = null
}
// 插入节点
insert (key) {
const newNode = new Node(key)
if (this.root === null) {
this.root = newNode
} else {
this.inOrderTraversNode(this.root, newNode)
}
}
// 插入节点具体方法
inOrderTraversNode (node, newNode) {
if (newNode.key < node.key) { // 左插
if (node.left === null) {
node.left = newNode
}else{
this.inOrderTraversNode(node.left, newNode)
}
} else { // 右插
if (node.right === null) {
node.right = newNode
} else {
this.inOrderTraversNode(node.right, newNode)
}
}
}
}
3. 如何遍历二叉树?
递归的缺点:时间和空间的消耗比较大、重复计算、调用栈溢出
遍历二叉树一般用递归比较简单,但是如果二叉树层级较多,则会出现调用栈溢出
- 先序遍历 中序遍历 后序遍历
- 前序遍历:首先访问根结点,然后遍历左子树,最后遍历右子树(根->左->右)
- 中序遍历:首先遍历左子树,然后访问根节点,最后遍历右子树(左->根->右)
- 后序遍历:首先遍历左子树,然后遍历右子树,最后访问根节点(左->右->根)
//节点类
class Node {
constructor(key) {
this.left = null
this.right = null
this.key = key
}
}
//二叉树类
class BinaryTree {
constructor() {
// 根节点
this.root = null
}
// 中序遍历--采用递归实现
inOrder(node) {
if (!(node == null)) { // 判断当前节点是否为null
inOrder(node.left); //递归调用,传入当前节点的左节点
console.log(node.show() + " "); // 打印当前节点
inOrder(node.right); // 递归调用,当前节点的右节点
}
}
// 先序遍历
preOrder(node) {
if (!(node == null)) {
console.log(node.show());
preOrder(node.left);
preOrder(node.right);
}
}
// 后序遍历
postOrder(node) {
if (!(node == null)) {
postOrder(node.left);
postOrder(node.right);
console.log(node.show());
}
}
}
4. 二叉树如何查找最大值、最小值?
// 查找最小值
getMin() {
// 由于二叉树是根据大小插入的(大于父节点的在右边,小于父节点的在左边)
// 所以最小值一定在左节点的尽头,反之最大值在右节点的尽头
var current = this.root;// 将当前节点指向根节点
while (!(current.left == null)) { // 判断当前节点的左节点是否为null
current = current.left; // 向下找
}
return current.data; // 返回最左节点
}
// 查找最大值
getMax() {
var current = this.root;
while (!(current.right == null)) {
current = current.right;
}
return current.data;
}
5. 二叉树中如何查找到指定元素?
// 查找特定值
find(data) {
var current = this.root;
while (current != null) {
// 根据数据大小一层一层向下遍历寻找
if (current.data == data) {
return current;
} else if (data < current.data) {
current = current.left;
} else {
current = current.right;
}
}
return null;
}
6. 二叉树中如何删除元素?
function removeSurface(data){
// 重写树,以为删除节点,树发生啦变化
this.root=removeNode(this.root,data);
}
function removeNode(node,data){
if(node==null){ //当树为空树时
return null;
}else if(node.data==data){ //当当前节点的值为data时
if(node.left==null&&node.right==null){ //当当前节点为叶子时
return null;
}else if(node.left==null){ //左子树为空
return node.right;
}else if(node.right==null){ //右子树为空
return node.left;
}else{
// 先通过getMin函数查找以当前元素为根节点的子树中右子树最小值所对应的节点
//(其实也可以找左子树的最大值)
var tempNode=getMin(node.right);
// 将当前元素替换成右子树最小值所对应的节点
node.data=tempNode.data;
// 然后用removeNode递归的方式
// 在以当前元素为根节点的子树中删除右子树最小值所对应的节点。
node.right=removeNode(node.right,tempNode.data);
return node;
// 为什么要找右子树最小值(或者左子树的最大值)呢?
// 因为右子树最小值比左子树的所有值都大,却比右子树的所有值小
// 这正是根节点的特性,用它来替代根节点再适合不过了。
// 当被删除节点的值小于当前节点值时
// 通过node.left=removeNode(node.left,data)将当前节点设置为
// 原当前节点的左节点,并重写以当前节点为根节点的树。
// 当被删除节点的值大于当前节点值时,原理也是如此。
}
}else if(data<node.data){
node.left=removeNode(node.left,data);
return node;
}else{
node.right=removeNode(node.right,data);
return node;
}
}
7. BFS和DFS是什么?
- BFS(广度优先)
- DFS(深度优先)
- 深度优先和广度优先是一种遍历的思想
- 深度优先是先往下遍历
- 广度优先的思想是一层一层往下遍历
BFS和DFS的区别
- 深度优先不需要记住所有的节点, 所以占用空间小, 而广度优先需要先记录所有的节点占用空间大
- 深度优先有回溯的操作(没有路走了需要回头)所以相对而言时间会长一点
- 深度优先采用的是堆栈的形式, 即先进后出
- 广度优先则采用的是队列的形式, 即先进先出
深度优先DFS
- 先序遍历非递归式方法
let deepfirst = function(nodes){
let stack = []
let result = []
stack.push(nodes)
while(stack.length){
let node = stack.pop()
result.push(node)
if(node.left){
result.push(node.left)
}
if(node.right){
result.push(node.right)
}
}
return result
}
广度优先BFS
// 广度优先
function bsf(root){
let quen = []; // 用来遍历的数组
quen.push(root);
// 从队列取,然后再追加
for(let i = 0;i<=quen.length-1;i++){
let k = quen[i];
if(k.left){
quen = quen.concat(k.left);
}
if(k.right){
quen = quen.concat(k.right);
}
}
return quen;
}