JavaScript中的数据结构与算法

252 阅读11分钟

背景

数据结构与算法 每天都会更新完善。

ECMAScript

将信息标准化的组织

数组

最基本最简单的数据结构

遵从先进后出LIFO的有序集合。

创建一个基于数组的栈

1.创建一个类来表示栈

class Stack{
    constructor(){
        this.items=[]
    }
}

2.为栈声明一些方法 都是基于数组的api

push
pop
peek
isEmpty
clear
size

队列

FIFO

创建

  • 创建一个类来表示队列
  • 声明一些队列可用的方法
  • 使用Queue类

双端队列

把栈和队列相结合的数据结构

创建

  • 声明Deque类以及构造函数
  • 声明属性和方法
  • 实例化使用

链表

链表存储有序的元素集合,不同于数组,链表中的元素在内存中不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(指针或链接)组成。对于传统数组,链表的优点在于,田间或者移除元素的时候不需要移动其他元素。

创建

  • 声明LinkedList class
  • 声明属性和方法

双向链表

在链表中,一个节点只有链向下一个节点的链接,双向链表中,一个链向下一个元素,另一个链向前一个元素。

创建

  • 实现class DoublyLinkedList
  • 声明方法

循环链表

既可以像链表一样有单向引用也可以像双向链表一样有双向引用。

创建

  • 实现 class CircularLinkedList
  • 声明方法

有序链表

保持元素有序的链表结构。

创建

  • 声明 class SortedLinkedList
  • 实现方法

小结

链表以及变体:双向链表、循环链表、有序链表。


集合

不允许值重复的顺序数据结构。

创建

  • 实现class Set
  • 实现方法
  • 其他运算 交集并集补集子集

小结

存放唯一的值

字典和散列表

在字典中使用[key,value]存储数据,在散列表中也是一样,但是在字典中的每个key只能有一个value。

字典

字典也称作映射、符号表或关联数组。常用来保存对象的引用地址。

创建

  • 使用ES6中Map类为基础实现字典
  • 实现方法

散列表

散列函数的作用是给定一个key,然后返回value在表中的地址。

创建

  • 使用关联数组(对象)来表示数据结构
  • 实现方法

小结

WeakMap WeakSet 二者没有keys entries values方法 只能用对象作为key(使用WeakMap封装私有属性)

递归

简单方便解决问题。

非顺序数据结构。

二叉树和二叉搜索树 BST

二叉树中的节点最多只能有两个子节点:一个是左侧子节点,一个是右侧子节点。

二叉搜索树 :左小右大

创建二叉搜索树

  • 创建class BinarySearchTree
  • 实现方法

树的遍历

  • 中序遍历

从最小到最大的顺序访问所有的节点

  • 线序遍历

以优先于后代节点的顺序访问每个节点

  • 后序遍历

先访问节点的后代节点,在访问节点本身

自平衡树 AVL(Adelson-Velskii-Landi)

添加或一处节点时,AVL树会尝试保持自平衡

红黑树

自平衡二叉搜索树,包含多次插入和删除的自平衡树。若插入和删除的频率较低,更需要多次进行搜索时,AVL树比红黑树更好。

规则

  • 树的根节点是黑的
  • 所有的叶节点都时黑的
  • 如果有一个节点是红的,那么它的两个子节点都是黑的
  • 不能有两个相邻的红节点,一个红节点不能有红的父或子节点

创建

  • 声明class RedBlackTree
  • 实现方法

二叉堆

特殊的二叉树,高效快速的找出最大值和最小值,应用于优先队列。

二叉堆数据结构

是一颗完全二叉树,树的每一层都有左侧和右侧子节点(除最后以鞥的叶节点),且最后一层的叶节点尽可能都时左侧子节点,这叫作结构特性。 二叉堆只有两种最大堆和最小堆,快速找出最大值和最小值。

创建最小堆类

  • 声明MinHeap
  • 二叉树的数组表示

创建最大堆类

  • 声明MaxHeap
  • 实现方法

非线性数据结构,网络结构的抽象模型,一组由边连接的节点或顶点。

相关术语

  • G=VE V:一组顶点 E:一组边
  • 有环 无环
  • 有向图和无向图
  • 加权 未加权

图的表示

  • 邻接矩阵
  • 邻接表
  • 关联矩阵

创建

  • 声明class Graph
  • 实现方法

图的遍历

追踪每个第一次访问的节点,追踪未探索的节点。

广度优先搜索(breadth-first search,BFS)

将顶点存入队列,最先入队列的顶点先被探索。先宽后深的访问顶点

实现

  • 创建队列Q

  • 标注v为被发现的(灰色),并将v入队列Q

  • 如果Q非空,运行以下步骤:

    a.将u从Q中出队列

    b.标注u为被发现的(灰色)

    c.将u所有未被访问过的邻点(白色)入队列

    d.标注u为已被探索的(黑色)

使用

  • 寻找最短路径

深度优先搜索(depth-first search,DFS)

将顶点存入栈,顶点沿着路径被探索,存在新的相邻顶点就去访问。先深度后广度。

实现

  • 标注v为被发现的(灰色)
  • 对于v的所有未访问(白色)的邻点w,访问顶点w
  • 标注v未已被探索的(黑色)

使用

  • 拓扑排序

最短路径算法

eg:需求两地之间最短的路径

实现

Dijkstra 算法

该算法是一种计算从单个源到所有其他源的最短路径的贪心算法,计算从图的一个顶点到其余各顶点的最短路径。

Floyd-Warshall算法

计算图中所有最短路径的动态规划算法。可以找出从所有源到所有顶点的最短路径。

最小生成树(MST)

eg:n个岛屿之间建造桥梁,用最低成本实现所有岛屿互通。

实现

Prim算法

求解加权无向连通图的MST问题的贪心算法。找出一个边的子集,使得其他构成的树包含图中所有顶点,且边的权值之和最小。

Kruskal算法

和prim算法类似,Kruskal算法也是一种求加权无向连通图的MST的贪心算法。


排序和搜索算法

排序算法

冒泡 选择 插入 希尔 归并 快速 计数 桶 基数 堆

冒泡排序

比较所有相邻的两个项,如果第一个比第二个大,则交换。

特点

  • 优点 简单
  • 缺点 运行时间最长
  • 复杂度

实现

//function index.js
const Compare = {
LESS_THAN: -1,
BIGGER_THAN: 1,
}
function defaultCompare(a, b) {
    if (a === b) {
        return 0
    }
    return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN
}
function swap(array, a, b) {
    const temp = array[a]
    array[a] = array[b]
    array[b] = temp
}

const ARRAY = [1, 2312, 123, 23, 1232]
export { Compare, defaultCompare, swap, ARRAY }

// bubbleSort.js
import { Compare, defaultCompare, swap, ARRAY } from '../function/index.js'
function bubbleSort(array = ARRAY, compareFn = defaultCompare) {
const { length } = array
for (let i = 0; i < length; i++) {
    for (let j = 0; j < length - 1; j++) {
        if (compareFn(array[j], array[j + 1]) === Compare.BIGGER_THAN) {
            swap(array, j, j + 1)
        }
    }
}
return array
}

时间

exited with code=0 in 0.047 seconds


选择排序

找到数据结构中最小的值放在第一位,第二小的放在第二位,依次类推。

特点

实现

插入排序

每次排一个数组项,以此方式构建最后的排序数组。

归并排序

分而治之算法,将原始数组切分成较小的数组,直到每个小数组只有一个位置,然后将小数组归并成较大的数组,直到最后只有一个排序完毕的大数组。

快速排序

分而治之,性能比其他复杂度为O(nlog(n))的排序算法好。

计数排序

桶排序

基数排序

分布式排序算法,

搜索算法

顺序搜索

顺序/线性搜索是最基本的搜索算法。机制是,将每一个数据结构中的元素和我们要找的元素作比较。

实现

二分搜索

其原理和猜数字游戏类似

实现

  • 选择数组的中间值
  • 如果选中值是待搜索值,执行完毕
  • 如果待搜索的值比选中的小,则返回步骤1并再选中值左边的子数组中寻找(较小)
  • 如果待搜索的值比选中的大,则返回步骤1并再选中值左边的子数组中寻找(较大)

内插搜索

改良版的二分搜索。二分搜索总是检查mid位置上的值,而内插搜索可能会根据要搜索的值检查数组中的不同地方。

实现

  • 使用poistion选中一个值
  • 如果该值是待搜索值,那么执行完毕
  • 如果待搜索的值比选中的小,则返回步骤1并再选中值左边的子数组中寻找(较小)
  • 如果待搜索的值比选中的大,则返回步骤1并再选中值左边的子数组中寻找(较大)

随机算法

数组中的值进行随机排列

实现

  • Fisher-Yates 随机

迭代数组,从最后一位开始并将当前位置和一个随机位置交换。这个随机位置比当前位置小。这样子这个算法可以保证随机跳过的位置不会再被随即一次。


算法设计与技巧

分而治之算法 动态规划 贪心算法 回溯算法 著名算法问题

分而治之

实现步骤

  • 分解原问题为多个子问题
  • 解决子问题,用返回解决子问题的方式递归算法
  • 组合这些子问题的解决方式,得到原问题的解

实现

  • 二分搜索

动态规划

将复杂问题分解成更小的子问题来解决的优化技术。

解决步骤

  • 定义子问题
  • 实现要反复执行来解决子问题的部分
  • 识别并请求解出基线条件

解决的问题

  • 背包问题

给出一组项,各自有值喝容量,目标是找出总值的最大的项的集合。

  • 最长公共子序列

找出一组序列的最长公共子序列。

  • 矩阵链相乘

给出一系列矩阵,目标是找到这些矩阵相乘的最高效办法。

  • 硬币找零/最少硬币找零

给出面额为d1,...dn的一定数量的硬币和要找零的钱数,找出有多少种找零的方法。

  • 图的全源最短路径

对所有顶点对(u,v),找出从顶点u到顶点v的最短路径。

贪心算法

遵循一种近似解决问题的技术,期盼通过每个阶段的局部最优选择,从而到达全局的最优。

解决的问题

  • 最少硬币找零问题
  • 分数背包问题

回溯算法

渐进式寻找并构建问题解决方式的策略。从一个可能的动作开始并试着用这个动作解决问题。如果不能解决,就回溯并选择另一个动作直到将问题解决。根据这种行为,回溯算法会尝试所有可能的动作(如果更快找到了解决办法就尝试较少的次数)来解决问题。

解决的问题

  • 骑士巡逻问题
  • N皇后问题
  • 迷宫老鼠问题
  • 数独解题器

函数式编程简介

函数式编程与命令行编程

  • 函数式编程的主要目标是描述数据,以及要对数据应用的转换。
  • 在函数式编程中,程序执行顺序的重要性很低;而在命令式编程中,步骤和顺序是非常重要的。
  • 函数和数据集合是函数式编程的核心。
  • 函数式编程中用函数和递归;命令式编程中使用循环,赋值、条件和函数。
  • 在函数式编程中,要避免副作用和可变数据,不会修改传入函数的数据。

ES6和函数式编程

结合一些ES6新的API让函数式编程更简单

JavaScript函数式工具箱-map、filter和reduce


算法复杂度

大O表示法和NP完全理论

大O表示法

描述算法的性能和复杂度。大O表示法将算法按照消耗的时间进行分类,依据随输入增大所需要的空间/内存,常遇到的几类函数如下:

  • O(1)
  • O(log(n))
  • O(log(n)c)
  • O(n)
  • O(n^2)
  • O(n^c)
  • O(c^n)

理解大O表示法

如何衡量算法的效率?用资源的占用。如:CPU(时间)占用、内存占用、硬盘占用和网络占用。当讨论大O表示法时,一般考虑的CPU(时间)占用。

时间复杂度比较

时间复杂度O(n)的代码只有一层循环,而O(n^2)的代码有双层循环,以此类推。