二叉树和二叉查找树

树是一种非线性的数据结构,以分层的方式存储数据。树被用来存储具有层级关系的数据,比如文件系统中的文件;树还被用来存储 有序列表。

二叉树是一种特殊的树,它的子节点个数不超过两个。二叉树具有一些特殊的计算性质,使得在它们之上的一些操作异常高效沿着一组特定的边,可以从一个节点走到另外一个与它不直接相连的节点。从一个节点到另一个节点的这一组边称为路径,树可以分为几个层次,根节点是第0层,它的子节点是第1层,子节点的子节点是第2层,以此类推。树中任何一层的节点可以都看做是子树的根,该子树包含根节点的子节点,子节点的子节点等。我们定义树的层数就是树的深度每个节点都有一个与之相关的值,该值有时被称为键。一个节点到另一个节点的路程称为路径

image.png

二叉树每个节点的子节点不允许超过两个。通过将子节点的个数限定为 2,可以写出高效的程序在树中插入、查找和删除数据一个父节点的两个子节点分别称为左节点和右节点。在一些二叉树的实现中,左节点包含一组特定的值,右节点包含另一组特定的值。

当考虑某种特殊的二叉树,比如二叉查找树时,确定子节点非常重要。
二叉查找树是一种特殊的二叉树,相对较小的值保存在左节点中,较大的值保存在右节点中。这一特性使得查找的效率很高
image.png

下面实现二叉查找树

Node 类的定义

每一个节点都含有三个元素

  1. 键值data
  2. 左节点指针left
  3. 右节点指针right

    class Node{
      constructor(data, left, right){
        this.data = data;
        this.left = left;
        this.right = right;
      }
      show(){
        return this.data
      }
    }

二叉查找树类

初始化根节点root为null

class BST{
  root=null
  constructor(){
  }
}

insert() 方法,用来向树中加入新节点

  insert(data) {
        let node = new Node(data, null, null)
        if (this.root == null) {
            this.root = node
        } else {
            let current = this.root
            let parent
            while (true) {
                parent = current
                if (data < current.data) {
                    current = current.left
                    if (current == null) {
                        parent.left = node
                        break;
                    }
                } else {
                    current = current.right
                    if (current == null) {
                        parent.right = node
                        break;
                    }
                }
            }
        }
    }

遍历二叉查找树

有三种遍历 BST 的方式:中序、先序和后序。中序遍历按照节点上的键值,以升序访问BST 上的所有节点。先序遍历先访问根节点,然后以同样方式访问左子树和右子树。后序遍历先访问叶子节点,从左子树到右子树,再到根节点。

先序遍历preOrder()

image.png

preOrder(node) {
        if (node != null) {
            console.log(node.show())
            this.inOrder(node.left)
            this.inOrder(node.right)
        }
    }

中序遍历inOrder()

image.png

  inOrder(node) {
        if (node != null) {
            this.inOrder(node.left)
            console.log(node.show())
            this.inOrder(node.right)
        }
    }

后序遍历postOrder()

image.png

postOrder(node) {
        if (node != null) {
            this.inOrder(node.left)
            this.inOrder(node.right)
            console.log(node.show())
        }
    }

getMin()查找最小值

最小值在最左边,一致遍历左边个元素,查找到最后一个元素,就是最小值

getMin() {
        let node = this.root
        while (node.left != null) {
            node = node.left
        }
        return node.data
    }

getMax()查找最大值

最大值在最右边,一致遍历右边个元素,查找到最后一个元素,就是最大值

getMax() {
        let node = this.root
        while (node.right != null) {
            node = node.right
        }
        return node.data
    }

find()查找指定值的节点

find(data) {
        let node = this.root
        let result = null
        while (node) {
            if (node.data == data) {
                result = node
                break
            }
            if (node.data < data) {
                node = node.right
            } else {
                node = node.left
            }
        }
        

从二叉查找树上删除节点

BST 上删除节点的操作最复杂,其复杂程度取决于删除哪个节点。如果删除没有子节点的节点,那么非常简单,直接移除节点即可。如果节点只有一个子节点,不管是左子节点还是右子节点,就变得稍微有点复杂了,需要将父节点指向删除节点的指针指向对应剩下的左子节点活右子节点。删除包含两个子节点的节点最复杂,需要将右子树中最小的节点或左子树中最小的节点将要被移除的节点替换。
为了管理删除操作的复杂度,我们使用递归操作,同时定义两个方法:remove()和removeNode()。

remove(data) {
        this.root = this.removeNode(this.root, data)
    }

    removeNode(node, data) {
        if (node == null) {
            return null
        }
        if (node.data == data) {
            if (node.left == null && node.right == null) {
                return null
            }
            if (node.left == null) {
                return node.right
            }
            if (node.right !== null) {
                return node.left
            }
            let smallestNode = this.getSmallest(node.right)
            node.data = smallestNode.data
            this.removeNode(node.right, smallestNode.data)
            return node
        } else if (node.data < data) {
            node.right = this.removeNode(node.right.data)
        } else {
            node.left = this.removeNode(node.left.data)
        }
    }
    getSmallest(node) {
        while (node.left != null) {
            node = node.left
        }
        return node
    }

完整代码

class Node {
    constructor(data, left, right) {
        this.data = data;
        this.left = left;
        this.right = right;
    }
    show() {
        return this.data
    }
}
class BST {
    root = null
    constructor() {
    }
    insert(data) {
        let node = new Node(data, null, null)
        if (this.root == null) {
            this.root = node
        } else {
            let current = this.root
            let parent
            while (true) {
                parent = current
                if (data < current.data) {
                    current = current.left
                    if (current == null) {
                        parent.left = node
                        break;
                    }
                } else {
                    current = current.right
                    if (current == null) {
                        parent.right = node
                        break;
                    }
                }
            }
        }
    }
    inOrder(node) {
        if (node != null) {
            this.inOrder(node.left)
            console.log(node.show())
            this.inOrder(node.right)
        }
    }
    preOrder(node) {
        if (node != null) {
            console.log(node.show())
            this.inOrder(node.left)
            this.inOrder(node.right)
        }
    }
    postOrder(node) {
        if (node != null) {
            this.inOrder(node.left)
            this.inOrder(node.right)
            console.log(node.show())
        }
    }
    getMin() {
        let node = this.root
        while (node.left != null) {
            node = node.left
        }
        return node.data
    }
    getMax() {
        let node = this.root
        while (node.right != null) {
            node = node.right
        }
        return node.data
    }
    find(data) {
        let node = this.root
        let result = null
        while (node) {
            if (node.data == data) {
                result = node
                break
            }
            if (node.data < data) {
                node = node.right
            } else {
                node = node.left
            }
        }
        return result
    }
    remove(data) {
        this.root = this.removeNode(this.root, data)
    }
    removeNode(node, data) {

        if (node == null) {
            return null
        }
        if (node.data == data) {
            if (node.left == null && node.right == null) {
                return null
            }
            if (node.left == null) {
                return node.right
            }
            if (node.right !== null) {
                return node.left
            }
            let smallestNode = this.getSmallest(node.right)
            node.data = smallestNode.data
            this.removeNode(node.right, smallestNode.data)
            return node
        } else if (node.data < data) {
            node.right = this.removeNode(node.right.data)
        } else {
            node.left = this.removeNode(node.left.data)
        }
    }
    getSmallest(node) {
        while (node.left != null) {
            node = node.left
        }
        return node
    }

}

图由边的集合及顶点的集合组成。边由顶点对 (v1,v2) 定义,v1 和 v2 分别是图中的两个顶点。顶点也有权重。如果一个图的顶点对是有序的,则可以称之为有向图。在对有向图中的顶点对排序后,便可以在两个顶点之间绘制一个箭头。有向图表明了顶点的流向。

image.png

如果图是无序的,则称之为无序图

image.png

我们将表示图的边的方法称为邻接表或者邻接表数组。这种方法将边存储为由顶点的相邻顶点列表构成的数组,并以此顶点作为索引。
image.png

下面实现图类

表示顶点

class Vertex{
  constructor(label){
    this.label=label
  }
}

构建图

class Graph{
  edges=0
  adj=[]
  vertices=0
  marked=[]
  edgeTo=[]
  constructor(v){
    this.vertices = v
    for(let index=0;index < this.vertices;index++){
      this.adj[index]=[]
      this.marked[index]=false
    }
  }
}

addEdge()添加节点

 addEdge(v,w){
    this.adj[v].push(w)
    this.adj[w].push(v)
    this.edges++
  }

showGraph() 函数会通过打印所有顶点及其相邻顶点列表的方式来显示图

showGraph() {
    for (var i = 0; i < this.vertices; ++i) {
     let resStr = `${i}-> `
      for (var j = 0; j < this.vertices; ++j ) {
        if (this.adj[i][j] != undefined) {
          resStr=resStr+`${this.adj[i][j]} `
        }
      }
      console.log(resStr)
    }
  }

深度优先搜索

深度优先搜索包括从一条路径的起始顶点开始追溯,直到到达最后一个顶点,然后回溯,继续追溯下一条路径,直到到达最后的顶点,如此往复,直到没有路径为止。这不是在搜索特定的路径,而是通过搜索来查看在图中有哪些路径可以选择

image.png

  dfs(v){
    this.marked[v]=true
    if(this.adj[v] != undefined){
      console.log("Visited vertex: " + v)
    }
    for(let key of this.adj[v]){
      if(!this.marked[key]){
        this.dfs(key)
      }
    }
  }

广度优先搜索

广度优先搜索从第一个顶点开始,尝试访问尽可能靠近它的顶点。本质上,这种搜索在图上是逐层移动的,首先检查最靠近第一个顶点的层,再逐渐向下移动到离起始顶点最远的层

image.png

  bfs(s){
    let queue = []
    this.marked[s] = true
    queue.push(s)
    while(queue.length>0){
      let v = queue.shift()
      if(v == undefined){
       console.log(`Visisted vertex: ${v}`)
      }
      for(let key of this.adj[v]){
        if(!this.marked[key]){
          this.edgeTo[key]=v
          this.marked[key] = true
          queue.push(key)
        }
      }
    }
  }

完整代码

class Vertex{
  constructor(label){
    this.label=label
  }
}
class Graph{
  edges=0
  adj=[]
  vertices=0
  marked=[]
  edgeTo=[]
  constructor(v){
    this.vertices = v
    for(let index=0;index < this.vertices;index++){
      this.adj[index]=[]
      this.marked[index]=false
    }
  }
  addEdge(v,w){
    this.adj[v].push(w)
    this.adj[w].push(v)
    this.edges++
  }
  showGraph() {
    for (var i = 0; i < this.vertices; ++i) {
     let resStr = `${i}-> `
  
      for (var j = 0; j < this.vertices; ++j ) {
        if (this.adj[i][j] != undefined) {
          resStr=resStr+`${this.adj[i][j]} `
        }
      }
      console.log(resStr)
    }
  }
  dfs(v){
    this.marked[v]=true
    if(this.adj[v] != undefined){
      console.log("Visited vertex: " + v)
    }
    for(let key of this.adj[v]){
      if(!this.marked[key]){
        this.dfs(key)
      }
    }
  }
  bfs(s){
    let queue = []
    this.marked[s] = true
    queue.push(s)
    while(queue.length>0){
      let v = queue.shift()
      if(v == undefined){
       console.log(`Visisted vertex: ${v}`)
      }
      for(let key of this.adj[v]){
        if(!this.marked[key]){
          this.edgeTo[key]=v
          this.marked[key] = true
          queue.push(key)
        }
      }
    }
  }
}


麻路平
8 声望0 粉丝