二叉树-js(1.基础知识与基本操作):

参考: 数组,链表,二叉树,这些是为了解决什么问题而出现的呢?

二叉树与 JavaScript

3 分钟理解完全二叉树、平衡二叉树、二叉查找树

树的作用:

数组的查找性能为O(1),插入删除为O(n)

链表的查找性能为O(n),插入删除为O(1)

树是数组与链表之间一个折中的办法:查找性能为O(logn),插入删除性能为O(logn)

二叉树的作用:

二叉树的前序遍历可以用来显示目录结构等;中序遍历可以实现表达式树,在编译器底层很有用;后序遍历可以用来实现计算目录内的文件及其信息等。

熟练使用二叉树在很多时候可以提升程序的运行效率,减少代码量,使程序更易读。

二叉树不仅是一种数据结构,也是一种编程思想。学好二叉树是程序员进阶的一个必然进程。

二叉树基本概念:

完全二叉树:所有叶子节点都在最后一层或者倒数第二层,且最后一层的叶子节点再左边连续,倒数第二层的叶子节点在右边连续。

(从上到下,从左到右数是没有中断的。一种数据比较有规律的树)



满二叉树:每一层都是满的,节点数为2^n-1 (n为层数)

满二叉树中的数字规律:

1.每一层的个数是2^(n-1)个,总节点数为 2^n-1 n为层数 (2^0 + 2^1 +2^2 =2^3-1)

2.一个节点的位置为i,左节点位置为i*2+1,右节点位置为i*2+1

image.png

二叉树的基本操作:

二叉树的构建:(数组转二叉树)

问题来源:101. 对称二叉树

[1,2,2,3,4,4,3]

转为二叉树的结构为:

    1
   / \
  2   2
 / \ / \
3  4 4  3

[1,2,2,null,3,null,3]

转为二叉树的结构为

    1
   / \
  2   2
   \   \
   3    3

利用完全二叉树叶子节点与根节点之间的数据关系:

父节点的序号为i时(从0开始),左节点为i*2+1,右节点为i*2+2(完全二叉树可能最后一个父节点上没有右节点)

class TreeNode {
  constructor (val) {
    this.val = val
    this.left = this.right = undefined
  }
  setLeft (left) {
    this.left= left
    return this // 为了链式调用
  }
  setRight (right) {
    this.right = right
    return this // 为了链式调用
  }
}

class BinaryTree {
  constructor () {
    this.root = null
  }

  setRoot (node) {
    this.root = node
  }

  getRoot () {
    return this.root
  }

  arrayToBtree (arr) { // 从左到右广度遍历的数组,转二叉树(针对完全二叉树)
    let nodeList = arr.map(item => new TreeNode(item))
    this.setRoot(nodeList[0])
    let i = 0
    // 给存在右节点的节点添加两个节点
    for (; i * 2 + 2 < arr.length; i++) {
      let node = nodeList[i]
      // left:i*2+1  right:i*2+2
      node.setLeft(nodeList[i * 2 + 1]).setRight(nodeList[i * 2 + 2])
    }
    // 最后一个节点可能为左节点
    if (i * 2 + 1 < arr.length) {
      nodeList[i].setLeft(nodeList[i * 2 + 1])
    }
  }
}
let tree = new BinaryTree()
tree.arrayToBtree([0, 1, 2, 3, 4, 5, 6])
console.log(tree.getRoot())

遍历:

遍历的序是以根节点的位置为基准的

前序遍历:根左右

中序遍历:左根右

后序遍历:左右根

遍历是以一个子树为单位进行递归的遍历

所以上面这个二叉树的中序遍历流程为:

子树1(由于是子树所以继续递归)

节点1.1、节点1.2、节点1.3 3,1,4

子树1遍历完毕,遍历与1平级的2

节点2 0

2遍历完毕,遍历与之平级的子树3

节点3.1、3.2、3.3 5,2,6

前序:0, 1, 3, 4, 2, 5, 6

中序:3, 1, 4, 0, 5, 2, 6

后续: 3, 4, 1, 5, 6, 2, 0

 // 前序遍历 (根左右)
  preorderShow (result = []) {
    result.push(this.val)
    this.left&& this.left.preorder(result) // 如果有左节点,那么进行递归
    this.right && this.right.preorder(result)
    return result
  }

  // 中序遍历 (左根右)
  inorderShow (result = []) {
    this.left&& this.left.inorder(result)
    result.push(this.val)
    this.right && this.right.inorder(result)
    return result
  }
  // 后续遍历 (左右根)
  postorderShow (result = []) {
    this.left&& this.left.postorder(result)
    this.right && this.right.postorder(result)
    result.push(this.val)
    return result
  }

查找:

  // 前序查找
  preorderSearch (val) {
    if (this.val === val) { // 根节点找到
      return this
    }
    let target = null
    target = this.left&& this.left.preorderSearch(val) // 左子树可能找到,可能没找到
    if (target !== undefined) {
      return target
    } else { // 左子树没找到找右子树
      return this.right && this.right.preorderSearch(val)
    }
  }
  // 中序查找
  inorderSearch (val) {
    let target = null
    target = this.left&& this.left.inorderSearch(val) // 左子树可能找到,可能没找到
    if (target !== undefined) {
      return target
    } else {
      if (this.val === val) { // 左子树没找到找根节点
        return this
      } else { // 根节点没找到找右子树
        return this.right && this.right.inorderSearch(val)
      }
    }
  }
  // 后续查找
  postOrderSearch (val) {
    let target = null
    target = this.left&& this.left.postOrderSearch(val) // 左子树可能找到,可能没找到
    if (target !== undefined) {
      return target
    } else {
      target = this.right && this.right.postOrderSearch(val) // 左子树没找到找右子树
      if (target !== undefined) {
        return target
      } else if (this.val === val) { // 右子树没找到找根节点
        return this
      }
    }
  }

删除:

删除一个节点就是将父节点对它的指向设置为undefined

所以在寻找节点并删除的遍历过程中,遍历父节点,判断父节点中是否有子节点需要删除

边界值:根节点没有父节点

只有在树中可以判断一个节点是否为根节点,所以对根节点的删除在树中处理。

BinaryTree:

delete (val) {
    if (this.root === val) {
      this.root = null
    } else {
      this.root.delete(val)
    }
  }

TreeNode:

 delete (val) {
    if (this.left&& this.left.val === val) {
      this.left= undefined
      return
    }
    if (this.right && this.right.val === val) {
      this.right = undefined
      return
    }
    this.left&& this.left.delete(val)
    this.right && this.right.delete(val)
  }

leetcode二叉树的对称:

根节点:判断左右两个子树是否对称

非根节点:对比两个子树是否对称,一层一层的递归判,判断根节点是否相等,再判断A1.left,与A2.right的子树是否对称。

当两棵树的根节点值不相等即可判断为不对称,终止递归

初步思路伪代码:

function isMirror(A1,A2) {
    if(A1.val!== A2.val) { return false }
    ......
    return isMirror(A1.left,A2.right) && isMirror(A1.right,A2.left)
}

改写前序遍历:

之前的前序遍历是写在TreeNode中,递归的终止条件是.left或者.right为空

class TreeNode {
    .
    .
    .
  preorderShow (result = []) {
    result.push(this.val)
    this.left && this.left.preorderShow(result)
    this.right && this.right.preorderShow(result)
    return result
  }
}

改写之后,TreeNode自身没有遍历方法,而是将TreeNode作为参数,此时的递归终止条件为传入的treeNode为空

function preorderShow (treeNode, result = []) {
  if (!treeNode) { return result }
  result.push(treeNode.val)
  preorderShow(treeNode.left, result)
  preorderShow(treeNode.right, result)
  return result
}

所以根据之前的思路以及第2种遍历方法的思想:判断两棵树相等的递归终止条件为两棵树都为空

​ 判断两棵树不等的递归终止条件有有一颗为空,或都不为空但是值不相等

所以题解:

function isMirror (A1, A2) {
  if (A1 == null && A2 == null) { return true }
  if ((!A1 && A2) || (A1 && !A2) || A1.val !== A2.val) { return false }
  return isMirror(A1.left, A2.right) && isMirror(A1.right, A2.left)
}

var isSymmetric = function (root) {
    if(root == null) return true
  return isMirror(root.left, root.right)
}

官方java题解:


以上涉及到的所有代码:

class TreeNode {
  constructor (val) {
    this.val = val
    this.left = this.right = undefined
  }
  setLeft (left) {
    this.left = left
    return this // 为了链式调用
  }
  setRight (right) {
    this.right = right
    return this // 为了链式调用
  }
  // 前序遍历 (根左右)
  preorderShow (result = []) {
    result.push(this.val)
    this.left && this.left.preorderShow(result)
    this.right && this.right.preorderShow(result)
    return result
  }
  // 中序遍历 (左根右)
  inorderShow (result = []) {
    this.left && this.left.inorderShow(result)
    result.push(this.val)
    this.right && this.right.inorderShow(result)
    return result
  }
  // 后续遍历 (左右根)
  postorderShow (result = []) {
    this.left && this.left.postorderShow(result)
    this.right && this.right.postorderShow(result)
    result.push(this.val)
    return result
  }
  // 前序查找
  preorderSearch (val) {
    if (this.val === val) { // 根节点找到
      return this
    }
    let target = null
    target = this.left && this.left.preorderSearch(val) // 左子树可能找到,可能没找到
    if (target !== undefined) {
      return target
    } else { // 左子树没找到找右子树
      return this.right && this.right.preorderSearch(val)
    }
  }
  // 中序查找
  inorderSearch (val) {
    let target = null
    target = this.left && this.left.inorderSearch(val) // 左子树可能找到,可能没找到
    if (target !== undefined) {
      return target
    } else {
      if (this.val === val) { // 左子树没找到找根节点
        return this
      } else { // 根节点没找到找右子树
        return this.right && this.right.inorderSearch(val)
      }
    }
  }
  // 后续查找
  postOrderSearch (val) {
    let target = null
    target = this.left && this.left.postOrderSearch(val) // 左子树可能找到,可能没找到
    if (target !== undefined) {
      return target
    } else {
      target = this.right && this.right.postOrderSearch(val) // 左子树没找到找右子树
      if (target !== undefined) {
        return target
      } else if (this.val === val) { // 右子树没找到找根节点
        return this
      }
    }
  }
  delete (val) {
    if (this.left && this.left.val === val) {
      this.left = undefined
      return
    }
    if (this.right && this.right.val === val) {
      this.right = undefined
      return
    }
    this.left && this.left.delete(val)
    this.right && this.right.delete(val)
  }
}

class BinaryTree {
  constructor () {
    this.root = null
  }

  setRoot (node) {
    this.root = node
  }

  getRoot () {
    return this.root
  }

  arrayToBtree (arr) { // 从左到右广度遍历的数组,转二叉树(针对完全二叉树)
    let nodeList = arr.map(item => new TreeNode(item))
    this.setRoot(nodeList[0])
    let i = 0
    // 给存在右节点的节点添加两个节点
    for (; i * 2 + 2 < arr.length; i++) {
      let node = nodeList[i]
      // left:i*2+1  right:i*2+2
      node.setLeft(nodeList[i * 2 + 1]).setRight(nodeList[i * 2 + 2])
    }
    // 最后一个节点可能为左节点
    if (i * 2 + 1 < arr.length) {
      nodeList[i].setLeft(nodeList[i * 2 + 1])
    }
  }

  delete (val) {
    if (this.root === val) {
      this.root = null
    } else {
      this.root.delete(val)
    }
  }
}

let tree = new BinaryTree()
tree.arrayToBtree([0, 1, 2, 3, 4, 5, 6])
console.log(tree.getRoot())
let root = tree.getRoot()
let preorder = root.preorderShow()
console.log(preorder) // [ 0, 1, 3, 4, 2, 5, 6 ]

let inorder = root.inorderShow()
console.log(inorder) // [ 3, 1, 4, 0, 5, 2, 6 ]

let postorder = root.postorderShow()
console.log(postorder) // [ 3, 4, 1, 5, 6, 2, 0 ]

console.log(root.preorderSearch(3))
console.log(root.preorderSearch(2))

console.log(root.inorderSearch(6))
console.log(root.inorderSearch(1))

console.log(root.postOrderSearch(5))
console.log(root.postOrderSearch(1))

console.log(root.postOrderSearch(8)) // undefined

tree.delete(2)
console.log(root.preorderShow()) // [ 0, 1, 3, 4 ]

// 前序遍历
function preorderShow (treeNode, result = []) {
  if (!treeNode) { return result }
  result.push(treeNode.val)
  preorderShow(treeNode.left, result)
  preorderShow(treeNode.right, result)
  return result
}
console.log(preorderShow(root, [])) // [ 0, 1, 3, 4 ]


// 二叉树的对称
function isMirror (A1, A2) {
  if (A1 == null && A2 == null) { return true }
  if ((!A1 && A2) || (A1 && !A2) || A1.val !== A2.val) { return false }
  return isMirror(A1.left, A2.right) && isMirror(A1.right, A2.left)
}
function isSymmetric (root) {
  if (root == null) return true
  return isMirror(root.left, root.right)
}
console.log(isSymmetric(root)) // false

二叉树进阶

红黑树、线段树、搜索树、B+树、语法树、哈夫曼树

你可能感兴趣的:(二叉树-js(1.基础知识与基本操作):)