XingYun blog
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具

XingYun

冲!
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具
  • 算法图解阅读笔记
    • MD5算法
    • JS实现常见排序
    • 邓俊辉算法与数据结构
    • JS实现队列
    • JS实现树的几种遍历方式
    • 二叉树相关
    • 哈希算法
    • 进制转换与js实现
    • JS实现链表
    • JS实现栈与栈应用
    • 算法与数据结构
    XingYun
    2022-03-08
    目录

    算法图解阅读笔记

    # 比较数组和链表

    链表存在一个问题。在需要读取链表的最后一个元素时,你不能直接读取,因为你不知道 它所处的地址,必须先访问元素#1,从中获取元素#2 的地址,再访问元素#2 并从中获取元素#3 的地址,以此类推,直到访问最后一个元素。需要同时读取所有元素时,链表的效率很高:你读 取第一个元素,根据其中的地址再读取第二个元素,以此类推。但如果你需要跳跃,链表的效率真的很低

    数组与此不同:你知道其中每个元素的地址。例如,假设有一个数组,它包含五个元素,起 始地址为 00,那么元素05 的地址是多少呢?

    只需执行简单的数学运算就知道:04。需要随机地读取元素时,数组的效率很高,因为可迅速找到数组的任何元素。在链表中,元素并非靠在一起的,你无法迅速计算出第五个元素的内存地址,而必须先访问第一个元素以获取第二个元素的地址,再访问第二个元素以获取第三个元素 的地址,以此类推,直到访问第五个元素。

    下面列出了常见的数组和链表操作的运行时间。

    使用场景: 数组用得很多,因为它支持随机访问。有两种访问方式:随机访问和顺序访问。顺序访问意味着从第一个元素开始逐个地读取元素。链表只能顺序访问:要读取链表的第十个元素,得先读取前九个元素,并沿链接找到第十个元素。随机 10 访问意味着可直接跳到第十个元素。

    # 对递归的理解

    Leigh Caldwell 在 Stack Overflow 上说的一句话:“如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解。如何选择要看什么对你来说更重要。”

    # 平均情况和最糟情况

    快速排序的性能高度依赖于你选择的基准值。假设你总是将第一个元素用作基准值,且要处理的数组是有序的。由于快速排序算法不检查输入数组是否有序,因此它依然尝试对其进行排序。

    注意,数组并没有被分成两半,相反,其中一个子数组始终为空,这导致调用栈非常长。现 在假设你总是将中间的元素用作基准值,在这种情况下,调用栈如下。

    调用栈短得多!因为你每次都将数组分成两半,所以不需要那么多递归调用。你很快就到达 了基线条件,因此调用栈短得多。

    第一个示例展示的是最糟情况,而第二个示例展示的是最佳情况。

    在最糟情况下,栈长为 O(n),而在最佳情况下,栈长为 O(log n)。

    现在来看看栈的第一层。你将一个元素用作基准值,并将其他的元素划分到两个子数组中。 这涉及数组中的全部 8 个元素,因此该操作的时间为 O(n)。在调用栈的第一层,涉及全部 8 个元素, 但实际上,在调用栈的每层都涉及 O(n)个元素。

    即便以不同的方式划分数组,每次也将涉及 O(n)个元素。

    因此,完成每层所需的时间都为 O(n)。

    在这个示例中,层数为 O(log n)(用技术术语说,调用栈的高度为 O(log n)),而每层需要的 时间为 O(n)。因此整个算法需要的时间为 O(n) _ O(log n) = O(n log n)。这就是最佳情况。
    在最糟情况下,有 O(n)层,因此该算法的运行时间为 O(n) _ O(n) = O(n2)。

    知道吗?这里要告诉你的是,最佳情况也是平均情况。只要你每次都随机地选择一个数组元 素作为基准值,快速排序的平均运行时间就将为 O(n log n)。

    快速排序是最快的排序算法之一,也 是 分而治之(divide and conquer,D&C) 典范。

    # 散列冲突

    大多数语言都提供了散列表实现,你不用知道如何实现它们。有鉴于此,我就不 再过多地讨论散列表的内部原理,但你依然需要考虑性能!要明白散列表的性能,你得先搞清楚 什么是冲突。

    你可能已经看出了问题。如果你要将苹果的价格存储到散列表中,分配给你的是第一个位置。

    接下来,你要将香蕉的价格存储到散列表中,分配给你的是第二个位置。

    不好,这个位置已经存储了苹果的价格! 怎么办? 这种情况被称为冲突(collision):给两个键分配的位置相同。这是个问题。如果你将鳄梨的价格存储到这个位置,将覆盖苹果的价格, 以后再查询苹果的价格时,得到的将是鳄梨的价格!冲突很糟糕,必须要避免。处理冲突的方式 很多,最简单的办法如下:如果两个键映射到了同一个位置,就在这个位置存储一个链表。

    在这个例子中,apple 和 avocado 映射到了同一个位置,因此在这个位置存储一个链表。在需 要查询香蕉的价格时,速度依然很快。但在需要查询苹果的价格时,速度要慢些:你必须在相应 的链表中找到 apple。如果这个链表很短,也没什么大不了——只需搜索三四个元素。但是,假设你工作的杂货店只销售名称以字母 A 打头的商品。

    等等!除第一个位置外,整个散列表都是空的,而第一个位置包含一个很长的列表!换言之, 这个散列表中的所有元素都在这个链表中,这与一开始就将所有元素存储到一个链表中一样糟 糕:散列表的速度会很慢。

    这里的经验教训有两个。

    • 散列函数很重要。前面的散列函数将所有的键都映射到一个位置,而最理想的情况是, 散列函数将键均匀地映射到散列表的不同位置。
    • 如果散列表存储的链表很长,散列表的速度将急剧下降。然而,如果使用的散列函数很好,这些链表就不会很长!

    在最糟情况下,散列表所有操作的运行时间都为 O(n)——线性时间,这真的很慢。我们来将 散列表同数组和链表比较一下。

    在平均情况下,散列表的查找(获取给定索引处的值)速度与数组一样快,而插入和删除速度与链表一样快,因此它兼具两者的优点! 但在最糟情况下,散列表的各种操作的速度都很慢。 因此,在使用散列表时,避开最糟情况至关重要。为此,需要避免冲突。

    # 填装因子

    散列表的填装因子很容易计算。

    填装因子越低,发生冲突的可能性越小,散列表的性能越高。一个不错的经验规则是:一旦填装因子大于 0.7,就调整散列表的长度。

    你可能在想,调整散列表长度的工作需要很长时间!你说得没错,调整长度的开销很大,因 此你不会希望频繁地这样做。但平均而言,即便考虑到调整长度所需的时间,散列表操作所需的 时间也为 O(1)。

    # NP完全问题

    NP完全问题的简单定义是,以难解著称的问题,如旅行商问题和集合覆盖问题。很多非常 聪明的人都认为,根本不可能编写出可快速解决这些问题的算法。

    NP完全问题无处不在!如果能够判断出要解决的问题属于NP完全问题就好了,这样就不用 去寻找完美的解决方案,而是使用近似算法即可。但要判断问题是不是NP完全问题很难,易于 解决的问题和NP完全问题的差别通常很小。

    例如,如何找 出从A点到B点的最短路径。

    但如果要找出经由指定几个点的的最短路径,就是旅行商问题——NP完全问题。简言之, 没办法判断问题是不是NP完全问题,但还是有一些蛛丝马迹可循的。

    • 元素较少时算法的运行速度非常快,但随着元素数量的增加,速度会变得非常慢。
    • 涉及“所有组合”的问题通常是NP完全问题。
    • 不能将问题分成小问题,必须考虑各种可能的情况。这可能是NP完全问题。
    • 如果问题涉及序列(如旅行商问题中的城市序列)且难以解决,它可能就是NP完全问题。 - 如果问题涉及集合(如广播台集合)且难以解决,它可能就是NP完全问题。
    • 如果问题可转换为集合覆盖问题或旅行商问题,那它肯定是NP完全问题。

    # 动态规划局限

    功能强大,它能够解决子问题并使用这些答案来解决大问题。但仅当 每个子问题都是离散的,即不依赖于其他子问题时,动态规划才管用。

    整本书比较比较浅显, 算是算法入门导论风格的科普书

    #算法#读书
    上次更新: 2023/04/05, 09:41:10
    MD5算法

    MD5算法→

    最近更新
    01
    JavaScript-test
    07-20
    02
    二维码的原理
    07-20
    03
    利用ChatGPT优化代码
    07-20
    更多文章>
    Theme by Vdoing | Copyright © 2021-2023 XingYun | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式