vlambda博客
学习文章列表

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)

本文专门为前端工程师准备, 从javascript出发分析数据结构与算法,栈,队列,链表,集合,字典,树,图,堆,以及对应的leetcode题的练习,后续将不断更新相关内容

栈的简介

  • • 一个先进后出的数据结构

  • • js中没有栈,可以用Array实现对栈的所有功能

$zUGHvrxkNQi#TCpwMKq#g==.png
const stack = []
// 进栈
stack.push(1)
stack.push(2)
// 出栈
const item1 = stack.pop()
const item2 = stack.pop()

什么时候用

  1. 1. js中基本数据类型存储栈内存中

  2. 2. js执行时有执行栈,事件循环中将以次执行执行栈中的回调

    队列

    队列简介

  • • 一个先进先出的数据结构

  • • js中没有队列,但是可以用Array实现对队列的所有功能

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
image.png

使用数组模拟先进先出的场景

const queue = [] 
// 进队列
queue.push(1)
queue.push(2)

// 出队列
const itme1 = queue.shift()
const itme2 = queue.shift()

什么时候用

  1. 1. 食堂排队打饭

  2. 2. 所有先进先出的场景

  3. 3. js 异步中的任务队列 一个leetcode题 第933题

链表

链表是什么

  • • 多个元素组成的列表

  • • 匀速存储不连续, 用next指针连在一起

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
image.png

数组 vs 链表

  • • 数组:增删非收尾元素是往往需要移动元素

  • • 链表:增删非收尾元素,不需要移动元素,只需要更改next指针即可

js中的链表

  • • js没有链表的数据结构

  • • 可以用Object模拟链表 ```js

const a = { val: 'a' } const b = { val: 'b' } const c = { val: 'c' } const d = { val: 'd' } a.next = b b.next = c c.next = d

// 遍历 let point = a while (point) { console.log(point.val) point = point.next }

// 插入 (c-d)中插入d

const e = {val:'e'} c.next = e e.next = d

// 删除 (删除e) c.next = d

leetcode练习:第83题-删除链表重复元素
```js
var deleteDuplicates = function(head) {
  // 定义链表的一个头部的指针
  let p = head
  while(p && p.next) {
      if(p.val === p.next.val) {
        // 删除链表的一项
          p.next = p.next.next
      }else {
      // 不相同的时候再移动指针
           p = p.next
      }
  }
  return head
 
};
  • • 前端原型链的链表 JavaScript 一文搞定理解原型与原型链 手写一个instanceof ```js function myInstanceof (A, B) { // 遍历链表 let p = A while (p) { p = p.proto // B的 prototype 属性是否出现在A实例对象的原型链上 if (p === B.prototype) { return true }} return false

} function Foo () {} var f = new Foo() console.log(myInstanceof(f, Foo)); // true console.log(myInstanceof(f, Object)); // true


# 集合
## 集合简介
+ 一种无序且唯一的数据结构
+ ES6中有集合, 名为Set
+ 集合常用操作: 去重,判断某元素是否在集合中,求交集

```js
// 去重
const arr = [1,1,2,3,4,3]
const arr2 = [...new Set(arr)]

// 判断元素是否在集合中
let set = new Set(arr)

// add 方法
set.add(1)
set.add('text')
set.add({a:1,b:2})

// has方法
const has =set.has(3)

// delete方法
set.delete(1)

// 获取size 方法
console.log(set.size)
// 求交集
const set2 = new Set([2,3])
const set3 new Set([...set]).filter(item => set2.has(item))

// 求差集
const set2 = new Set([2,3])
const set4 = new Set([...set]).filter(item => !set2.has(item))

// 数组转为set
set2 = new Set([1,2,3])

// 迭代方法 fot ..of
for (let item of set) console.log(item)
for (let item of set.keys())) console.log(item)
for (let item of set.values()) console.log(item)
for (let item of set.entrise()) console.log(item)

补充说明迭代 内置迭代器:

可迭代的对象,都内置以下3种迭代器

entries(): 返回一个迭代器,值为键值对

values(): 返回一个迭代器, 值为集合的值

keys(): 返回一个迭代器,值为集合中的所有键

let userList = [ 'ghostwu', '悟空', '八戒' ];

for ( let name of userList.entries() ) {
    console.log( name );
}

let set = new Set( [ 10, 20, 30 ] );
for ( let num of set.entries() ){
    console.log( num );
}

let map = new Map( [ [ 'name', 'ghostwu' ], [ 'age', 22 ] ] );
for ( let detail of map.entries() ){
    console.log( detail );
}
一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
y2lP#f9L1r0ZWXu#82u90g==.png

字典

字典简介

  • • 与集合相似, 字典也是一种存储为一值的数据结构, 但他是以键值对的形式存储

  • • ES6中有字典-->Map(映射)

  • • 常见操作 增(set) 删(delete) 改(set) 查(get)

const m = new Map()

//增
m.set('a','aaa')

// 删
m.delete('a')
m.clear()

// 改
m.set('a','aaaaa')

// 查
m.get('a')

使用Map取两个数组的交集

var intersection = function(nums1, nums2) {
  // new Set(nums1) 去重
  return  [...new Set(nums1)].filter(item => nums2.includes(item))
};

树简介

  • • 一种分层数据的抽象模型

  • • 前端工作中常见的树包括:DOM树,级联选择,树形控件....

  • • js中没有树,但是可以用Array 和Object构建树

  • • 树的常用操作: 深度/广度优先遍历 , 先中后序遍历

几个概念:

  • • 拥有相同父节点的节点,互称为兄弟节点

  • • 节点的深度: 从根节点到该节点所经历的边的个数

  • • 节点的高度: 节点到叶节点的最长路径

注意点:

  • • 仅有唯一一个根节点,没有节点则为空树

  • • 除根节点外,每个节点都有并仅有唯一一个父节点

  • • 节点间不能形成闭环

树的深度/广度优先遍历

  • • 深度优先遍历:尽可能深的搜索树的分支:递归

    1. 1. 访问根节点

    2. 2. 对根节点的children挨个进行深度优先遍历

      一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
      impicture_20210925_155733.png
const tree = {
  val: 'a',
  children: [
    {
      val: 'b',
      children: [
        {
          val: 'd',
          children: [
            
          ]
        },
        {
          val: 'e',
          children: [
            
          ]
        }
      ]
    },
    {
      val: 'c',
      children: [
        {
          val: 'f',
          children: [
            
          ]
        },
        {
          val: 'g',
          children: [
            
          ]
        }
      ]
    }
  ]
}
const dfs =(root) => {
  console.log(root.val)
  root.children.forEach(dfs)
}

dfs(tree)

打印结果

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210925_161217.png
  • • 广度优先遍历:先访问离根节点最近的节点

    1. 1. 新建一个队列, 把根节点入队

    2. 2. 把对头出队并访问

    3. 3. 把对头的children挨个入队

    4. 4. 重复第二,第三,直到队列为空

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210925_155839.png


const bfc = (root) => {
  const q = [root]
  while (q.length > 0) {
    const n = q.shift()
    console.log(n.val)
    n.children.forEach(child => {
      q.push(child)
    })
  }
}

打印结果:

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210925_162636.png

二叉树的先中后序遍历(深度优先)

二叉树是什么?

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210925_162952.png
  • • 树中每个节点最多只能有两个子节点

  • • 在js中通常用Object来模拟二叉树const binaryTree = {
    val: 1,
    left: {
      val:2,
      left: null,
      right: null
    },
    right: {
      val:3,
      left: null,
      right: null
    }
    }

先序遍历算法

  1. 1. 访问节点

  2. 2. 对根节点的子树进行先序遍历

  3. 3. 对根节点的子树进行先序遍历

遍历顺序如图

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210925_171603.png

定义一棵树


const binaryTree = {
 val: 1,
 left: {
   val: 2,
   left: {
     val: 4,
     left: null,
     right: null,
   },
   right: {
     val: 5,
     left: {
       val: 7,
       left: null,
       right: null,
     },
     right: null,
   },
 },
 right: {
   val: 3,
   left: null,
   right: {
     val: 6,
     left: null,
     right: null,
   },
 },
};

递归版:

const preorder = root => {
  if (!root) return;
  console.log(root.val);
  preorder(root.left);
  preorder(root.right);
};
preorder(binaryTree);

非递归(栈特性):

const preorder = root => {
  if (!root) return;
  const stack = [root];
  while (stack.length) {
    const n = stack.pop();
    console.log(n.val);
    n.right && stack.push(n.right);
    n.left && stack.push(n.left);
  }
};
preorder(binaryTree);

打印结果:

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210925_192938.png

中序遍历算法

  1. 1. 对根节点的子树进行中序遍历

  2. 2. 访问接节点

  3. 3. 对根节点的子树进行中序遍历

遍历顺序如图

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210925_191542.png

还是使用binaryTree这个树 递归版实现:

const inorder = root => {
  if(!root) return 
  inorder(root.left)
  console.log(root.val)
  inorder(root.right)
}
inorder(binaryTree)

非递归版实现:

const inorder = root => {
  if (!root) return;
  const stack = [];
  let p = root;
  while (stack.length || p) {
    while (p) {
      stack.push(p);
      p = p.left;
    }
    const n = stack.pop();
    console.log(n.val);
    p = n.right;
  }
}
inorder(binaryTree)

打印结果:

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210925_193158.png

后序遍历算法

  1. 1. 对根节点的子树进行中序遍历

  2. 2. 对根节点的子树进行中序遍历

  3. 3. 访问接节点

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210925_193507.png

还是使用binaryTree这个树 递归版实现:

const postorder = (root) => {
  if (!root) return;
  postorder(root.left);
  postorder(root.right);
  console.log(root.val);
};
postorder(binaryTree);

非递归版实现:

const inorder = root => {
  if (!root) return;
  const outputStack = [];
  const stack = [root];
  while (stack.length) {
    const n = stack.pop();
    outputStack.push(n);
    if (n.left) stack.push(n.left);
    if (n.right) stack.push(n.right);
  }
  while (outputStack.length) {
    const n = outputStack.pop();
    console.log(n.val);
  }
}
inorder(binaryTree)

打印结果:

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20211011_101839.png

前端与树

遍历JSON的所有节点值

使用深度优先遍历

const json = {
  a: { b: { c: 1 } },
  d: [1, 2],
};
// 深度优先遍历
const dfs = (n, path) => {
  console.log(n, path);
  Object.keys(n).forEach((k) => {
    dfs(n[k], path.concat(k));
  });
};
dfs(json, []);

打印结果

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
8#N46LVi5WYavPpD0kJxEA==.png

图是什么

  • • 图是网络结构的抽象模型, 是一组由边连接的节点

  • • 图可以表示任何二元关系, 比如路,航班

  • • js没有图, 可以用Array Object模拟

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
eCbZ97wow3zli41Hgl62LA==.png

图的表示法

邻接矩阵

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
Efua5ZlxkTcN1VY$4z3r1g==.png
一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
m3#LjYpWKEuLT8mWVYd#Lg==.png

邻接表

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
XVIV5XK$ps1xMTOcJCv9$Q==.png
一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
yMJh03aIcxtiz2ymXyoyiA==.png

图的遍历

  • • 深度优先遍历: 尽可能深的搜索图的分支

  1. 1. 访问根节点

  2. 2. 对根节点的没访问过得相邻节点挨个进行深度优先遍历

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
7ThsFuwR4YTamHXonaJgZw==.png

定义一个图

const graph = {
  0:[1,2],
  1:[2],
  2:[0,3],
  3:[3]
}

使用深度优先遍历


const visited = new Set()
const dfs = n => {
  console.log(n)
  visited.add(n)
  graph[n].forEach(c => {
    if(!visited.has(c)) {
      dfs(c)
    }
  })
}
dfs(2)

打印结果

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210928_192607.png
  • • 广度优先遍历: 先访问离根节点最近的节点

  1. 1. 新建一个队列, 把根节点入队

  2. 2. 把队头出队并访问

  3. 3. 把队头的没访问过得相邻节点入队

  4. 4. 重复第二 三步, 直到队列为空

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210928_192719.png
const bfs = node => {
  const visited = new Set()
  visited.add(node)
  const q = [node]
  while (q.length) {
    const n = q.shift()
    console.log(n)
    graph[n].forEach(c => {
      if(!visited.has(c)) {
        q.push(c)
        visited.add(c)
      }
    })
  }
}
bfs(2)

打印结果:

一文搞定js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)
impicture_20210928_195138.png

堆是什么

  • • 堆是一种特殊的完全二叉树 完全二叉树如图:

    5SDCt6K6Cahj5FnPmtHr8w==.png
  • • 所有的节点都大于等于(最大堆)或小于等于(最小堆)他的节点

  • • js中通常用数组表述堆

  • • 左侧子节点的位置是2 * index + 1

  • • 右侧子节点的位置是2 * index + 2

  • • 父节点的位置是(index - 1) / 2

rxkochTNbMbEkci4KeUmLQ==.png

堆的应用

  • • 对能高效快速地找出最大值和最小值, 时间复杂度:O(1)

  • • 找出第K个最大(小)元素

第K个最大元素

  1. 1. 构建一个最小堆, 并将元素依次插入堆中

  2. 2. 当堆的容量超过K, 就是删除堆顶

  3. 3. 插入结束后, 堆顶就是第K个最大元素

js实现最小堆类

  • • 构建一个类

  1. 1. 在类里, 声明一个数组, 用来装元素

  2. 2. 主要方法: 插入, 删除堆顶, 获取堆顶, 获取堆大小

  • • 插入

  1. 1. 将值插入堆的底部,即数组的尾部

  2. 2. 然后上移: 将这个值和它的防父节点进行交换, 直到父节点小于等于这个插入的值

  3. 3. 大小为k的堆中插入元素的时间复杂度为O(logk)

  • • 删除堆顶

  1. 1. 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)

  2. 2. 然后下移: 将新堆顶和它的子节点进行交换, 直到子节点大于等于这个新堆顶

  3. 3. 大小为k的堆中删除堆顶得时间复杂度为O(logk)