《图解算法》总结

2023-11-18

最近快速阅读了《图解算法》这本算法的入门书,对其中的一些知识点做了总结。

  1. 使用递归函数需要确定基线条件递归条件
  2. 调用栈。在调用一个函数的时候,当前函数暂停并处于未完成状态
  3. 分而治之(D&C算法),找出基线条件,然后不断将问题分解,直到符合基线条件
  4. 快速排序比归并排序快,虽然两者都是O(n * log n) 但是快排的常量比归并排序小
  5. 散列表,也就是哈希表,根据是根据关键码值而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
  6. 广度优先搜索只能在非加权图中查找最短路径,即转折的次数最少。如果要在非负加权图中查找最短路径,可以使用狄克斯特拉算法。这是一种贪婪算法,每次都选择路径最小的点,然后更新其到邻居节点的权值和。如果图中有负权边,使用贝克曼-福德算法
  7. 贪婪算法,确实很贪婪
  8. 动态规划,最优子结构重叠子问题
  9. 分治思想和动态规划的相同点是:二者都要求原问题具有最优子结构性质,都是将原问题分而治之,分解成若干个规模较小(小到很容易解决的程序)的子问题,然后将子问题的解合并,形成原问题的解。
  10. 分治思想和动态规划的区别是:分治是将分解后的子问题看成是相互独立的,通常用递归来做;动态规划将分解后的子问题看成是具有相互联系,有重叠部分,需要记忆,通常用迭代来做。
  11. 在计算样本特征之间的差距的时候,除了使用距离公式,还可以使用余弦相似度计算样本之间的角度。
  12. 之后可以了解:树,包括B树、红黑树、堆、伸展树;并行算法,如MapReduce;加密算法;线性规划

前三章

五种常见的大O运行时间

O(log n),也叫对数时间,如二分查找
O(n),也叫线性时间,如简单查找
O(n * log n),快排
O(n*n),选择
O(n!),旅行商问题

算法的基本认识

算法的速度指的并非时间,而是操作数的增速
讨论算法的速度时,我们说的是随着输入的增加,指其运行时间将以什么样的速度增加
算法的运行时间用大O表示法表示
O(logn)比O(n)快,当需要搜索的元素越多时,前者比后者快得越多

递归和迭代的区别:

递归是在函数里面调用自己
迭代是不断调用另一个值

递归

基线条件,指的是函数何时不再调用自己,结束递归
递归条件,指的是,函数何时调用自己
递归函数如果一直运行:
    栈将不断地增大,每个程序可使用的调用栈空间有限,程序用完这些空间(终将如此)后,将因栈溢出而终止
递归指的是调用自己的函数
所有函数的调用都进入调用栈
调用栈可能很长,这将占用大量内存

调用栈(call stack)

对于这样的一个函数

def greet(name)print("hello" + name + "!")
    greet2(name)
    print("getting ready to say bye")
    bye()

def greet2(name):
    print("how are you" + name + "?")

def bye():
    print("ok bye!")
当调用greet("mary")的时候,计算机首先为该函数调用分配一块内存
使用这些内存来存储变量
当再次调用greet2("mary")的时候,计算机也为这个函数的调用分配一块内存。
计算机使用一个栈来表示这些内存块,其中第二个内存块位于第一个内存块上面,当函数调用返回的时候,栈顶的内存块弹出
当调用greet2的时候,greet只执行了一部分。调用另一个函数的时候,当前函数暂停并处于未完成状态。该函数的所有变量的值都还在内存中。
执行玩函数greet2后,回到greet函数,并从离开的地方接着向下执行。
打印完后调用函数bye
函数bye打印完毕后,又回到greet执行最后的功能
这个栈用于存储多个函数的变量,称为调用栈

分而治之(divide and conquer, D&C)

一种著名的递归式问题解决方法
不是某种解决问题的算法,而是一种解决问题的思路
解决问题的两个步骤
    找出基线条件,这种条件尽可能的简单
    不断将问题分解,或者说缩小规模,直到符合基线条件
D&C将问题逐步分解。使用D&C处理列表时,基线条件很可能是空数组或只包含一个元素的数组
大O表示法中的常量有时候事关重大,这就是快速排序比归并排序快的原因所在
比较算法快慢的时候,首先比较算法的运算级数,之后再考虑常量大小

第四章-快速排序

快速排序

过程
    选择基准值
    将数组分成两个子数组:小于基准值的元素组成的子数组和大于基准值的元素组成的子数组
    对这两个子数组进行快速排序
性能高度依赖于选择的基准值
    最坏情况下,基准值有序从小到大
    最佳情况下,基准值能将数组分成平均的两部分
在最佳情况下
    调用栈的高度为O(log n),每层涉及O(n)个元素,需要的操作时间为O(n),因此运行时间为O(log n) * O(n) = O(n * log n)
在最坏情况下
    调用栈的高度为O(n),每层涉及元素O(n),所以运行时间为O(n*n)
实现快速排序的时候,请随机地选择用作基准值的元素。

归纳证明:

就是高中时候的归纳法,证明算法成立
分为基线条件和归纳条件
基线条件,就是在问题规模为1的时候,算法成立
归纳条件,在已知问题规模为n-1的时候,证明问题规模为n的时候依然成立

第五章-散列表

散列函数

散列函数是这样的函数,即无论你给他什么数据,它都还你一个数字。将输入映射到数字
散列函数必须满足一些要求:
    它必须是一致的。例如,假设你输入apple时,得到的是4,那么每次输入apple时,得到的都必须为4,如果不是这样,那么散列表将毫无用处
    它应将不同的输入映射到不同的数字。例如,如果一个散列函数,不管输入是什么都返回1,它就不是好的散列函数。最理想的情况是,将不同的输入映射到不同的数字
散列函数能够准确指出索引位置的原因:
    散列函数总是将同样的输入映射到相同的索引。每次输入apple时,得到的都是同一个数字。因此,可以首先使用它来确定苹果的价格存储在什么地方,并在以后使用它来确定苹果的价格存储在什么地方
    散列函数将不同的输入映射到不同的索引。apple映射索引4,milk映射索引0。每种商品都映射到数组的不同位置,让你能够将其价格存储到这里
    散列函数知道数组有多大,只返回有效的索引。
可以使用散列函数和数组来创建散列表

散列表应用案例

将散列表用于查询。如DNS解析,散列表是提供这种功能的方式之一
防止重复,例如投票
缓存。将常用的网页保存在本地,是一种常见的加速方式
    用户能够更快地看到网页。
    服务器端需要做的工作更少
以上三种案例分别是:模拟映射关系;防止重复;缓存/记录数据

散列表的冲突

散列函数总是将不同的键映射到数组的不同位置。但是几乎不可能编写出这样的散列函数
如果两个键映射到同一个位置,这种情况叫冲突
处理冲突的方法:
    在冲突的位置存储一个链表
冲突的经验教训
    散列函数很重要。最理想的情况是散列函数将键均匀地映射到不同的位置
    如果散列表存储的链表很长,那么散列表的速度会急剧下降。但是如果使用的散列函数很好,这些链表就不会很长。

散列表的性能

散列表的查找、插入和删除速度都非常快
在使用散列表的时候,避开最坏情况至关重要。为了避免冲突:
    较低的填装因子。填装因子 = 散列表包含的元素书 / 位置总数。如果填装因子超过0.7,就要调整列表的长度
    良好的散列函数

第六章-广度优先搜索

广度优先搜索 breadth-first search BFS

图是由节点和边组成,一个节点可能与众多节点直接相连,这些节点称为邻居
图分为有向图和无向图。有向图的关系是单向的,即只有指向一个方向的箭头。如果两个节点互相指向,则等于无向图。
广度优先搜索可以回答两种问题
    第一类,从节点A出发,是否有前往节点B的路径
    第二类,从节点A出发,前往节点B的哪条路径最短
队列 queue 是一种先进先出的结构 first in first out FIFO
栈 stack 是一种后劲先出的结构 last in first out LIFO
实现图
    可以用散列表,在python里是字典。每个键值对表示从键指向值
实现BFS
    创建一个队列,用于存储要检查的人
    从队列中弹出一个人
    检查这个人是否是目标
    是的话返回
    不是的话就将其所有指向,即其在字典里的所有值添加到队列中,回到第二步
    如果队列为空,就说明目标不存在
    为了防止形成无限循环,应该添加一个列表用于存储记录检查过的人
    需要按加入顺序检查搜索列表中的人,否则找到的就不是最短路径,因此搜索列表必须是队列。

广度优先搜索用于在非加权图中查找最短路径
狄克斯特拉算法用于在加权图中查找最短路径
仅当权重没有负值的时候,狄克斯特拉算法才有效
如果图中包含负权边,使用贝克曼-福德算法

第七章-狄克斯特拉算法

狄克斯特拉算法

包含四个步骤
    找出当前权值最小的节点
    对于该节点的邻居,检查是否有前往它们的更短的路径,如果有,就更新其开销
    重复这个过程,直到对图中的每个点都这样做了
    计算最终路径
术语
    权重,每条边的度量指标
    加权图,带权重的图称为加权图
    非加权图,不带权重的图称为非加权图
狄克斯特拉算法假设:对于处理过的海报节点,没有前往该节点的更短的路径,这种假设仅在没有负权边的时候才成立。因此这个算法不能用于包含负权边的图。在包含负权边的图中,要找出最短路径,可以使用贝尔曼-福德算法
算法的实现
    定义三个散列表,graph、costs、parents
        graph 存储图信息,每个键值对也是一个散列表,键为当前节点,包括当前节点的邻居节点和前往邻居节点的开销,即边的权重
        costs 存储从出发点到当前节点的开销
        parents 存储着最小开销时,其父节点
    定义一个列表 processed
        用于存储处理过的节点

狄克斯特拉算法

graph = {}
graph["start"] = {}
graph["start"]["a"] = 6
graph["start"]["b"] = 2
graph["a"] = {}
graph["a"]["end"] = 1
graph["b"] = {}
graph["b"]["a"] = 3
graph["b"]["end"] = 5
graph["end"] = {}

costs = {}
costs["a"] = 6
costs["b"] = 2
costs["end"] = 999

parents = {}
parents["a"] = "start"
parents["b"] = "start"
parents["end"] = None

processed = []

node = find_lowest_cost_node(costs)
while node is not None:
    cost = costs[node]    # 取出当前节点目前为止的总开销
    neighbors = graph[node]    # neighbors是一个字典,包含着node的所有邻居与权重
    for n in neighbors.keys():
        new_costs = cost + neighbors[n]    # 计算当前节点到邻居节点的总开销
        if new_costs < costs[n]:    # 如果这个总开销比邻居节点之前的总开销小的话
            costs[n] = new_costs    # 就将小值赋给邻居节点的总开销,并
            parents[n] = node        # B并且将邻居节点的父节点更新为当前节点
    processed.append(node)    # 将当前节点放入以处理过的节点中
    node = find_lowest_cost_node(costs, processed)    # 找到下一个要处理的节点,直到全部节点都处理过

def find_lowest_cost_node(costs, processed):
    lowest_cost = 999    # 最小开销
    lowest_cost_node = None    # 最小开销时的节点
    for node in costs.keys():    # 遍历costs字典,寻找下一个最小开销值
        cost = costs[node]    # 将当前开销记为cost
        # 如果当前开销小于之前的最小开销并且不在processed中
        if cost < lowest_cost and (node not in processed):    
            lowest_cost = cost    # 将最小开销记为当前节点的开销
            lowest_cost_node = node     # 将要返回的最小开销节点记为当前节点
    return(Lowest_cost_node)

第八章-贪婪算法

NP完全问题

NP完全问题是一种没有快速算法的问题
使用近似算法,求解NP完全问题的近似解
近似算法
    在获得精确解需要的时间太长时,可使用近似算法
    近似算法的优劣标准:
        速度有多快
        得到的近似解与最优解的接近程度
贪婪算法,每步都采取局部最优解,最终得到全局最优解的近似解。贪婪算法易于实现、运行速度快,是不错的近似算法。
    广度优先搜索、狄克斯特拉算法都是贪婪算法
如何识别NP完全问题
    元素较少时算法的运行速度非常快,但随着元素数量的增加,速度会非常慢
    涉及“所有组合”的问题通常是NP完全问题
    不能将问题分成小问题,必须考虑各种可能的情况。这可能是NP完全问题
    如果问题涉及序列(如旅行商问题中的城市序列)且难以解决,它可能是NP完全问题
    如果问题涉及集合(如广播台集合)且难以解决,它可能是NP完全问题
    如果问题可转换为集合覆盖问题或旅行商问题,那它肯定是NP完全问题

第九章-动态规划

动态规划

这是一个求解最优解的算法,将问题分成小问题,然后求解小问题的最优解
以背包问题为例,如何能够在不超过背包空间的情况下,装价值总和最大的物品
    简单的说,就是在是否装这个物品的时候,选择以下两个值中的较大值
        上一个单元的值
        当前物品的价值 + (背包空间 - 当前物品需要的空间)这个剩余空间的最大价值
    但是动态规划有一个假设,当且仅当每个子问题都是离散的时候,即不依赖于其它子问题的时候,动态规划才管用
通过背包问题可以发现:
    动态规划可以在给定约束条件下,找到最优解。如在背包问题中,必须在背包容量给定的情况下,偷到价值最高的商品
    在问题可以分解为彼此独立且离散的子问题时,就可以使用动态规划来解决
如何设计动态规划解决方案:
    每种动态规划解决方案都设计网格
    单元格中的值通常就是要优化的值
    每个单元格都是一个子问题,考虑如何将问题分成子问题,这有助于找出网格的坐标轴
绘制网格需要考虑
    单元格中的值是什么
    如何将这个问题划分为子问题
    网格的坐标轴是什么
最长公共子串
    如果两个字母不相同,则为0
    如果两个字母相同,值为左上角的值+1
最长公共子序列
    如果两个字母不同,就选择上方和左方邻居中较大的那个(其实就是A[m-1][n]和A[m][n-1])
    如果两个字母相同,值为左上角的值+1

背包问题最长公共子串问题最长公共子序列问题

第十章-K近邻算法

KNN

KNN用于分类和回归
    分类就是编组
    回归就是预测
特征提取意味着将样本转换为一系列可比较的数字
能够挑选合适的特征事关KNN算法的成败
毕达哥拉斯公式
在计算样本之间距离的时候,除了使用距离公式,还可以使用预先相似度,计算两个样本之间的角度而不是距离

第11章-接下来如何做

    在前面的二分查找中,每当有新数据的时候,必须将数据插入到合适的位置,使插入后的数组依然有序
    二叉查找树:左子节点都比根节点小,右子节点都比根节点大
    二叉查找树不能随机访问
    有一些处于平衡状态的特殊二叉查找树:B树(数据库常用来存储数据)、红黑树、堆、伸展树
反向索引
    散列表,将内容映射到索引,如网页将网页内容映射到url,这种数据结构称为反向索引
傅里叶变换

并行算法

    为了提高算法的速度,需要它们在多个内核中并行的执行
    并行算法速度的提升不是线性的,原因有:
        并行管理开销,两个内核对总共1000个数据,分别排序500个数据后,需要将2个有序数组合并成一个有序数组,这个过程也是需要时间的
        负载均衡,在分配任务的时候,如何均匀的分配任务使得两个内核能够几乎同时结束

MapReduce

    分布式算法。分布式算法适合用于在短时间内完成海量的工作,其中MapReduce基于两个简单的理念:映射(map)函数和归并(reduce)函数
    映射函数,接受一个数组,然后对数组中的每个元素都执行同样的处理
    归并函数,将很多项归并为一项。将一个数组转换为1个函数

布隆过滤器和HyperLogLog

    布隆过滤器是一种概率型数据结构,它提供的答案有可能不对,但很可能是正确的。
    布隆过滤器的优点在于占用的存储空间很少。比如使用散列表虽然可以得到绝对正确的答案,但是需要存储的空间很大。
    布隆过滤器可能出现错报的情况,即无说成有,但不可能出现漏报的情况,即有说成无
    HyperLogLog,是一种类似于布隆过滤器的算法。近似的计算集合中不同的元素数,与布隆过滤器一样,不能给出准确的答案,但也相近,而且占用空间少

SHA算法

    一种算列函数是安全散列函数(secure hash algorithm,SHA)函数。给定一个字符串,SHA返回其散列值
    SHA是一个散列函数,它生成一个散列值——一个较短的字符串。
    用于创建散列表的散列函数根据字符串生成数组索引,而SHA根据字符串生成另一个字符串
    用于文件比较、检查密码

局部敏感的散列算法

    SHA有一个重要的特征是局部不敏感,即使只修改字符串中的一个字符,得到的散列值也截然不同
    Simhash 是一种局部敏感的算法,对字符串做细微的修改的话,生成的散列值也只存在细微的差别
    用于检查两项内容的相似程度

Diffie - Hellman 秘钥交换

    Diffie - Hellman算法解决了如下两个问题:
        双方无需知道加密算法。不必协商要使用的加密算法
        要破解加密的信息很难
    公钥加密,私钥解密
    RSA是Diffie - Hellman的替代者

线性规划

    也是一种最优解,用于在给定约束条件下,最大限度地改善指定的指标
    线性规划使用Simplex算法
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《图解算法》总结 的相关文章

随机推荐

  • oracle数据库imp/sqlplus命令无效引发的问题

    好久没有使用Oracle数据库 在导入数据库dmp文件时出现imp命令无效 oracle导入dmp文件命令 imp user password ip 端口 server name file 文件路径 dmp full y 如 imp crm
  • HTML 常用快捷键,HTML介绍

    一 1 修改主题 2 3 修改 4 长代码换行 file setting general wrap 对勾选中 5 新建项目 file new project 6 关联浏览器 file setting tool web borther 复制路
  • bigdata_git版本控制系统

    一丶版本控制系统发展 集中式VCS 分布式VCS git 二丶git工作流程图 三丶分支管理 每个项目确立后可以添加多个分支 分支可以更新版本 只要分支没有合并提交 对其他人没有任何影响 这也是跟svn的不同 四丶内部数据存储方式 git统
  • 惠斯通电桥与运算放大器的输入失调电流和输入偏置电流

    在做数字开关气压表项目中 使用的气压传感器的结构是惠斯通电桥 输出差分信号 差分电压与气压大小成线性关系 运放的失调电压对精度影响很大 在这里考虑选择使用低漂移运放 在选择运放时考虑了输入电阻 失调电压的影响 如果运放的输入电阻大小与电桥电
  • 大模型训练避坑指南

    原文 https baijiahao baidu com s id 1760862056681517207 wfr spider for pc 自 2022 年 11 月底 ChatGPT 发布以来 大模型的热度持续发酵 相信高屋建瓴的讨论
  • 辛普森悖论_所谓的辛普森悖论

    辛普森悖论 We all know the Simpsons family from Disneyland but have you heard about the Simpson s Paradox from statistic theo
  • cdn缓存服务器有网站图片,CDN缓存服务器图片存储一致性hash算法的理解

    用hash做缓存 假如有三台服务器 1 2 3 有三万张图片 我想将图片平均缓存到我三台服务器上 一个服务器大概一万张 怎么去实现这个办法呢 可以用hash来取余数进行操作 加入我们是以图片的名字作为key进行hash计算 hash 图片名
  • C - 滑动窗口 /【模板】单调队列

    Description 有一个长为 n 的序列 a 以及一个大小为 k 的窗口 现在这个从左边开始向右滑动 每次滑动一个单位 求出每次滑动后窗口中的最大值和最小值 例如 The array is 1 3 1 3 5 3 6 7 and k
  • .whl is not a supported wheel on this platform的原因及其解决办法

    在PIP安装 whl文件的时候碰到这个错误 具体如下 我的python版本是3 4 4 这个错误的原因如下 可能的原因1 安装的不是对应python版本的库 下载的库名中cp27代表python2 7 其它同理 可能的原因2 下载的是对应版
  • IDEA写SQL语句时不会提示表名、列名的处理方法(实测有效)

    打出表名没有提示 下面进行设置 按照别人的设置没效果 打开设置 还是刚才的路径 要同时设置两个地方才有效
  • 《Netty实战》读书笔记

    第一章 Netty 异步和事件驱动 Netty包含网络编程 多线程处理和并发 NIO NIO 代表非阻塞 I O Non blocking I O Netty 的核心组件 Netty 的主要构件 Channel 回调 Future 事件和
  • SSM商城项目实战总结

    SSM商城项目实战总结 编程思想是指在软件开发过程中 程序员所遵循的一种思维模式或方法论 它是指导程序员如何组织和解决问题的一种思考方式 下面是对常见的编程思想进行的总结 面向对象编程 OOP 面向对象编程是一种将数据和操作数据的方法组合在
  • ADS使用J-LINK调试之配置方法

    ADS使用J LINK调试之配置方法 1 安装好ADS1 2及J LINK驱动文件 2 ADS配置 随便打开项目 进入AXD调试界面 在AXD中 选择 3 进入以下界面 选择 Add 进入J LINK安装目录下 添加 JLinkRDI dl
  • ubuntu终止terminal中下载任务以及继续下载

    ctrl c是终止正在下载的任务 wget c URL是继续刚才终止的那个任务
  • 22 年国内最牛的 Java 面试八股文合集(全彩版),不接受反驳

    很多小伙伴从四月份就开始准备面试了 截止现在已经过去 2 个多月的时间 显然这段时间的准备没有白费 很多小伙伴都报喜 成功拿到了 XX 公司的 Offer 下面我把这段时间给小伙伴本准备的 Java 面试八股文合集 全彩版 拿出来 来帮助没
  • Python 字符串拼接 ‘+=‘ 和 ‘join()‘ 谁的速度更快?

    一 字符串拼接的两种方法 程序当中经常出现需要不断接收新字符串并将这些字符串组成新字符串输出的情况 该方法一般有两种解决方案 创建一个空字符串 test str 将每次新传入的 new str 使用 test str new str 的方式
  • 面试题 02.07. 链表相交

    给你两个单链表的头节点 headA 和 headB 请你找出并返回两个单链表相交的起始节点 如果两个链表没有交点 返回 null 图示两个链表在节点 c1 开始相交 来源 力扣 LeetCode 链接 https leetcode cn p
  • Linux内核中PF_KEY协议族的实现(4)

    本文档的Copyleft归yfydz所有 使用GPL发布 可以自由拷贝 转载 转载时请保持文档的完整性 严禁用于任何商业用途 msn yfydz no1 hotmail com 来源 http yfydz cublog cn 6 通知回调处
  • Android高级工程师普遍进阶难题:遇到瓶颈我们该如何去提升自己?哪个方向

    不要抱怨 抱怨无济于事 只能带来负能量 最重要的是改变 7 坚持写博客和技术文章 多总结 多参与开源项目 8 选择一家好的有发展前途的公司陪其成长 当发现现在公司不能满足自己的成长和发展时 果断跳槽 因为人生毕竟最宝贵的是时间 特别是程序员
  • 《图解算法》总结

    最近快速阅读了 图解算法 这本算法的入门书 对其中的一些知识点做了总结 使用递归函数需要确定基线条件和递归条件 调用栈 在调用一个函数的时候 当前函数暂停并处于未完成状态 分而治之 D C算法 找出基线条件 然后不断将问题分解 直到符合基线