常用数据结构

47 阅读1分钟

数组

建议这么创建数组

const arr = (new Array(7)).fill(1)

遍历数组方法:

  • for
  • forEach
  • map
const len = arr.length
for(let i=0;i<len;i++) { }

此外还有二维数组也叫矩阵

可以这么初始化二维数组

const len = arr.length
for(let i=0;i<len;i++) {
  arr[i] = []
}

栈和队列

JS 中都依靠数组来实现

首先需要了解数组中增加元素的三种方法

  • push 添加到尾部
  • unshift 添加到头部
  • splice 添加到任意位置

还有数组删除元素的三种方法

  • shift 删除头部
  • pop 删除尾部
  • splice 删除任意位置

栈指的是后进先出的数据结构

// 遍历一个栈
while(stack.length) {
    // 单纯访问栈顶元素(不出栈)
    const top = stack[stack.length-1]
    // 将栈顶元素出栈
    stack.pop()
}

队列是先进先出

// 遍历一个队列
while(queue.length) {
    // 单纯访问队头元素(不出队)
    const top = queue[0]
    // 将队头元素出队
    queue.shift()
}

链表

与数组的区别是每个节点在内存中可以是离散的

{
  // 数据域
  val: 1,
  // 指针域,指向下一个结点
  next: {
    val:2,
    next: ...
  }
}  

初始化一个链表,需要一个构造函数

function ListNode(val) {
  this.val = val;
  this.next = null;
}
const node = new ListNode(1)

在两个结点间添加元素,假设添加如下结点

const node3 = new ListNode(3)    

把node3的 next 指针指向 node2(即 node1.next),再把node1的 next 指针指向 node3

node3.next = node1.next
node1.next = node3

那么如何删除一个元素呢,还是以删除 node3 为例

node1.next = node3.next

可以看到关键在于定位 node3 的前驱结点,所以只需要如下操作

const target = node1.next // node3
node1.next = target.next

链表和数组的比较

  • 增删:链表 - O(1)、数组 - O(n),添加和删除元素都不需要挪动多余的元素
  • 访问:链表 - O(n)、数组 - O(1)

原本数组删除或增加元素的复杂度应该是O(n)

但 JS 中不一定是。 JS比较特别。如果我们在一个数组中只定义了一种类型的元素,比如:

const arr = [1,2,3,4]

它是一个纯数字数组,那么对应的确实是连续内存。 但如果我们定义了不同类型的元素:

const arr = ['haha', 1, {a:1}]

它对应的就是一段非连续的内存。此时,JS 数组不再具有数组的特征,其底层使用哈希映射分配内存空间,是由对象链表来实现的。

树与二叉树

首先需要了解树的关键特性和重点概念

  • “度”的概念:一个结点开叉出去多少个子树,被记为结点的“度”。
  • “叶子结点”:叶子结点就是度为0的结点。
  • 树的层次计算规则:根结点所在的那一层记为第一层,其子结点所在的就是第二层,以此类推。
  • 结点和树的“高度”计算规则:叶子结点高度记为1,每向上一层高度就加1,逐层向上累加至目标结点时,所得到的的值就是目标结点的高度。树中结点的最大高度,称为“树的高度”。

二叉树:

  • 它可以没有根结点,作为一棵空树存在
  • 如果它不是空树,那么必须由根结点、左子树和右子树组成,且左右子树都是二叉树
// 二叉树结点的构造函数
function TreeNode(val) {
    this.val = val;
    this.left = this.right = null;
}

新建二叉树结点

const node  = new TreeNode(1)

遍历一个二叉树的顺序主要有以下三种

  • 根结点 -> 左子树 -> 右子树
  • 左子树 -> 根结点 -> 右子树
  • 左子树 -> 右子树 -> 根结点

分别对应了二叉树的先序遍历、中序遍历和后序遍历规则,所谓的“先序”、“中序”和“后序”,“先”、“中”、“后”其实就是指根结点的遍历时机

先序遍历

// 所有遍历函数的入参都是树的根结点对象
function preorder(root) {
  if(!root) {
    return 
  }
  console.log('当前遍历的结点值是:', root.val)  
  preorder(root.left)  
  preorder(root.right)
}

中序遍历

function inorder(root) {
  if(!root) {
      return 
  }
  inorder(root.left)  
  console.log('当前遍历的结点值是:', root.val)  
  inorder(root.right)
}

后序遍历

function postorder(root) {
  if(!root) {
      return 
  }
  postorder(root.left)  
  postorder(root.right)
  console.log('当前遍历的结点值是:', root.val)  
}