数据结构与算法学习笔记

2023-11-14

数据结构与算法

一、数据结构

一组数据的存储结构

二、算法

操作一组数据的方法

三、二者关系

数据结构为算法服务,算法作用在特定的数据结构之上

四、常用数据结构

数组、链表、堆、栈、队列、二叉树、散列表、跳表、图、Trie树

五、常用算法

递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法

六、图解

在这里插入图片描述

复杂度分析

一、什么是复杂度分析?

1.数据结构和算法解决是“如何让计算机更快时间、更省空间的解决问题”。

2.因此需从执行时间和占用空间两个维度来评估数据结构和算法的性能。

3.分别用时间复杂度和空间复杂度两个概念来描述性能问题,二者统称为复杂度。

4.复杂度描述的是算法执行时间占用空间数据规模的增长关系

二、为什么要进行复杂度分析?

1.和性能测试相比,复杂度分析有不依赖执行环境、成本低、效率高、易操作、指导性强的特点。

2.掌握复杂度分析,将能编写出性能更优的代码,有利于降低系统开发和维护成本。

三、如何进行复杂度分析?
1、大O表示法

1)来源

算法的执行时间与每行代码的执行次数成正比,用T(n) = O(f(n))表示,其中T(n)表示算法执行总时间,f(n)表示每行代码执行总次数,而n往往表示数据的规模。

2)特点

以时间复杂度为例,由于时间复杂度描述的是算法执行时间与数据规模的增长变化趋势,所以常量阶、低阶以及系数实际上对这种增长趋势不产决定性影响,所以在做时间复杂度分析时忽略这些项。

2、复杂度分析法则

1)单段代码看高频:比如循环。

2)多段代码取最大:比如一段代码中有单循环和多重循环,那么取多重循环的复杂度。

3)嵌套代码求乘积:比如递归、多重循环等

4)多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加。

四、常用的复杂度级别
1、多项式阶

随着数据规模的增长,算法的执行时间和空间占用,按照多项式的比例增长。包括,O(1)(常数阶)、O(logn)(对数阶)、O(n)(线性阶)、O(nlogn)(线性对数阶)、O(n2)(平方阶)、O(n3)(立方阶)

2、非多项式阶

随着数据规模的增长,算法的执行时间和空间占用暴增,这类算法性能极差。包括,O(2^n)(指数阶)、O(n!)(阶乘阶)

3、时间复杂度四大分类

1)最好情况时间复杂度:最好情况时间复杂度:在最理想的情况下,执行这段代码的时间复杂度。

2)最坏情况时间复杂度:最坏情况时间复杂度:在最糟糕的情况下,执行这段代码的时间复杂度。

3)平均情况时间复杂度:执行这段代码的时间复杂度, 也称加权平均时间复杂度或者期望时间复杂度。

4)均摊时间复杂度:在代码执行的所有复杂度情况中绝大部分是低级别的复杂度,个别情况是高级别复杂度且发生具有时序关系时,可以将个别高级别复杂度均摊到低级别复杂度上。基本上均摊结果就等于低级别复杂度。(特殊的平均时间复杂度)

数组(Array)

一、线性表

数据排成线一样的结构,线性表上的数据只有前后两个方向;链表、队列、栈等也是线性表结构

二、非线性表

数据之间不是简单的前后关系,比如二叉树、堆、图

三、特点

数组是一种线性表数据结构,用一组连续的内存空间,来存储一组具有相同类型的数据。

数组从0开始编号

查询效率较高,可根据下标随机访问;插入与删除效率较低,因为要保证内存数据的连续性,每次插入与删除基本都需要移动其它数据(操作数组最末尾数据不需要)

四、数组容器:ArrayList

ArrayList优势:封装数组操作细节,支持动态扩容,存储空间不够,自动扩容为1.5倍大小;创建ArrayList的时候指定数据大小,可节约扩容是的内存申请与数据搬移的性能损耗

ArrayList无法存储基本数据类型,当需要存储基本数据类型时,会将其转换成对应的包装类再进行存储

链表(Linked list)

一、缓存

提高数据读取性能的技术,常见的有CPU缓存、数据库缓存、浏览器缓存等

二、缓存淘汰策略

缓存占满时清除数据的优先顺序

1、先进先出策略FIFO(First In, Fist Out)

2、最少使用策略LFU(Least Frequently Used)

3、最近最少使用策略LRU(Least Recently Used)

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

三、特点

删除插入效率较高,不需要移动其它数据;查询效率较低,需要逐一遍历节点数据查找

常见链表结构:单链表、双向链表、循环链表

四、单链表

每个链表节点除了存储数据之外,还需要记录链表上下一个节点的地址,这个记录下个节点地址的指针叫做后继指针next;第一个节点叫做头结点,最后一个节点叫做尾节点,尾节点指针指向下一个空地址NULL

五、循环链表

特殊的单链表,与单链表的区别为尾节点不指向空地址Null,而是指向链表的头节点,从链尾到链头比较方便

六、双向链表

每个链表节点除了存储数据,还有记录前后节点地址的前继指针prev与后继指针next,最后一个节点的后继指针指向第一个节点的前继指针

与引用类似,都是存储所指对象的内存地址

赋值变量给指针,就是赋值变量的地址给指针,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量

存储同样数量的数据时,双向链接明显比单链表占用更多的空间。因其双指针支持双向遍历,所以查询、插入、删除等操作要比单链表高效。

栈(Stack)

一、特点

“操作受限”的线性表,只允许在一端插入和删除数据

具有先进后出、后进先出的特性

用数组实现的栈叫做顺序栈,用链表实现的栈叫做链式栈

二、时间复杂度

不管是基于数组还是链表的栈,入栈push()、**出栈pop()**的时间复杂度都为O(1),动态扩容的顺序栈使用均摊时间复杂度分析方法之后,其时间复杂度也为O(1)

三、栈帧

操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”;栈用来存储函数调用的临时变量,临时变量被称为栈帧;当函数调用开始进入“栈”中时,栈帧依次入栈,当函数调用完成时,栈帧依次出栈

队列(Queue)

一、特点

具有先进先出、后进后出的特性

操作受限的线性表结构

只支持两个基本操作:入队enqueue(),放一个数据到队列尾部;出队dequeue(),从队列头部取一个元素。

用数组实现的叫做顺序队列,用链表实现的叫做链式队列

队列拥有两个指针:一个head指针指向队头,一个tail指针指向队尾

数据入队与出队的时间复杂度都为O(1),当队尾无法插入新数据且队列数据未满发生数据搬移时,使用均摊时间复杂度分析方法之后,入队操作时间复杂度也为O(1)

二、循环队列

对头与队尾相连,形成闭环

循环队列优点:可以解决数据搬移问题

循环队列缺点:代码实现较复杂,关键要确认好队空和队满的判定条件

队满时,(队尾下标+1)%队列长度=对头下标,(tail+1)%n=head

队满时,循环队列会浪费一个数组的存储空间

三、阻塞队列

在队列基础上增加了阻塞操作

如果队列为空,从队头取数据会被阻塞,无法取到数据;如果队列满了,从队尾插入数据会被阻塞,无法插入数据

阻塞队列 可以定义为生产者-消费者模型

四、并发队列

在多线程情况下,会有多个线程同时操作队列,存在线程安全问题

线程安全的队列叫做并发队列

最简单的实现方法是在enqueue()、dequeue()方法上加锁。同一时刻仅允许一个存或者取操作

递归(Recursion)

一、什么是递归?

1、递归是一种非常高效、简洁的编码技巧,一种应用非常广泛的算法,比如DFS深度优先搜索、前中后序二叉树遍历等都是使用递归。

2、方法或函数调用自身的方式称为递归调用,调用称为递,返回称为归。

3、基本上,所有的递归问题都可以用递推公式来表示,比如

f(n) = f(n-1) + 1;
f(n) = f(n-1) + f(n-2);
f(n)=n*f(n-1);

4、本身借助栈实现

二、递归的优缺点

1.优点:代码的表达力很强,写起来简洁。

2.缺点:空间复杂度高、有堆栈溢出风险、存在重复计算、过多的函数调用会耗时较多等问题。

三、使用递归解决问题需满足的条件

1、一个问题可以分解为几个子问题的解。何为子问题?就是数据规模更小的问题。

2、这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样

3、存在递归终止条件

四、递归理解

递归关键为:1、递推公式 2、终止条件

只要遇到递归,就把它抽象为一个递推公式,不用想每层的调用关系也不要去分解递推的步骤,一切从简。

五、递归常见问题及解决方案

1.堆栈溢出:可以声明一个全局变量来控制递归的深度,从而避免堆栈溢出。

2.重复计算:通过某种数据结构来保存已经求解过的值,从而避免重复计算。

六、如何将递归改写为非递归代码?

笼统的讲,所有的递归代码都可以改写为迭代循环的非递归写法。

如何做?抽象出递推公式、初始值和边界条件,然后用迭代循环实现。

排序(Sort)

一、排序算法
1、怎么衡量排序算法的执行效率?

1.最好情况、最坏情况、平均情况时间复杂度

2.时间复杂度的系数、常数 、低阶

3.比较次数和交换(或移动)次数

2、排序算法的内存消耗

通过空间复杂度来衡量

原地排序算法:特指空间复杂度为O(1)的排序算法

3、排序算法的稳定性

稳定性:如果待排序的序列中存在值相等的元素,排序过后,值相等元素之间原有的先后顺序不变

符合稳定性的排序算法叫做稳定的排序算法,不符合稳定性的排序算法叫做不稳定的排序算法

二、冒泡排序(Bubble Sort)
1、如何排序?

只操作相邻的两个数据,每次冒泡操作笔记相邻两个元素,根据大小关系判断是否交换位置。一次冒泡至少会让一个元素移动到它应该在的位置,重复n次,就完成n个数据的排序

2、冒泡排序归类

冒泡排序是原地排序算法,冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为O(1)

冒泡排序是稳定的排序算法,当有相邻的两个元素大小相等的时候,相同大小的数据在排序前后不会改变顺序

3、时间复杂度

冒泡排序最好情况时间复杂度为O(n),要排序的数据已经是有序的了只需要进行一次冒泡操作;最坏情况时间复杂度为O(n2),要排序的数据刚好是倒序排列的,我们需要进行n次冒泡操作;所以冒泡排序的复杂度需要用平均时间复杂度来恒定

冒泡排序包含两个操作原子,比较交换。数组逆序度确定,交换次数确定,所以复杂度主要看比较操作,比较操作的复杂度上限是O(n2),所以平均情况下的时间复杂度就是O(n2)。

4、有序度

有序度:数组中具有有序关系的元素对的个数,两个元素为一对

eg:(2,4,3,1)这组数据有序度为2,(2,4)和(2,3)这两对

完全有序的数组的有序度叫做满有序度n*(n-1)/2

eg:(2,4,3,1)这组数据满有序度为4*(4-1)/2=6

逆序度与有序度相反,默认从小到大为有序

逆序度=满有序度-有序度,排序的过程就是一种增加有序度,减少逆序度,最后达到满有序度完成排序的过程

三、插入排序(Insertion Sort)
1、如何排序

插入数据时遍历数组,找到数据应该插入的位置将其插入;插入数据依旧能保持数据有序

插入排序将数组中的数据分为两个区间,已排序区间未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。

插入排序也包含两种操作,一种是元素的比较,一种是元素的移动

移动次数等于逆序度

2、插入排序归类

插入排序是原地排序算法,插入排序算法的运行并不需要额外的存储空间,所以空间复杂度是O(1)

插入排序是稳定的排序算法,对于值相同的元素,我们可以选择将后面出现的元素,插入到前面出现元素的后面,这样就可以保持原有的前后顺序不变

3、时间复杂度

最好是时间复杂度为O(n),要排序的数据已经是有序的,并不需要搬移任何数据,每次只需要比较一个数据就能确定插入的位置;最坏情况时间复杂度为O(n2),如果数组是倒序的,每次插入都相当于在数组的第一个位置插入新的数据,所以需要移动大量的数据。

在数组中插入一个数据的平均时间复杂度是O(n),所以插入排序平均时间复杂度为O(n2)。

四、选择排序(Selection Sort)
1、如何排序

将数据分为两个区间,已排序区间和未排序区间。选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。

2、选择排序归类

选择排序是原地排序算法,空间复杂度是O(1)

选择排序是不稳定的排序算法,选择排序每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性。

3、时间复杂度

选择排序的最好情况时间复杂度、最坏情况和平均情况时间复杂度都为O(n2)。初始已排序区间数据未0,数据为满有序度时依旧会将未排序区间最小数据放入已排序区间

五、归并排序
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构与算法学习笔记 的相关文章

  • Java new Date() 打印

    刚刚学习 Java 我知道这可能听起来很愚蠢 但我不得不问 System out print new Date 我知道参数中的任何内容都会转换为字符串 最终值是 new Date 返回对 Date 对象的引用 那么它是如何打印这个的呢 Mo
  • Spring Batch 多线程 - 如何使每个线程读取唯一的记录?

    这个问题在很多论坛上都被问过很多次了 但我没有看到适合我的答案 我正在尝试在我的 Spring Batch 实现中实现多线程步骤 有一个包含 100k 条记录的临时表 想要在 10 个线程中处理它 每个线程的提交间隔为 300 因此在任何时
  • 如何默认将 Maven 插件附加到阶段?

    我有一个 Maven 插件应该在编译阶段运行 所以在项目中consumes我的插件 我必须做这样的事情
  • 为什么 i++ 不是原子的?

    Why is i Java 中不是原子的 为了更深入地了解 Java 我尝试计算线程中循环的执行频率 所以我用了一个 private static int total 0 在主课中 我有两个线程 主题 1 打印System out prin
  • Java中反射是如何实现的?

    Java 7 语言规范很早就指出 本规范没有详细描述反射 我只是想知道 反射在Java中是如何实现的 我不是问它是如何使用的 我知道可能没有我正在寻找的具体答案 但任何信息将不胜感激 我在 Stackoverflow 上发现了这个 关于 C
  • 在画布上绘图

    我正在编写一个 Android 应用程序 它可以在视图的 onDraw 事件上直接绘制到画布上 我正在绘制一些涉及单独绘制每个像素的东西 为此我使用类似的东西 for int x 0 x lt xMax x for int y 0 y lt
  • Final字段的线程安全

    假设我有一个 JavaBeanUser这是从另一个线程更新的 如下所示 public class A private final User user public A User user this user user public void
  • Android MediaExtractor seek() 对 MP3 音频文件的准确性

    我在使用 Android 时无法在eek 上获得合理的准确度MediaExtractor 对于某些文件 例如this one http www archive org download emma solo librivox emma 01
  • 反射找不到对象子类型

    我试图通过使用反射来获取包中的所有类 当我使用具体类的代码 本例中为 A 时 它可以工作并打印子类信息 B 扩展 A 因此它打印 B 信息 但是当我将它与对象类一起使用时 它不起作用 我该如何修复它 这段代码的工作原理 Reflection
  • 如何为俚语和表情符号构建正则表达式 (regex)

    我需要构建一个正则表达式来匹配俚语 即 lol lmao imo 等 和表情符号 即 P 等 我按照以下示例进行操作http www coderanch com t 497238 java java Regular Expression D
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • 总是使用 Final?

    我读过 将某些东西做成最终的 然后在循环中使用它会带来更好的性能 但这对一切都有好处吗 我有很多地方没有循环 但我将 Final 添加到局部变量中 它会使速度变慢还是仍然很好 还有一些地方我有一个全局变量final 例如android Pa
  • 如何在 javadoc 中使用“<”和“>”而不进行格式化?

    如果我写
  • 如何在控制器、服务和存储库模式中使用 DTO

    我正在遵循控制器 服务和存储库模式 我只是想知道 DTO 在哪里出现 控制器应该只接收 DTO 吗 我的理解是您不希望外界了解底层域模型 从领域模型到 DTO 的转换应该发生在控制器层还是服务层 在今天使用 Spring MVC 和交互式
  • Android 中麦克风的后台访问

    是否可以通过 Android 手机上的后台应用程序 服务 持续监控麦克风 我想做的一些想法 不断聆听背景中的声音信号 收到 有趣的 音频信号后 执行一些网络操作 如果前台应用程序需要的话 后台应用程序必须能够智能地放弃对麦克风的访问 除非可
  • 获取 JVM 上所有引导类的列表?

    有一种方法叫做findBootstrapClass对于一个类加载器 如果它是引导的 则返回一个类 有没有办法找到类已经加载了 您可以尝试首先通过例如获取引导类加载器呼叫 ClassLoader bootstrapLoader ClassLo
  • 在 Maven 依赖项中指定 jar 和 test-jar 类型

    我有一个名为 commons 的项目 其中包含运行时和测试的常见内容 在主项目中 我添加了公共资源的依赖项
  • 使用 JMF 创建 RTP 流时出现问题

    我正处于一个项目的早期阶段 需要使用 RTP 广播DataStream创建自MediaLocation 我正在遵循一些示例代码 该代码目前在rptManager initalize localAddress 出现错误 无法打开本地数据端口
  • JGit 检查分支是否已签出

    我正在使用 JGit 开发一个项目 我设法删除了一个分支 但我还想检查该分支是否已签出 我发现了一个变量CheckoutCommand但它是私有的 private boolean isCheckoutIndex return startCo
  • 使用 xpath 和 vtd-xml 以字符串形式获取元素的子节点和文本

    这是我的 XML 的一部分

随机推荐