背景
数据结构与算法 每天都会更新完善。
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)的代码有双层循环,以此类推。