前端中常见数据结构小结

常见数据结构的 JavaScript 实现系列

前端与数据结构

数据结构在开发中是一种编程思想的提炼,无关于用何种语言开发或者是哪种端开发。下列将笔者涉猎到的与前端相关的数据结构案例作如下总结:

数据结构 案例
FILO: 其它数据结构的基础,redux/koa2 中间件机制
队列 FIFO:其它数据结构的基础
链表 React 16 中的 Fiber 的优化
集合 对应 JavaScript 中的 Set
字典 对应 JavaScript 中的 Map
哈希表 一种特殊的字典,可以用来存储加密数据
DOM TREE / HTML TREE / CSS TREE
暂时没遇到,不过里面的 BFS/DFS 蛮常见

下文为增加字数,挑了篇上述的二叉树章节(字数太少不能发布),欢迎阅读原文。

二叉树

这幅图中有如下概念:

  • 根节点:一棵树最顶部的节点
  • 内部节点:在它上面还有其它内部节点或者叶节点的节点
  • 叶节点:处于一棵树根部的节点
  • 子树:由树中的内部节点和叶节点组成

此外这棵树是二叉树(树中最多有两个分支),同时它也是二叉搜索树(左侧子节点的数字小于父节点,右侧子节点的数字大于父节点)

二叉搜索树的实现

function BinarySearchTree() {
  function Node(key) {
    this.key = key
    this.left = null
    this.right = null
  }

  let root = null

  // 插入元素
  // 实现思路:至顶向下插入,先判断顶点是否为空;顶点为空则直接在该处插入,若不为空,则通过比较顶点的 key 和插入元素的 key 判断该插入到顶点的左侧还是右侧,后面进行如上递归
  this.insert = function(key) {
    const node = new Node(key)
    if (root === null) {
      root = node
    } else {
      insertNode(root, node)
    }
    function insertNode(parent, node) {
      if (parent.key > node.key) {
        if (parent.left === null) {
          parent.left = node
        } else {
          insertNode(parent.left, node)
        }
      } else if (parent.key < node.key) {
        if (parent.right === null) {
          parent.right = node
        } else {
          insertNode(parent.right, node)
        }
      }
    }
  }

  // 中序遍历
  this.inOrderTraverse = function(cb) {
    inOrderTraverse(root, cb)
    function inOrderTraverse(node, cb) {
      if (node) {
        inOrderTraverse(node.left, cb)
        cb(node.key)
        inOrderTraverse(node.right, cb)
      }
    }
  }

  // 先序遍历
  this.preOrderTraverse = function(cb) {
    preOrderTraverse(root, cb)
    function preOrderTraverse(node, cb) {
      if (node) {
        cb(node.key)
        preOrderTraverse(node.left, cb)
        preOrderTraverse(node.right, cb)
      }
    }
  }

  // 后序遍历
  this.postOrderTraverse = function(cb) {
    postOrderTraverse(root, cb)
    function postOrderTraverse(node, cb) {
      if (node) {
        postOrderTraverse(node.left, cb)
        postOrderTraverse(node.right, cb)
        cb(node.key)
      }
    }
  }

  // 最大值:思路最右边
  this.max = function() {
    let maxResult = {}
    function getMax(node) {
      if (node && node.right) {
        maxResult = node.right
        getMax(node.right)
      }
    }
    getMax(root)
    return maxResult.key
  }

  // 最小值:思路最左边
  this.min = function() {
    let minResult = {}
    function getMin(node) {
      if (node && node.left) {
        minResult = node.left
        getMin(node.left)
      }
    }
    getMin(root)
    return minResult.key
  }

  // 查找指定元素
  this.search = function(key) {
    const searchKey = function(node) {
      if (!node) {
        return false
      }
      if (key > node.key) {
        return searchKey(node.right)
      } else if (key < node.key) {
        return searchKey(node.left)
      } else {
        return true
      }
    }

    return searchKey(root)
  }

  // 移除指定 key 值
  this.remove = function(key) {
    const removeKey = function(node, key) {
      if (key < node.key) {         // ① 如果 key 值在传入节点的左边
        node.left = removeKey(node.left, key)
        return node
      } else if (key > node.key) {  // ② 如果 key 值在传入节点的右边
        node.right = removeKey(node.right, key)
        return node
      } else {                      // ③ 如果找到了 key 值
        if (node.left === null && node.right === null) { // 删除的节点为根节点
          node = null
          return node
        }
        if (node.left === null) {                        // 删除的节点下有一个分支
          node = node.right
          return node
        } else if (node.right === null) {
          node = node.left
          return node
        }
        const minNode = findMinNode(node.right)          // 删除的节点下有两个分支
        node.key = minNode.key
        node.right = removeKey(node.right, minNode.key)
        return node
      }
    }

    // 查找最小的节点
    const findMinNode = function(node) {
      if (node.left) {
        return findMinNode(node.left)
      } else {
        return node
      }
    }

    removeKey(root, key)
  }
}

var tree = new BinarySearchTree()
tree.insert(11)
tree.insert(7)
tree.insert(15)
tree.insert(5)
tree.insert(3)
tree.insert(9)
tree.insert(8)
tree.insert(10)
tree.insert(13)
tree.insert(12)
tree.insert(14)
tree.insert(20)
tree.insert(18)
tree.insert(25)
tree.insert(6)

三种遍历方式的不同

  • 中序遍历:可用于二叉搜索树的排序
  • 先序遍历:可用于打印结构化的文档
  • 后序遍历:可用于查看文件夹目录

三种遍历的实现方式大同小异,可在上面代码中观察到实现的差异。

remove 的几种情况

remove 方法是二叉查找树中相对复杂的实现。思路仍然是递归。

如果要删除的 key 在传入节点的左侧,则递归调用 removeKey(node.left, key);

如果要删除的 key 在传入节点的右侧,则递归调用 removeKey(node.right, key);

如果要删除的 key 与传入节点相等,有如下三种情况:

①:删除的节点为根节点

②:删除的节点下有一个分支

③:删除的节点下有两个分支

这里的思路是找到当前节点的右分支中最小的节点,然后将该节点代替当前节点,同时移除当前节点的右分支中最小的节点

测试用例

var tree = new BinarySearchTree()
tree.insert(11)
tree.insert(7)
tree.insert(15)
tree.insert(5)
tree.insert(3)
tree.insert(9)
tree.insert(8)
tree.insert(10)
tree.insert(13)
tree.insert(12)
tree.insert(14)
tree.insert(20)
tree.insert(18)
tree.insert(25)
tree.insert(6)

var cb = (key) => console.log(key)

tree.inOrderTraverse(cb)   // 中序遍历: 3 5 6 7 8 9 10 11 12 13 14 15 18 20 25
tree.preOrderTraverse(cb)  // 先序遍历:11 7 5 3 6 9 8 10 15 13 12 14 20 18 25
tree.postOrderTraverse(cb) // 后序遍历:3 6 5 8 10 9 7 12 14 13 18 25 20 15 11

tree.max() // 25
tree.max() // 3

tree.search(6) // true
tree.search(1) // false

原文地址:https://www.cnblogs.com/MuYunyun/p/9498142.html

时间: 2024-10-09 20:07:35

前端中常见数据结构小结的相关文章

Java中常见数据结构:list与map -底层如何实现

1:集合 2 Collection(单列集合) 3 List(有序,可重复) 4 ArrayList 5 底层数据结构是数组,查询快,增删慢 6 线程不安全,效率高 7 Vector 8 底层数据结构是数组,查询快,增删慢 9 线程安全,效率低 10 LinkedList 11 底层数据结构是链表,查询慢,增删快 12 线程不安全,效率高 13 Set(无序,唯一) 14 HashSet 15 底层数据结构是哈希表. 16 哈希表依赖两个方法:hashCode()和equals() 17 执行顺

Unity3D游戏开发之C#编程中常见数据结构的比较

一.前言 Unity3D是如今最火爆的游戏开发引擎,它可以让我们能轻松创建诸如三维视频游戏.建筑可视化.实时三维动画等类型的互动内容.它支持2D/3D游戏开发,据不完全统计,目前国内80%的手机游戏都是用Unity3D开发. 由于Unity3D在开发过程中使用最多的是C# 语言,所以就要合理的使用C#提供的一些数据结构是非常有必要的,合理的选择数据结构可以加快开发速度,提高游戏运行性能,不合理的使用数据结构则会导致游戏运行性能降低,加大开发复杂程度! 先通过表格看看常用的数据结构: C#常用数据

JAVA中常见异常小结

1.java.lang.ArithmeticException 算术运算异常,例如除数为0,所以引发了算数异常 2.Java.lang.StringIndexOutOfBoundsException:  这是截取字符串substring()产生的下标越界异常.原因是可能是字符串为空,或长度不足1 3.Java.lang.NullPointerException空指针异常 出现该异常的原因在于某个引用为null,但却调用了它的某个方法,这时就会出现该异常 4.ClassCastException

Java中常见数据结构Map之HashMap

之前很早就在博客中写过HashMap的一些东西: 彻底搞懂HashMap,HashTableConcurrentHashMap关联: http://www.cnblogs.com/wang-meng/p/5808006.html HashMap和HashTable的区别: http://www.cnblogs.com/wang-meng/p/5720805.html 今天来讲HashMap是分JDK7和JDK8 对比着来讲的, 因为JDK8中针对于HashMap有些小的改动, 这也是一些面试会经

Java中常见数据结构Map之LinkedHashMap

前面已经说完了HashMap, 接着来说下LinkedHashMap. 看到Linked就知道它是有序的Map,即插入顺序和取出顺序是一致的, 究竟是怎样做到的呢? 下面就一窥源码吧. 1, LinkedHashMap基本结构 LinkedHashMap是HashMap的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap. LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序.此实现提供所有可选的映射操作,并允许使用n

Java中常见数据结构Set之HashSet

今天来说说Java集合中的Set系列之HashSet. Set我们众所周知的就是虑重功能, 我们平时在项目开发中也常用到这个特性的.那么Set为何能够虑重呢? 接下来我们就看下源码吧. Set的底层实现是HashMap(这个后面讲Map时也会讲它的源码), 当我们在HashSet中添加一个新元素时, 其实这个值是存储在底层Map的key中,而众所周知,HashMap的key值是不能重复的, 所以这里就可以达到去重的目的了. 直接看下HashSet的源码: 当我们new 一个HashSet实例时,

前端面试中常见的几个问题

在前端很少有机会接触到算法,大多都交互性的操作,然而从各大公司面试来看,算法依旧是考察的一方面.下面这篇文章就给大家总结了在前端JS面试中常见的测试题问题,有需要的朋友们可以参考借鉴,下面来一起看看吧. 学习数据结构与算法对于工程师去理解和分析问题都是有帮助的.如果将来当我们面对较为复杂的问题,这些基础知识的积累可以帮助我们更好的优化解决思路.下面罗列在前端面试中经常撞见的几个问题吧.image.png1.介绍js的基本数据类型 Undefined.Null.Boolean.Number.Str

竞赛中常见的数据结构

这篇文章结合15pku暑期training的资料,简单介绍几种竞赛中常见的数据结构,包括线段树.树状数组.伸展树.后缀数组.并查集等. 需要点明的这,这个专栏的文章可以视作一个“预处理”,是作为笔者16年暑期pku集训的一个先导,因此拘于时间和精力很多知识点都是从整体上把握,缺少细节缺少证明也缺少代码实现,这些东西笔者会在以后的训练中详细的介绍出来. 线段树: 对于细胞和人口的指数级爆炸我们都很熟悉,而在算法设计当中,最忌讳的也是出现O(2^n)的时间复杂度,我们将这个计算过程视为正向,那么我们

一、javaSE (十七)set集合、Collection集合、针对Collection集合我们到底使用谁、在集合中常见的数据结构

1:set集合(理解) (1)Set集合的特点 无序,唯一 (2) Hashset集合(掌握) A:底层数据结构是哈希表(是一个元素为链表的数组) B:哈希表底层依赖两个方法: hashCode()和equals() 执行顺序 首先比较哈希值是否相同 相同:继续执行equals()方法 返回true:元素重复了,不添加 返回fa1se:直接把元素添加到集合 不同:就直接把元素添加到集合 C:如何保证元素唯一性的呢? 由 hashcode()和equals()保证的 D:开发的时候,代码非常的简单