主要是理解思路
1. 数据结构
1.1 概述
数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。
1.2 划分
从关注的维度看,数据结构可以划分为数据的逻辑结构和物理结构,同一逻辑结构可以对应不同的存储结构,逻辑结构反映的是数据元素之间的逻辑关系,逻辑关系是指数据元素之间的前后间以什么形式相互关联,这与他们在计算机中的存储位置无关。
逻辑结构包括:
- 集合:只是扎堆凑在一起,没有互相之间的关联
- 线性结构:一对一关联,队形
- 树形结构:一对多关联,树形
- 图形结构:多对多关联,网状
数据物理结构指的是逻辑结构在计算机存储空间中的存放形式(也称为存储结构)。一般来说,一种数据结构的逻辑结构根据需要可以表示成多种存储结构
存储结构
- 顺序存储:用一组地址连续的存储单元依次存储集合的各个数据元素,可随机存取,但增删需要大批移动。
- 链式存储:不要求连续,每个节点都由数据域和指针域组成,占据额外空间,增删快,查找慢需要遍历。
- 索引存储:除建立存储结点信息外,还建立附加的索引表来标识结点的地址。检索快,空间占用大
程序中常见的数据结构
- 数组(Array):连续存储,线性结构,可根据偏移量随机读取,扩容困难栈( Stack):线性存储,只允许一端操作,先进后出,类似水桶
- 队列(Queue):类似栈,可以双端操作。先进先出,类似水管.
- 链表( LinkedList):链式存储,配备前后节点的指针,可以是双向
- 树( Tree):典型的非线性结构,从唯一的根节点开始
- 图(Graph):另一种非线性结构,由节点和关系组成,没有根的概念,互相之间存在关联。
- 堆(Heap):特殊的树,特点是根结点的值是所有结点中最小的或者最大的,且子树也是堆
- 散列表(Hash):源自于散列函数,将值做一个函数式映射,映射的输出作为存储的地址
1.3 算法定义
算法是在基于存储的结构下,对数据如何有效的操作,采用什么方式可以更有效地处理数据,提高数据运算效率
- 检索:在数据结构里查找满足一定条件的节点
- 插入:往数据结构中增加新的节点,一般有一点位置上的要求,
- 删除:把指定的结点从数据结构中去掉,本身可能隐含有检索的需求。
- 更新:改变指定节点的一个或多个字段的值,同样隐含检索。
- 排序:把节点里的数据,按某种指定的顺序重新排列,例如递增或递减。
2. 算法思想
2.1 分而治之
分成各部分独立的块
2.2 动态规划
- 与分治法最大的不同在于,分治法的思想是并发,动态规划的思想是分步。该方法经分解后得到的子问题往往不是互相独立的,其下一个子阶段的求解往往是建立在上一个子阶段的解的基础上。动态规划算法同样有一定的适用性场景要求:
- 最优化解:拆解后的子阶段具备最优化解,且该最优化解与追踪答案方向一致
- 流程向前,无后效性:上一阶段的解决方案一旦确定,状态就确定,只会影响下一步,而不会反向影响
2.3 贪心算法
同样对问题要求作出拆解,但是每一步,以当前局部为目标,求得该局部的最优解。
- 要求问题可拆解,并且拆解后每一步的状态无后效性(与动态规划算法类似)要求问题每一步的局部最优,与整体最优解方向一致。至少会导向正确的主方向。
2.4 回溯算法
回溯算法实际上是一个类似枚举的搜索尝试过程。在每一步的问题下,列举可能的解决方式。选择某个方案往深度探究,寻找问题的解,当发现已不满足求解条件,或深度达到一定数量时,就返回,尝试别的路径。回溯法一般适用于比较复杂的,规模较大的问题。有“通用解题法“之称。
- 问题的解决方案具备可列举性,数量有限·界定回溯点的深度。达到一定程度后,折返
2.5 分支限界
与回溯法类似,也是一种在空间上枚举寻找最优解的方式。但是回溯法策略为深度优先。分支法为广度优先。
分支法-般找到所有相邻结点,先采取淘汰策略,抛弃不满足约束条件的结点,其余结点加入活结点表。然后从存活表中选择一个结点作为下一个操作对象。
3. 失效算法
3.1 先来先淘汰FIFO
在每一次数据插入时,如果队列已满,则将最早插入的数据移除
3.2 LRU
least recently used,淘汰最后一次使用时间最久远的数值。
以链表为例,新加入的数据放在头部,最近访问的,也移到头部,空间满时,将尾部元素删除
3.3 LFU
最近最少使用
4. 限流算法与应用
限流时对系统的保护措施,即限制流量请求,一般来说,请求流量超过系统的瓶颈,则丢弃掉多余的请求流量,保证系统的可用性
4.1 计数器
计数器采用简单的计数操作,到一段时间后自动清零
4.2 漏桶算法
漏桶算法将请求缓存在桶中,服务流程匀速处理。超出桶容量的部分丢弃。漏桶算法主要用于保护内部的处理业务,保障其稳定有节奏的处理请求,但是无法根据流量的波动弹性调整响应能力。现实中,类似容纳人数有限的服务大厅开启了固定的服务窗口
- 有效的挡住了外部的请求,保护了内部的服务不会过载内部服务匀速执行,
- 无法应对流量洪峰,无法做到弹性处理突发任务任务超时溢出时被丢弃。
- 现实中可能需要缓存队列辅助保持一段时间
4.3 令牌桶
漏桶算法的升级
- 令牌桶算法可以认为是漏桶算法的一种升级,它不但可以将流量做一步限制,还可以解决漏桶中无法弹性伸缩处理请求的问题。体现在现实中,类似服务大厅的门口设置门禁卡发放。发放是匀速的,请求较少时,令牌可以缓存起来,供流量爆发时一次性批量获取使用。而内部服务窗口不设限。
4.4 滑动窗口
滑动窗口可以理解为细分之后的计数器,计数器粗暴的限定1分钟内的访问次数,而滑动窗口限流将1分钟拆为多个段,不但要求整个1分钟内请求数小于上限,而且要求每个片段请求数也要小于上限。相当于将原来的计数周期做了多个片段拆分。更为精细。
- 应用在tcp协议发包过程中
5. 调度算法
当多个进程发出请求,必须按照一定的原则选择进程,
5.1 先来先服FCFS
5.2 短作业优先
- 适用于任务时间差别较大的场景,仍然以进站为例,拿出公交卡的优先刷卡,还没办卡的让一让解决了FCFS整体处理时间长的问题,降低平均等待时间,提高了系统吞吐量。.
- 未考虑作业的紧迫程度,因而不能保证紧迫性作业(进程)的及时处理
- 对长作业的不利,可能等待很久而得不到执行
5.3 时间片轮转
5.4 优先级调度
6. 定时算法
6.1 最小堆
Timer是最典型的基于优先级队列+最小堆实现的定时器,内部维护一个存放定时任务的优先级队列,该优先级队列使用了最小堆排序。当我们调用schedule方法的时候,一个新的任务被加入gueue,堆重排,始终保持堆顶是执行时间最小(即最近马上要执行)的。同时,内部相当于起了一个线程不断扫描队列,从队列中依次获取堆顶元素执行,任务得到调度。
下面以Timer为例,介绍优先级队列+最小堆算法的实现原理
6.2 时间轮
时间轮是一种更为常见的定时调度算法,各种操作系统的定时任务调度,linux crontab,基于iava的通信框架Netty等。其灵感来源于我们生活中的时钟。轮盘实际上是一个头尾相接的环状数组,数组的个数即是插槽数,每个插中可以放置任务。以1天为例,将任务的执行时间%12,根据得到的数值,放置在时间轮上,小时指针沿着轮盘扫描,扫到的点取出任务执行
- 如果三点种的时候,有多个任务执行怎么办,可以在三点的槽上添加一个队列
- 问题:每个轮盘的时间有限,比如1个月后的第3天的5点怎么办?
- 方案一:加长时间刻度,扩充到1年
优缺点:简单,占据大量内存,即使插槽没有任务也要空轮询,白白的资源浪费,时间、空间复杂度都高 - 方案二:每个任务记录一个计数器,表示转多少圈后才会执行。没当指针过来后,计数器减1,减0的再执行
- 优缺点:每到一个指针都需要取出链表遍历判断,时间复杂度高,但是空间复杂度低
- 方案一:加长时间刻度,扩充到1年
7. 负载均衡算法
负载均衡,英文名称为Load Balance,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,例如FTP服务器、Web服务器、企业核心应用服务器和其它主要任务服务器等,从而协同完成工作任务。既然涉及到多个机器,就涉及到任务如何分发,这就是负载均衡算法问题。
7.1 轮询
排队 一个接一个
7.2 随机
7.3 源地址哈希
对当前访问的ip地址做一个hash值,相同的key被路由到同一台机器去。场景常见于分布式集群环境下,用户登录时的请求路由和会话保持。
实现
使用HashMap可以实现请求值到对应节点的服务,其查找时的时间复杂度为0(1)。固定一种算法,将请求映射到key上即可