二叉树以及链表

446 阅读4分钟

二叉树

树形结构

  • 像数组、栈、队列、默认都是线性结构类型。常见的树形结构有二叉树和多叉树(大于两个叉的树)。

  • 开发中常见的树形结构有: 文件夹目录、DOM结构、路由的配置...... (树的数据结构是非常重要的)

常见概念

  • 节点 (根节点、父节点、字节点、兄弟节点)

  • 子树 (左子树、右子树),子树的个数称之为度

  • 叶子节点 (度为0的节点) 非叶子节点 (度不为0的节点)

  • 节点的深度 (从根节点到当前节点所经过的节点总数)

  • 节点的高度 (从当前节点到最远叶子节点经过的节点总数)

  • 树的层数 (树的高度、树的深度)

  • 有序树( 节点按照顺序排列)、无序树

二叉树

二叉树是每个结点最多有两个子树的树结构 ,每个节点的度最多为2。 通常子树被称作“左子树”(left subtree)和“右子树”(right subtree) ,左子树和右子树是有顺序的

二叉树的常见概念
  • 真二叉树: 不含一度节点的二叉树称作真二叉树(proper binary tree)
  • 满二叉树:满二叉树也是真二叉树,且所有的叶子节点都在最后一层
  • 完全二叉树: 深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
.二叉搜索树
  • 1.什么是二叉搜索树? 一般情况下存储数据我们可以采用数组的方式,但是从数组中检索数据的时间复杂度是O(n),如果数据存储有序,则可以采用二分查找的方式来检索数据,复杂度为:O(logn),但是如果操作数组中的数据像增加、删除默认数组会产生塌陷。时间复杂度为O(n)

  • 二叉搜索树中查询、增加、删除复杂度最坏为O(logn),特性是当它的左子树不空,则左子树上所有结点的值均小于它的根结点的值,当右子树不空,则右子树上所有结点的值均大于它的根结点的值。

class Node {
  constructor(element, parent) {
    this.element = element
    this.parent = parent
    this.left = null
    this.right = null
  }
}
class BST {
  constructor() {
    // 树有跟节点,和节点个数
    this.root = null
    this.size = 0
  }
  add(element, parent = null) {
    // 构建二叉树
    if (this.root === null) {
      // 根节点
      this.root = new Node(element, null)
    } else {
      // 叶子节点
      let current = this.root // 找到当前要添加的父元素,从根节点开始找
      let compare = 0
      let parent = null
      while(current) {
        // 判断是在当前元素的左边还是右边
        parent = current
        compare = element - current.element
      
        // 如果> 0,在右侧子树,如果小于0,在左侧子树
        if (compare > 0) {
          current = current.right
        } else {
          current = current.left
        }
      }
      let node = new Node(element, parent)
      if (compare > 0) {
        parent.right = node
      } else {
        parent.left = node
      }
    }
    this.size++
    return this.root
  }
  preorderTravesal(visitor) {
    const travesal = (node) => {
      if(node === null) return
      visitor.visit(node.element)
      travesal(node.left)
      travesal(node.right)
    }
    // 先序遍历, 根左右
    travesal(this.root)
  }
  inorderTravesal(visitor) {
    // 中序遍历, 左根右
    const travesal = (node) => {
      if(node === null) return
      travesal(node.left)
      visitor.visit(node.element)
      travesal(node.right)
    }
    // 先序遍历, 根左右
    travesal(this.root)
    
  }
  postorderTravesal(visitor) {
    // 后序遍历,左右根
    const travesal = (node) => {
      if(node === null) return
      travesal(node.left)
      travesal(node.right)
      visitor.visit(node.element)
    }
    // 先序遍历, 根左右
    travesal(this.root)

  }
  levelordertravesal(visitor) {
    // 层序遍历, 一层一层的来,使用栈
    if (!this.root || !visitor) return null
    let stack = [this.root]
    let index = 0
    let current = null
    while(current = stack[index]) {
      visitor.visit(current.element)
      if(current.left) {
        stack.push(current.left)
      } 
      if (current.right) {
        stack.push(current.right)
      }
      index++ // 指针右移
    }
    stack = []
  }
  reverse() {
    // 二叉树的反转,在遍历过程中将左右孩子交换,其实是一个冒泡排序的过程
    if (!this.root) return
    let stack = [this.root]
    let index = 0
    let current = null
    while(current = stack[index++]) {
      // 遍历树
      let temp = current.left
      current.left = current.right
      current.right = temp
      if (current.left) {
        stack.push(current.left)
      }
      if (current.right) {
        stack.push(current.right)
      }
    }
    return this.root
  }
}
let bst = new BST()
bst.add(10)
bst.add(8)
bst.add(19)
bst.add(6)
bst.add(15)
bst.add(22)
console.log(bst.root)
console.log(bst.reverse())

单向链表

  • 各个节点数据通过指针的方法串联起来,构成链表。(单向指针)

创建链表

class LinkNode {
  constructor(element, next) {
    this.element = element
    this.next = next
  }
}

class LinkList {
  constructor() {
    this.head = null
    this.size = 0
  }
  getNode(index) {
    // 从头开始找
    let current = this.head
    for(let i = 0; i < index; i++) {
      current = current.next
    }
    return current
  }
  add(index, element) {
    // 实现函数的重载,如果参数只有一个,是从后追加一个,
    if (arguments.length === 1) {
      element = index
      index = this.size
    }
    // element 是本身的值,index是索引
    if (index > this.size || index < 0) throw new Error('越界')
    if(index === 0) {
      // 说明是头指针
      let oldHead = this.head
      this.head = new LinkNode(element, oldHead)
    } else {
      // 找到当前位置老的元素,将next指向老的元素
      let previousNode = this.getNode(index -1)
      previousNode.next = new LinkNode(element, previousNode.next)
    }

    this.size++
  }


let links = new LinkList()
links.add(1)
links.add(2)
links.add(3)
console.dir(links, {depth: 1000})

反转链表

   // 反转链表
  reverseLink() {
    /*
       思路:
       递归: 将头部的next.next指向当前头部,新的头部为老头的next
    */
    const reverse = (head) => {
      if (head === null || head.next === null) return head;
      let newHead = reverse(head.next)
      head.next.next = head
      head.next = null
      return newHead
    }
    this.head = reverse(this.head)
    return this.head
  }
  reverseLink2() {
    /*
      非递归方式: 
      先创建一个新头,然后一个一个的往下拿
    */ 
   let head = this.head //老头
   if (head === null || head.next === null) return head;
   let newHead = null
    while(head) {
      const temp = head.next // 保存下一个
      head.next = newHead
      newHead = head
      head = temp
    }
    this.head = newHead
    return this.head
  }
}
console.dir(links.reverseLink2(), {depth: 1000})