树 - 二叉树、二叉搜索树

111 阅读4分钟

回忆下,我们说的链表是一维结构,它的查找节点时间复杂度是O(n)的,正式因为这个原因的话,后来就出现了跳表一样的结构,加上了更快的索引,比如从头指针直接跨到第二个节点,注意,这里涉及一个理念,前面也提到过,如果我们想加速,关键点是升维,常见的二维的数据结构有树和图

链表结构: 如果链表的节点有多个next指针,指向多个节点,那么它就变成了一个数🌲状结构了。接下来,我们认识一种新的数据结构 - 树。

Tree 树

树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树.

首先它有个根节点(root),B节点和C节点都是它的子树🌲,我们称B和C为左子树和右子树,它们是彼此的兄弟节点,同时B和C节点又为它们子节点的父节点。树还分了不同的层,root节点为第0层,B和C为第一层。

定义树节点

function TreeNode(val) {
    this.val = val;
    this.left = this.right = null;
}

Binary Tree 二叉树

现实中用的更多的还是二叉树,二叉树的子节点只有2个。

树的延伸 - Graph 图(简介)

那么树和图最关键的一个差别是什么呢,就是看有没有环装结构,如果节点本身的话,只连到新的儿子节点,永远不会走回去,如下图,如果F节点有个next指针指向了E,或者指向了A,或者指向了它的父节点C的话,那么就相当于形成了一个环,这是我们按照定义不称为树,而是叫做图。

但是在特殊情况下,你也可以理解为Linked list就是特殊化的Tree(多个next指针的链表), Tree就是特殊化的Graph(没有环的🌲)。

树的遍历

  • 前序遍历(pre-order):根-左-右
  • 中序遍历(In-order):左-根-右
  • 后序遍历(Post-order):右根左
/**
 * @descripe 前序遍历
 * @param {TreeNode} root
 * @return {number[]}
 */
var preorderTraversal = function (root) {
    let arr = [];
    preorder(root, arr);
    return arr;
}

function preorder(root, arr){
    if (!root) { 
        return;
    }
    if (root){
        arr.push(root.val); // 当前节点收集
        preorder(root.left, arr); // 左节点搜集
        preorder(root.right, arr); // 右节点搜集
    }
}

这么看来,我们要想查找某个节点的值,时间复杂度还是O(n)的,因为看来需要整个遍历一下,它和一个链表基本上没有太大的区别,那么树到底有什么优势呢?一般来说,树的结构我们会把它变得更加有序,这就好像你的宿舍或者你的办公室、或者你的电脑里面,经常使用的东西,把它排的相对有序的话,那你查找和维护起来就会更加有效,二叉树也是这样,这就是我们接下来要介绍的二叉搜索树。

二叉搜索树 (Binary Search Tree)

二叉搜索树,也称为二叉排序树、有序二叉树(Ordered Binary Tree)、排序二叉树(Sorted Binary Tree),是指一棵空树或者具有下列性质的二叉树:

  1. 左子节点树上所有节点的值均小于它的根节点上的值
  2. 右子节点树上所有节点的值均大于它的根节点上的值
  3. 以此类推,左、右子树也分别为二叉查找树。(这就是重复性!)

二叉搜索树的中序遍历就是升序遍历!!

二叉搜索树常见操作

  • 查询
  • 插入新节点(创建)
  • 删除

它的查询和插入都是O(logn)了,所以就相当于加速了。

可视化操作