数据结构与算法笔记——1绪论

学习自《数据结构与算法 Python语言描述》机械工业出版社。记录一些要点,以供加深记忆和日后查阅。

1. 算法和算法分析

1.1 问题、实例和算法

三个基本概念:

  1. 问题:需要解决的一个具体需求。
  2. 实例:以上问题的一个具体例子。
  3. 算法:一种计算过程的严格描述。

算法的性质:

  1. 有穷性:算法的描述由有限条语句构成,对任何问题在有限的时间内获得解答。
  2. 可行性:语句严格、简单明确,可以被机械执行。
  3. 确定性:对于确定的输入产生确定的输出。
  4. 明确的输入/输出。

算法的描述:

  1. 自然语言描述:容易阅读,冗长繁琐,易有歧义。
  2. 数学公式描述:更加简洁严格。
  3. 形式化记法描述:如图灵机模型。
  4. 编程语言描述:简洁清晰,涉及语言细节,不易移植。
  5. 伪代码描述:不拘泥于具体语言形式。

算法思想(设计模式):

  1. 枚举法:在解决简单问题时十分有效。
  2. 贪心法:由部分解逐步扩充得到整体的解。
  3. 分治法:把复杂问题分解为相对简单的子问题。
  4. 回溯法:通过探索的方式求解,当后面的求解步骤无法继续时,退回到前面的步骤,另行选择求解路径。
  5. 动态规划法:在前面的步骤中积累信息,在后续步骤中使用已知信息。
  6. 分支限界法:在搜索的过程中逐步缩小求解的范围。

1.2 算法的代价及其度量

算法分析就是很对一个具体算法,设法确定一种函数关系,以问题实例的某种规模n为参量,反映出这个算法在处理该规模实例时所需要付出的时间或空间代价。

  1. 实例的规模采用什么来度量?方阵的行数、元素个数还是维度
  2. 完成某一算法平均需要多少时间,最长需要多少时间?

对于抽象的算法,通常无法做出精确度量。在这种情况下只能退而求其次,设法估计算法复杂性的量级。对于算法的时间和空间性质,最重要的是其量级和趋势,这些是代价的主要部分,而代价函数的常量因子可以忽略不计。

大O记法:对于单调的整数函数f,如果存在一个整数函数g和实常数c>0,使得对于充分大的n总有f(n)<=c*g(n),就说函数g是f的一个渐进函数,记为f(n)=O(g(n))。由此可以描述算法的时间复杂度和空间复杂度。

常用渐进复杂度函数:

O(1), O(log n), O(n), O(n log n), O(n^2), O(n^3), O(2^n)

算法的复杂度反过来决定了算法的可用性。如果算法的复杂度较低,就可能用于解决很大的实例,而复杂度很高的算法只能用于很小的实例。

1.3 算法分析

考虑最基本的循环程序,其中只有顺序组合、条件分支和循环结构。分析这种算法只需要几条基本计算规则:

(1)基本操作,认为其时间复杂度为O(1)。如果是函数调用,应将其时间复杂度代入,参与整体时间复杂度的计算。

(2)加法规则(顺序复合)。如果算法是两个或多个部分的顺序复合,其复杂度是这两部分或多个部分之和。

T(n)=T1(n)+T2(n)=O(T1(n)+T2(n))=O(max(T1(n),T2(n)))

由于忽略了常量因子,加法等价于求最大值。

(3)乘法规则(循环结构)。如果算法是一个循环,循环体将执行T1(n)次,每次执行需要T2(n)时间,那么

T(n)=T1(n)*T2(n)=O(T1(n))*O(T2(n))=O(T1(n)*T2(n))

(4)取最大规则(分支结构)。如果算法是条件分支,两个分支的时间复杂性分别为T1(n)和T2(n),则有

T(n)=O(max(T1(n),T2(n)))

1.4 Python程序的计算代价

1.4.1 时间开销

  • 基本算术运算和逻辑运算是常量时间的。
  • 组合对象的操作有些是常量时间的,有些不是。
  1. 复制和切片操作通常需要线性时间;
  2. list和tuple的元素访问和元素赋值是常量时间的;
  3. dict的操作情况比较复杂。
  • 字符串也应当看作组合对象,其许多操作不是常量时间的。
  • 创建对象也需要付出时间和空间,其代价都与对象的大小有关。
  • 构造新结构。构造新的空结构(空表、空集合等)是常量时间操作,而构造一个包含n个元素的结构则需要O(n)时间。
  • 关于list:表元素访问和元素修改是常量时间操作,但一般的加入/删除元素操作是O(n)的操作。
  • 关于字典:一般效率较高,但偶尔也会出现效率低的情况。
  • 在表的最后加入和删除元素的效率高,而在中间位置插入和删除元素的效率低,应优先选择前者。

1.4.2 空间开销

在程序里使用任何类型的对象,都需要付出空间的代价。

相对而言,表和元组是比较简单的结构,而集合和字典需要支持快速查询等操作,其结构更加复杂。

  • python中各种组合数据对象都没有预设的最大元素个数,这些结构可以根据元素个数的增长自动扩充空间。
  • 组合对象的实际空间开销在存续期内可能变大,但通常不会自动缩小(即使后来元素个数变少了)。

在程序开发时,不但要选择好的算法,还要考虑每一步的良好实现。

Python等高级程序语言存在一些“效率陷阱”,可能会大量浪费计算机的时间和空间。

2. 数据结构

用计算机解决问题,可以看作实现某种信息表示的转换。

需要处理的信息越复杂,处理计算过程越复杂,良好的数据组织结构就越重要。

2.1 数据结构及其分类

2.1.1 信息、数据和数据结构

信息:任何存在着的事物,其形态和运动都蕴含着信息。

数据:计算机程序能够处理的符号形式的总和,即编码的信息。

数据结构:研究数据之间的关联和组合的形式,总结其中的规律,发掘特别值得注意的有用结构,研究这些结构的性质,进而研究如何在计算机里实现这些有用的数据结构,以支持响应组合数据的高效使用,支持处理它们的高效算法。

2.1.2 抽象定义与重要类别

一般定义:一个具体的数据结构就是一个二元组

D=(E,R)

E是数据结构D的元素集合,R是D的元素之间的某种关系。

典型数据结构:

  • 集合:元素间没有明确关系,R为空。
  • 序列结构:数据元素间有一个明确的先后关系,存在一个排位在最前的元素,除了最后的元素外,每个元素都一个唯一的后继元素。R为线性顺序关系。这种结构也称为线性结构。还有一些变形,如环形结构。
  • 层次结构:数据元素属于一些不同的层次,一个上层元素可以关联一个或多个下层元素。
  • 树形结构:层次结构中最简单的一种。只有一个最上层元素,称为根,,除根之外的每个元素,都有且仅有一个上层元素与之关联。
  • 图结构:数据元素之间可以有任意复杂的互相联系。广义,包含上面几种,上面几种可以看作图的受限形式。

另一类数据结构:并没有对其元素的相互关系提出任何结构性的规定,而是要求其在某种计算中实现有用的功能,最基本的有数据元素的存储和访问。

功能性数据结构:栈、队列、优先队列、字典等。

2.2 计算机内存对象表示

 

内存是CPU可以直接访问的数据存储设备,保存在外存里的数据必须先装入内存,而后CPU才能使用它们。

内存单元具有唯一编号,成为单元地址。全部可用地址为从0开始的一个连续的正整数区间。

在程序执行中,对内存单元的访问都通过地址进行。64位计算机一次可以存取8个字节的数据。

基于地址访问内存单元是一个O(1)操作,与单元的位置或整个内存的大小无关。

 

在计算机内存里表示数据元素之间的联系,只有两种基本技术:

  1. 顺序表示:利用数据元素的存储位置隐式表示。显然序列数据类型中的元素线性关系可以用这种方式表示。
  2. 把数据元素之间的联系也看作一种数据,显式地保存在内存中。用这种方式可以表示数据元素之间任意复杂的关系,因此这种技术的功能更强大。

2.3 Python对象和数据结构

Python语言的实现基于一套精心设计的链接结构,变量与其值对象的关联通过链接的方式实现,对象之间的联系同样通过链接。

Python程序内部有一个存储管理系统,负责管理可用内存,为各种对象安排存储,支持灵活有效的内存使用。程序中要求建立对象时,管理系统就会为其安排存储,某些对象不再有用时则回收其占用的存储。管理系统屏蔽了具体内存使用的细节,大大减少了编程人员的负担。

 

posted @ 2021-08-13 11:07  叮叮当当sunny  阅读(187)  评论(0编辑  收藏  举报