• 数据结构笔记(王道考研) 第一章:绪论


    绪论

    数据结构学习如何用程序代码把现实世界的问题信息化,学习如何用计算机高效地处理这些信息从而创造价值

    在这里插入图片描述

    数据结构的基本概念

    在这里插入图片描述

    数据

    数据是信息的载体,是描述客观事物属性的数,字符及所有能输入计算机中并被计算机程序识别和处理的符号的集合。数据是计算机程序加工的原料。

    数据元素,数据项

    数据元素是数据的基本单位,通常作为一个整体进行考虑和处理

    一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单位。多个数据项可组成组合项

    要根据实际的业务需求来确定什么是数据元素,什么是数据项。

    数据结构,数据对象

    数据结构是相互之间存在一种或多种特定关系的数据元素的集合

    数据对象是具有相同性质的数据元素的集合,是数据的一个子集

    在这里插入图片描述

    数据结构的三要素

    数据的逻辑结构

    数据元素之间的逻辑关系是什么?

    在这里插入图片描述

    集合:各个元素同属一个集合,别无其他关系

    线性结构:数据元素之间是一对一的关系。除了第一个元素,所有元素都有唯一前驱;除了最后一个元素,所有元素都有唯一后继

    树形结构:数据元素之间是一对多的关系

    图结构:数据元素之间是多对多的关系

    数据的物理结构(存储结构)

    探讨如何用计算机表示数据元素的逻辑关系?

     

    以线性结构这种逻辑结构为例。存储结构可分为顺序存储,链式存储,索引存储,散列存储,后三种为非顺序存储

    顺序存储:把逻辑上相邻的元素存储在物理上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现

    链式存储:逻辑上相邻的元素在物理位置上可以不相邻,借助指示元素存储地址的指针来表示元素之间的逻辑关系,用指针表示下一个数据元素的存储地址

    索引存储:在存储元素信息的同时,还建立附加的索引表。索引表中的每项称为索引项,索引项的一般形式是(关键字,地址)

    在这里插入图片描述

    散列存储:根据元素的关键字直接计算出元素的存储地址,又称哈希存储(在第六章的散列表学习)

    以上内容,现在只需要理解以下几点即可

    • 若采用顺序存储,则各个数据元素在物理上必须是连续的;若采用非顺序存储,则各个数据元素在物理上可以是离散的
    • 数据的存储结构会影响存储空间分配的方便程度
    • 数据的存储结构会影响对数据运算的速度

    数据的运算

    施加在数据上的运算包括运算的定义和实现。运算的定义是针对逻辑结构的,指出运算的功能;运算的实现是针对存储结构的,指出运算的具体操作步骤。

    数据类型,抽象数据类型

    数据类型是一个值的集合和定义在此集合上的一组操作的总称,又可分为

    1.原子类型。其值不可再分的数据类型

    2.结构类型。其值可以再分解为若干成分(分量)的数据类型

    抽象数据类型(ADT)是抽象数据组织及与之相关的操作。ADT用数学化的语言定义数据的逻辑结构,定义运算。与具体的实现无关。

    定义一个ADT,就是定义了数据的逻辑结构,数据的运算。也就是定义了一个数据结构。而确定一种存储结构,就意味着在计算机中表示出数据的逻辑结构。存储结构不同,也会导致运算的具体实现不同。确定了存储结构,才能实现数据结构。

    在探讨一种数据结构时:

    1.定义逻辑结构(数据元素之间的关系)

    2.定义数据的运算(针对现实需求,应该对这种逻辑结构进行什么样的运算)

    3.确定某种存储结构,实现数据结构,并实现一些对数据结构的基本运算

    算法的基本概念

    在这里插入图片描述

    什么是算法?

    程序=数据结构+算法,数据结构研究如何把现实世界的问题信息化,将信息存进计算机。同时还要实现对数据结构的基本操作。算法研究如何处理这些信息,解决实际问题

    算法的五个特性

    • 有穷性。一个算法必须总在执行有穷步之后结束,且每一步都可在有穷时间内完成
    • 确定性。算法中每条指令必须有确切的含义,对于相同的输入只能得出相同的输出
    • 可行性。算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现
    • 输入。一个算法有0个或多个输入,这些输入取自于某个特定的对象的集合
    • 输出。一个算法有一个或多个输出,这些输出是与输入有着某种特定关系的量

    算法必须是有穷的,要求用有限的步骤解决某个特定的问题。而程序可以是无穷的

    “好”算法的特性

    • 正确性。算法应能够正确地解决求解问题
    • 可读性。算法应具有良好的可读性,以帮助人们理解
    • 健壮性。输入非法数据时,算法能适当地做出反应或进行处理,而不会莫名其妙的输出结果
    • 高效率(执行速度快,时间复杂度低)与低存储量需求(不费内存,空间复杂度低)

    算法可以用伪代码描述,甚至用文字描述,重要的是要“无歧义”地描述出解决问题的步骤

    算法的时间复杂度

    在这里插入图片描述

    如何评估算法时间开销?

    让算法先运行,事后统计运行时间的方法是不科学的。因为存在如下问题

    • 和机器性能有关,如超级计算机和单片机
    • 和编程语言有关,越高级的语言执行效率越低
    • 和编译程序产生的机器指令质量有关
    • 有些算法是不能事后再统计的,如:导弹发射算法

    我们希望评估算法时能排除与算法本身无关的外界因素,且能事先估计,所以有了时间复杂度,以事前预估算法时间开销 T ( n ) T(n) T(n)与问题规模 n n n的关系

    时间复杂度的计算

    • 时间复杂度的计算满足乘法规则和加法规则

    加法规则: T ( n ) = T 1 ( n ) + T 2 ( n ) = O ( f ( n ) ) + O ( g ( n ) ) = O ( m a x ( f ( n ) , g ( n ) ) ) T(n)=T_1(n)+T_2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n))) T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))

    多项相加,只保留最高阶的项且系数变为1

    乘法规则: T ( n ) = T 1 ( n ) + T 2 ( n ) = O ( f ( n ) ) × O ( g ( n ) ) = O ( f ( n ) × g ( n ) ) T(n)=T_1(n)+T_2(n)=O(f(n))\times O(g(n))=O(f(n)\times g(n)) T(n)=T1(n)+T2(n)=O(f(n))×O(g(n))=O(f(n)×g(n))

    多项相乘,都保留

    • 表达式只保留阶数高的部分,如 T ( n ) = n 3 + n 2 + 9999999 T(n)=n^3+n^2+9999999 T(n)=n3+n2+9999999的各项只保留 n 3 n^3 n3

    • 只关心数量级,用大 O O O表示“同阶”,同等数量级。即:当 n → ∞ n\to\infty n时,二者之比为常数

    所以常数项系数可以省略,同时时间复杂度的计算结果用 O ( . . . ) O(...) O(...)的方式表示

    • O ( 1 ) < O ( l o g 2 n ) < O ( n ) < O ( n l o g 2 n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1)O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)

    可以结合洛必达法则和函数图像来证明,用“常对幂指阶”来记忆

    • 顺序执行的代码只会影响最终结果的表达式的常数项,可以忽略。

    • 考虑循环语句时,只需挑循环中的一个基本操作分析它的执行次数与 n n n的关系即可

    • 如果有多层嵌套循环,只需关注最深层循环的循环次数与 n n n的关系

    • 很多算法执行时间与输入的数据有关。这种时候就要考虑最好时间复杂度和最坏时间复杂度,平均时间复杂度

    最坏时间复杂度:最坏情况下算法的时间复杂度

    平均时间复杂度:所有输入示例等概率出现的情况下,算法的期望运行时间

    最好时间复杂度:最好情况下算法的时间复杂度

    算法的空间复杂度

    在这里插入图片描述

    空间复杂度的计算

    基本上可以类比时间复杂度的计算

    现在要运行一个程序,程序运行前会先把程序代码(这里的代码是源代码编译后生成的机器指令)放到内存中(大小固定,与问题规模无关)。接下来CPU会一行行的执行这些代码,内存中开辟空间存放局部变量和参数,数组和其他信息。

    • 如果无论问题规模怎么变,算法运行所需的内存空间都是固定的常量,算法空间复杂度为 S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)。此时可以说算法原地工作。
    • 只需关注存储空间大小与问题规模相关的变量。表达式中的常数项可省略
    • 由于只关心数量级,计算时没必要考虑不同类型变量所占用的内存空间大小上的差异
    • 同样遵循加法和乘法原则,上一节用于比较数量级的不等式也适用于空间复杂度的计算

    函数递归调用带来的内存开销

    递归过程中每加深一层的调用都需要把这一层的局部变量,参数等在内存中开辟一块新的空间用于存储

    • 只需关注存储空间大小与问题规模相关的变量。表达式中的常数项可省略
    • 由于只关心数量级,计算时没必要考虑不同类型变量所占用的内存空间大小上的差异
    • 同样遵循加法和乘法原则,上一节用于比较数量级的不等式也适用于空间复杂度的计算

    函数递归调用带来的内存开销

    递归过程中每加深一层的调用都需要把这一层的局部变量,参数等在内存中开辟一块新的空间用于存储

    • 绝大多数情况下空间复杂度=递归调用的深度,有些情况则要具体分析。
  • 相关阅读:
    微信H5跳转微信小程序
    网站优化搜索引擎与关键词
    Python中的*args 和 **kwargs
    UE4蓝图节点不同颜色代表
    【SimpleFunction系列一】SpringBoot整合Redis(含序列化问题)
    P1 缓冲池管理
    Linux编辑器-gcc/g++使用
    java计算机毕业设计网上汽车售票系统源码+数据库+lw文档+系统+部署
    【C++】·搭建HTTP服务器
    Java 开发者必备:一文解决 AES 加密中的“非法密钥大小”异常
  • 原文地址:https://blog.csdn.net/zimuzi2019/article/details/126247129