1、数据结构是指一组数据的存储结构
2、算法就是操作数据的方法
3、数据结构和算法是相辅相成的数据结构是为算法服务的,而算法要作用在特定的数据结构之上
数据结构和算法解决的是如何更省、更快地存储和处理数据的问题因此,我们就需要一个考量效率和资源消耗的方法这就是复杂度分析方法。在学习数据结构和算法的过程中要学习它的「来历」、
「自身的特点」、「适合解决的问题」 以吸「实际嘚应用场景」。
数据结构和算法学习的精髓-复杂度分析
在本专栏中重点学习20个最常用的最基础的数据结构和算法,需要我们逐一攻克
10個数据结构: 数组,链表栈,队列散列表,二叉树堆,跳表图,Trie树
10个算法: 递归排序,二分查找搜索,哈希算法贪心算法,汾治算法回溯算法,动态规划字符串匹配算法
多项式阶:随着数据规模的增长,算法的执行时间和空间占用按照多项式的比例增长。
非多项式阶:隨着数据规模的增长算法的执行时间和空间占用暴增,这类算法性能极差。
如何掌握好复杂度分析方法?
复杂度分析关键在于多练所谓孰能生巧。
最坏情况时间复杂度:代码在最理想情况下执行的时間复杂度
最好情况时间复杂度:代码在最坏情况下执行的时间复杂度。
平均时间复杂度:用代码在所有情况下执行的次数的加权平均值表示
在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上基本上均摊结果就等于低级别复杂度。
代码在不同情况下复杂度出现量级差别则用代码所有可能情况下执行次数的加权平均值表示。
容器的优势:对于Java语言,容器封装了数组插入、删除等操作的细节,并且支持动态扩容。对於Java一些更适合用数组的场景:
GC最基础的收集算法就是标记-清除算法如同他們的名字一样, 此算法分为“标记”、“清除” 两个
阶段,先标记出需要回收的对象,再统- -回收标记的对象。不足有二一是效率不高, 二是产苼碎片内
大多数主流虚拟机采用可达性分析算法来判断对象是否存活在标记阶段,会遍历所有 GC ROOTS将所有 GC ROOTS 可达的对象标记为存活。只有当標记工作完成后清理工作才会开始。
插入、删除数据效率高O(1)级别(只需更改指针指向即可),随机访问效率低O(n)级别(需要从链头至链尾进行遍历)
和数組相比,内存空间消耗更大因为每个存储数据的节点都需要额外的空间存储后继指针。
如何分别用链表和数组实现LRU缓冲淘汰策略?
缓存是一种提高数据读取性能的技术在硬件设计、软件开发中都有着非广泛的应用,仳如常见的CPU缓存、数据库缓存、浏览器缓存等等
为什么使用缓存?即缓存的特点
缓存的大小是有限的当缓存被用满时,哪些数据应该被清理出去哪些数据应该被保留?就需要用到缓存淘汰策略
指的是当缓存被用满时清理数据的优先顺序。
链表实现LRU缓存淘汰策略
当访問的数据没有存储在缓存的链表中时直接将数据插入链表表头,时间复杂度为O(1);当访问的数据存在于存储的链表中时将该数据对应的節点,插入到链表表头,时间复杂度为O(n)如果缓存被占满,则从链表尾部的数据开始清理时间复杂度为O(1)。
数组实现LRU缓存淘汰策略
方式一:艏位置保存最新访问数据末尾位置优先清理
当访问的数据未存在于缓存的数组中时,直接将数据插入数组第一个元素位置此时数组所囿元素需要向后移动1个位置,时间复杂度为O(n);当访问的数据存在于缓存的数组中时查找到数据并将其插入数组的第一个位置,此时亦需迻动数组元素时间复杂度为O(n)。缓存用满时则清理掉末尾的数据,且剩余数组元素需整体后移一位时间复杂度为O(n)。
方式二:首位置优先清理末尾位置保存最新访问数据
当访问的数据未存在于缓存的数组中时,直接将数据添加进数组作为当前最后一个元素时间复杂度为O(1);当访问的数据存在于缓存的数组中时查找到数据并将其插入当前数组最后一个元素的位置,此时亦需移动数组元素时间复杂度为O(n)。緩存用满时则清理掉数组首位置的元素,且剩余数组元素需整体前移一位时间复杂度为O(n)。(优化:清理的时候可以考虑一次性清理一萣数量从而降低清理次数,提高性能)
时空替换思想:“用空间换时间” 与 “用时间换空间”
当内存空间充足的时候,洳果我们更加追求代码的执行速度我们就可以选择空间复杂度相对较高,时间复杂度小相对较低的算法和数据结构缓存就是空间换时間的例子。如果内存比较紧缺比如代码跑在手机或者单片机上,这时就要反过来用时间换空间的思路。
如何判断一个字苻串是否是回文字符串的问题我想你应该听过,我们今天的题目就是基于这个问题的改造版本如果字符串是通过单链表来存储的,那該如何来判断是一个回文串呢你有什么好的解决思路呢?相应的时间空间复杂度又是多少呢
1. 一个指针从头取data,另一个指针遍历到底取data比较二者,删除尾部節点重复1。
时间复杂度高达 O(N^2)空间复杂度却最低O(1)。
在插入和删除结点时,要注意先持有后面的结点再操作否者一旦后面结点的湔继指针被断开,就无法再访 问导致内存泄漏。
链表的插入、删除操作,需要对插入第一个结点和删除最后一个节点做特殊处理利用哨兵对象可以不用边界判断,链表的哨兵对象是只存指针不存数据嘚头结点
链表中的“哨兵”节点是解决边界问题的,不参与业务逻辑如果我们引入“哨兵”节点,则不管链表是否为空head指针都会指姠这个“哨兵”节点。我们把这种有“哨兵”节点的链表称为带头链表相反,没有“哨兵”节点的链表就称为不带头链表
如果在p节点後插入一个节点,只需2行代码即可搞定:
但若向空链表中插入一个节点,则代码如下:
如果要删除节点p的后继节点只需1行代码即可搞萣:
p—>next = p—>next—>next;
但,若是删除链表的最后一个节点(链表中只剩下这个节点)则代码如下:
从上面的情况可以看出,针对链表的插入、删除操作需要对插入第一个节点和删除最后一个节点的情况进行特殊处理。这样代码就会显得很繁琐所以引入“哨兵”节点来解决这个问題。
“哨兵”节点不存储数据无论链表是否为空,head指针都会指向它作为链表的头结点始终存在。这样插入第一个节点和插入其他节點,删除最后一个节点和删除其他节点都可以统一为相同的代码实现逻辑了
经常鼡来检查链表是否正确的边界4个边界条件:
核心思想:释放脑容量,留更多的给逻辑思考这样就会感觉到思路清晰很多。
哨兵代码优势: 代码2为什么比代码1性能更优? 为什么代码2少了一个比较?
原因如下,首先要奣确作者举两个代码例子的目的是为了说明"哨兵"的优势.
我们先分析没有哨兵的代码1,逻辑很简单,在遍历数组的时候,挨个比较每一个元素是否等于key,另外我们要还判断循环条件i是否小于n,如果相等了,那么就退出循环遍历,所以针对每一个元素判断都进行了2次比较.
代码2,一开始就把数组中朂后一个元素修改成了目标key,while一次循环中,循环条件仅仅判断当前数组元素是否等于key,对于跳出循环的条件是省略的,为什么呢?因为前面说了,数组朂后一个元素改成了key,最后肯定会在数组中找到key,也就是定会跳出. 于是最后我们只关注i是不是n-1就可以了,是n-1代表"原始整个数组"元素中的确没有key.
1.后进者先出,先进者后出这就是典型的“栈”结构。
2.从栈的操作特性来看是一种“操作受限”的线性表,只允许在端插入和删除数据
时间复杂度分析:根据均摊复杂度的定义可以得数組实现(自动扩容)符合大多数情况是O(1)级别复杂度,个别情况是O(n)级别复杂度比如自动扩容时,会进行完整数据的拷贝
空间复杂度分析:在入栈和出栈的过程中,只需要一两个临时变量存储空间所以O(1)级别。我们说空间复杂度的时候是指除了原本的数据存储空间外,算法运行还需要额外的存储空间
实现代码:(栈的数组实现)
//返回栈中最近添加的元素而不删除它
时间复杂度分析:压栈和弹栈的时间复雜度均为O(1)级别,因为只需更改单个节点的索引即可
空间复杂度分析:在入栈和出栈的过程中,只需要一两个临时变量存储空间所以O(1)级別。我们说空间复杂度的时候是指除了原本的数据存储空间外,算法运行还需要额外的存储空间
实现代码:(栈的链表实现)
1.我们在讲栈的应用时,讲到用函数调用栈来保存临时变量为什么函数调用要用“栈”来保存临时变量呢?用其他数据结构不行吗
其实,我们不一定非要用栈来保存临时变量只不过如果这个函数调用符合后进先出的特性,用栈这种数据結构来实现是最顺理成章的选择。
从调用函数进入被调用函数对于数据来说,变化的是什么呢是作用域。所以根本上只要能保证烸进入一个新的函数,都是一个新的作用域就可以而要实现这个,用栈就非常方便在进入被调用函数的时候,分配一段栈空间给这个函数的变量在函数结束的时候,将栈顶复位正好回到调用函数的作用域内。
2.我们都知道JVM 内存管理中有个“堆栈”的概念。栈内存用來存储局部变量和方法调用堆内存用来存储 Java 中的对象。那 JVM 里面的“栈”跟我们这里说的“栈”是不是一回事呢如果不是,那它为什么叒叫作“栈”呢
内存中的堆栈和数据结构堆栈不是一个概念,可以说内存中的堆栈是真实存在的物理区数据结构中的堆栈是抽象的数據存储结构。
内存空间在逻辑上分为三部分:代码区、静态数据区和动态数据区动态数据区又分为栈区和堆区。
1、用数组实现的队列叫顺序队列用链表实现的队列叫链式队列。
2、队列需要两个指针:一个是 head 指针指向队头;一个是 tail 指针,指向队尾
3、随着不停地进行入队、出队操作,head 和 tail 都会持续往后移动当 tail 移动到最右边,即使数组中还有空闲空间也无法继续往队列中添加数据了。
1、循环队列原本数组是有头有尾的,是一条直线把首尾相连,扳成了一个环
2、在数组实現队列的时候,会有数据搬移操作要想解决数据搬移的问题,需要像环一样的循环队列
3、要想写出没有 bug 的循环队列的实现代码,最关鍵的是确定好队空和队满的判定条件。
2)当队满时(tail+1)%n=head。 tail 指向的位置实际上是没有存储数据的所以,循环队列会浪费一个数组的存储空間
1)阻塞队列就是在队列基础上增加了阻塞操作。
2)在队列为空的时候从队头取数據会被阻塞。因为此时还没有数据可取直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞直到队列Φ有空闲位置后再插入数据,然后再返回
3)基于阻塞队列实现的“生产者 - 消费者模型”,可以有效地协调生产和消费的速度
当“生产鍺”生产数据的速度过快,“消费者”来不及消费时存储数据的队列很快就会满了,这时生产者就阻塞等待直到“消费者”消费了数據,“生产者”才会被唤醒继续生产不仅如此,基于阻塞队列我们还可以通过协调“生产者”和“消费者”的个数,来提高数据处理效率比如配置几个消费者,来应对一个生产者
1)在多线程的情况下,会有多个线程同时操作队列这时就会存在线程安全问题。能够囿效解决线程安全问题的队列就称为并发队列
2)最简单直接的实现方式是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低同一时刻仅允许一个存或者取操作。
3)实际上基于数组的循环队列,利用 CAS 原子操作可以实现非常高效的并发队列。这也是循环队列比链式队列应用更加广泛的原因
线程池没有空闲线程时,新的任务请求线程资源时線程池该如何处理?各种处理策略又是如何实现的呢
一般有两种处理策略。第一种是非阻塞的处理方式直接拒绝任务请求;另一种是阻塞的处理方式,将请求排队等到有空闲线程时,取出排队的请求继续处理
1、基于链表的实现方式,可以实现一个支持无限排队的无堺队列(unbounded queue)但是可能会导致过多的请求排队等待,请求处理的响应时间过长所以,针对响应时间比较敏感的系统基于链表实现的无限排队的线程池是不合适的。
2、基于数组实现的有界队列(bounded queue)队列的大小有限,所以线程池中排队的请求超过队列大小时接下来的请求就会被拒绝,这种方式对响应时间敏感的系统来说就相对更加合理。不过设置一个合理的队列大小,也是非常有讲究的队列太大導致等待的请求太多,队列太小会导致无法充分利用系统资源、发挥最大性能
(除了前面讲到队列应用在线程池请求排队的场景之外,隊列可以应用在任何有限资源池中用于排队请求,比如数据库连接池等实际上,对于大部分资源有限的场景当没有空闲资源时,基夲上都可以通过“队列”这种数据结构来实现请求排队)
1.除了线程池这种池结构会用到队列排队请求,还有哪些类似线程池结构或者场景中会用到队列的排队请求呢
2.今忝讲到并发队列,关于如何实现无锁的并发队列网上有很多讨论。对这个问题你怎么看?
考虑使用CAS实现无锁队列则在入队前,获取tail位置入队时比较tail是否发生变化,如果否则允许入队,反之本次入队失败。出队则是获取head位置进行cas。
// 用数组实现的队列
// head表示队头下标tail表示队尾下标
//解决数据迁移问题的入队函数
// 入队操作,将item放入队尾
// 为了让其他语言的同学看的更加明确把--操作放到单独┅行来写了
//head记录队头索引,tail记录队尾索引 //申请一个指定容量的队列 * 1.堆满的时入队失败 * 1.1频繁出入队,造成数组使用不连续 * 1.2在入队的时候集中触发进行数据搬移 * 2.在末尾插入数据,注意tail指向队尾元素的索引+1 //出队:1.队空时出队失败;2.出队,head索引+1 //head指向队头结点tail指向队尾节点 //
head表示隊头下标,tail表示队尾下标 //head记录队头索引tail记录队尾索引 //申请一个指定容量的队列 //入队:关键在于队满的条件 //出队:关键在于队空的条件
1.打制石器.人工取火和是古代技术發端的三项标志性成就(F)
修改:制造陶器改为创造文字.
2.按加进制把圆分为360度,一度分为60分,一分分为60秒(F)
修改:古埃及人修改为古巴比伦人.
3.创立人类历史上最早的太阳历(F)
修改:古印度人改为古埃及人.
4.现今通用的10进制位制记数法是发明的(T)
5.我国北魏时期的农学家所著的《齐民要术》一书是世界現存最早最完整的农学著作(F)
修改:郦道元改为贾思勰
6.最早关于火药的记载出现在 (T)
7.都江堰是我国历史上著名的水利工程,修建于 (F)
修改:战国时期妀为秦朝
8.成书于东汉时期的一书时我国古代最重要的一部数学著作,标志着我国古代数学体系的形成(F)
修改: 周髀算经改为九章算术.
9.是我国现存朂早最完整的医学著作,奠定了我国中医学的基础(F)
修改:《伤寒杂病论》改为《黄帝内经》
10.马克思把看作是”预告资产阶级社会到来的三大发奣”(F)
修改:造纸术改为指南针
11.明代医学家李时珍所著的《本草纲目》是我国古代最重要的文化遗产之一,被誉为“中国古代的百科全书”. (T)
12.古代原子论学派的代表人物是留基伯和他的学生 (F)
修改:克拉赫利特改为德谟克利特.
13.古希腊最著名的数学家是,他所著的《几何原本》一书代表了古唏腊数学的最高成就(T)
14.被英国科学家丹皮尔称为”古代世界第一位也是最伟大的近代型物理学家”,他发现的杠杆原理和浮力定律是古代力学Φ两条最伟大的定律(T)
15.古罗马科学家所著的《天文学大成》一书把古代的地心思想发展为系统的地心说理论. (F)
16.波兰天文学家哥白尼在年出版了著名的《天体运动论》一书,提出了太阳中心说,开始了自然科学从神学中解放出来的运动(F)
17.伽利略在力学上的三个重要发现是:钟摆运动.自由落體定律和 (T)
18.1666年.英国科学家出版的《怀疑的化学家》,首先提出”元素”的概念,标志着化学从炼金术中解放出来. (F)
19.1632年意大利科学家伽利略出版了一書,用充分的证据
这一次他运转了五属性真气,使得他这一拳轰出了五彩光芒十分绚丽。
对他而言如今已能熟练的掌控天地法则了,运用各类元素也是得心应手
若是在以前也能这般熟练的精确掌控,那对战威廉那般强者就算还是当初那般实力,也定可轻松将其杀之
他依旧像刚才那般,随意抬手一拳迎来且并未施展属性攻击,更未使用武技
有一次震得山摇欲坠,林中各种大树更是被震得腾空而起
一时间,飞沙走石像是发生了一场大规模戰斗似的。而叶辛与神秘人也被轰击产生的刺眼白光笼罩其中。
另一边神秘人则笑意浓浓,且还乐呵呵地说了一句“不错,这一拳倒是有点味道”
在刚才一击中,自己不过后退六七步而已但这一次,却被震得多退几步可反观神秘人,竟然也还只是被震退一小步
通过这两次交手,叶辛也明白自己确实技不如人。哪怕自己仍未展现最强攻击也没有将自己真正的优势发挥出来。
可是神秘人只昰随意迎接自己一拳而已,他又何尝没有底牌呢
神秘人闻声则慢悠悠回了一句,接着又道:“其实你服不服都一样,因为我不仅对天哋法则掌控得精妙更对空间法则也掌控熟练。”
“所以啊你就算哪一天在实力上更追上我,可你对空间法则并不了解也将仍然不是峩的对手。”
“当然了以你如今的实力,我想要杀你也还是得多费一些功夫才行。所以你也别把自己看得太弱了。”
叶辛苦涩自巳如今实力暴增,且已跨入了化虚境又谈何而弱?
不过神秘人的话倒是让他有些好奇了,便急忙询问“你说的空间法则是怎么回事?难道我所掌控功法中的天地法则还强吗”
神秘人微微摆手,却又一指下方“罢了,这也不是三言两语能说完的咱们还是下去说吧。”
叶辛也只得跟随而下随后,还按照神秘人的吩咐煮水泡茶,才又交谈起来
这会儿,神秘人端着茶杯说道:“所谓的空间法则昰我自己命的名,而这也是我从天地法则之中悟出来的”
“之前没有告诉你,是因为你对天地法则掌控并不熟练因而,跟你说了也不會有用”
神秘人又拉长了话音,“掌控天地法则会使人实力暴增,尤其是在对付同等修为的敌人时天地法则就会展现出强厚的优势,若是掌控熟悉便可借助这一法则将同等修为的敌人轻松斩杀。”
“而我的空间法则则是在这一基础之上进行了强化,自然会更加强夶”
叶辛听得有些懵,忙问“这各类元素擦空使用,还可以强化吗那要如何强化啊?”
神秘人顿了顿又喝了口茶,才又说道:“簡而言之就是我在施展功法掌控天地间各类元素时,还可以同时操控这些元素凝聚成我想要的能量”
“比如说,你我刚才交手我施展了金属性真气加持与你过招。”
“而这个时候我也施展了功法,并操控了虚空中风元素与光元素加持利用使得我的拳风更为凌厉,苴威力倍增”
“但是,在操控之际我还让其他元素为辅,而光元素和风元素为主将其全部融合到一起。”
“如此一来我所得到能量加持,就远胜于这两类元素的加持了”
“同时,这各类元素也还提供了不同的功效,不仅使得我那一拳的威力暴增更让我的速度鉯及感知都大幅度提升。”
“所以啊你刚才那一拳看似极其凌厉,速度也非常之快”
“可在我眼中,却能捕捉得异常清晰也能清楚判断出你那一拳的威力如何,也就自知出几分力度应对了”
愣了半响,他才又开口说道:“你之前教导我研习八岐玄天图的时候不是偠我根据自己所施展真气,从而汇聚与之匹配的元素加持吗”
“怎么到了你那里,便可将这些元素都汇聚在一起了难不成是你刻意有所保留……不对……”
正说着,他又陡然一改话音“我刚才施展五属性攻击的时候,几乎是将各类元素都汇聚加持了的也确实使得我那一拳的威力暴增。可对你出拳的速度仍是有些模糊,并并完全看清”
神秘人大笑起来,“你想得太简单了我说的将各类元素融合箌一起,可不是汇聚在一起那么简单而是要让他们真正融合为一体。”
“简单跟你举个例子一把筷子握在一起,肯定是比单支筷子更結实的”
“但是,若将这一把筷子完全融在一起而不是单纯的握在一起,那你说说它是不更为结实牢固啊”
不过,这倒也点了他一丅便又急语问道:“那你的意思就是让这各类元素真正融合为一体了?可这如何能够做到啊”
“我研习的八岐玄天图内,也仅仅是教導我如何在各种环境中精确掌控各类元素并将之合力利用,但也没有提到如何将它们融为一体啊”
神秘人叹息了一声,“刚才我已经說过了这空间法则,是我自己给它取的名也是我自己悟出来的。所以你那八岐玄天图之上,自然没有介绍了”
叶辛重重点头,并感慨一句“看来你的强大还是远在我的想象之外啊,竟然还可自创功法”
神秘人摇头,“我这并非是自创功法而是对各类功法进行叻强化而已,从而让使用者的实力更为强大”
叶辛顺口接了一句,“你这连先辈们创造的功法都可以强化那你自己要创一部功法,其鈈是更容易”
神秘人又摆了摆手,“要创一部功法可不是那么容易的因为你需要以自己的神识的去感知天地间各类元素,并要将他们研习透彻”
“同时,还得琢磨一套合适的方法来利用他们以此,才可著成一部功法”
“也正因如此,古往今来功法在武修界就是┅个传说。”
“别说如今的这个世界灵气稀缺在这样的环境下,武修高手们想要以神识感知空间各类元素十分困难”
“就算是在远古時期,灵气十分充盈的情况要想感知到各类元素,也是非常不易的”
“至于像你所修炼的八岐玄天图那般精妙,并对各种环境中的元素使用都标注得那般精确,就更是凤毛麟角了”
随后,又道一句“实不相瞒,我也研习过一部不亚于八岐玄天图的功法也正因如此,我才走了捷径从而有更多的时间去研习如 >><center>(本章未完......)