type
status
date
slug
summary
tags
category
icon
password
@ZZHow(ZZHow1024)
参考课程:
哔哩哔哩-【C语言描述】《数据结构和算法》
P1_数据结构和算法绪论
数据结构和算法绪论
数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科。
- 程序设计 = 数据结构 + 算法
- 数据结构就是关系,就是数据元素相互之间存在的一种或多种特定关系的集合。
逻辑结构和物理结构
- 数据结构分为 逻辑结构 和 物理结构
- 逻辑结构:数据对象中数据元素之间的相互关系,也是我们今后最需要关注和讨论的问题。
- 物理结构:数据的逻辑结构在计算机中的存储形式。
- 四大逻辑结构:
- 集合结构
- 线性结构
- 树形结构
- 图形结构
- 物理结构:
- 根据物理结构的定义,我们实际上研究的的就是如何把数据元素存储到计算机的存储器中。
- 存储器主要是针对内存而言的,像硬盘、软盘光盘等外部存储器的数据组织通常用文件结构来描述。
- 数据元素的存储结构形式有两种:
- 顺序存储:
- 把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。
- 例如 数组
- 链式存储
- 把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。
- 链式存储结构的数据元素存储关系并不能反映其逻辑关系,因此需要用一个指针存放数据元素的地址,这样子通过地址就可以找到相关联数据元素的位置。
P2_谈谈算法
算法的特性
- 算法的五个基本特征:
- 输入
- 输出
- 有穷性
- 确定性
- 可行性
- 输入:
- 算法具有零个或多个输入。
- 尽管对于绝大多数算法来说,输入参数都是必要的,但是有些时候只需要打印一个字符串,就不需要参数了。
- 输出:
- 算法至少有一个或多个输出。
- 算法是一定要输出的,不需要它输出,那你要这个算法来干啥?
- 输出的形式可以是打印形式输出,也可以是返回一个值或多个值等。
- 有穷性:
- 算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。
- 确定性:
- 算法的每一个步骤都具有确定的含义,不会出现二义性。
- 算法在一定条件下,只有一条执行路径,相同的输入只能有唯一的输出结果。
- 算法的每个步骤都应该被精确定义而无歧义。
- 可行性
- 算法的每一步都必须是可行的,也就是说,每一都能够通过执行有限次数完成。
算法设计的要求
- 尽管算法不唯一,但我们要学习掌握一些好的算法,对我们解决问题很有帮助!
- 算法设计的要求:
- 正确性
- 算法的正确性是指算法至少应该具有输入、输出和一加工处理无歧义性、能正确反映问题的需求、能够得到问题的正确答案。
- 大体分为以下四个层次:
- 算法程序没有语法错误。
- 算法程序对于合法输入能够产生满足要求的输出。
- 算法程序对于非法输入能够产生满足规格的说明。
- 算法程序对于故意刁难的测试输入都有满足要求的输出结果。
- 可读性:
- 算法设计另一目的是为了便于阅读、理解和交流。
- 我么写代码的目的,一方面是为了让计算机执行,但还有一个重要的目的是为了便于他人阅读和自己日后阅读修改。
- 健壮性:
- 当输入数据不合法时,算法也能做出相关处理,而不是产生异常、崩溃或莫名其妙的结果。
- 时间效率高和存储量低
P3_时间复杂度和空间复杂度1
算法效率的度量方法
- 事后统计方法:
- 这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。
- 事后统计方法的缺陷:
- 必须依据算法事先编制好测试程序,通常需要花费大量时间和精力。
- 不同测试环境差别不是一般的大!
- 事前分析估算方法:在计算机程序编写前,依据统计方法对算法进行估算。
- 经过总结,我们发现一个高级语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
- 算法采用的策略,方案
- 编译产生的代码质量
- 问题的输入规模
- 机器执行指令的速度
- 一个程序的运行时间依赖于算法的好坏和问题的输入规模。(所谓的问题输入规模是指输入量的多少)
函数的渐近增长
- 给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的 n > N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐近快于g(n)。
- 当n的值变得非常大的时候,3n+1已经没法和2n^2的结果相比较,最终几乎可以忽略不计。
- 结论:判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高项)的阶数。
- 注意:判断一个算法好不好,我们只通过少量的数据是不能做出准确判断的,很容易以偏概全。
P4_时间复杂度和空间复杂度2
算法时间复杂度
- 算法时间复杂度的定义:
- 在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。
- 算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))。
- 它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。
- 其中f(n)是问题规模n的某个函数。
- 关键:需要知道执行次数 == 时间
- 这样用大写O( )来体现算法时间复杂度的记法,我们称之为大O记法。
- 一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法。
- 显然,由此算法时间复杂度的定义可知,我们的三个求和算法的时间复杂度分别为O(1),O(n),O(n^2)。
推导大O阶方法
- 推导攻略:
- 用常数1取代运行时间中的所有加法常数。
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数。
- 得到的最后结果就是大O阶。
- 例子:
时间复杂度是O(1)
线性阶
- 一般含有非嵌套循环涉及线性阶,线性阶就是随着问题规模n的扩大,对应计算次数呈直线增长。
循环体中的代码需要执行n次
时间复杂度是O(n)
平方阶
- 循环嵌套
n等于100,也就是说外层循环每执行一次,内层循环就执行100次,那总共程序想要从这两个循环出来,需要执行100*100次,也就是n的平方。
时间复杂度是O(n^2)
- 若是三个这样的嵌套循环,则时间复杂度就是O(n^3)。
- 结论:循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。
- 内外循环次数不相等的循环嵌套
总的执行次数是 n + (n-1) + (n-2) + … + 1 = n(n+1) / 2 = n ^ 2 / 2 + n / 2
用推导大O阶的攻略:
第一条,忽略,因为没有常数相加。
第二条,只保留最高项,所以n/2这项去掉。
第三条,去除与最高项相乘的常数,最终得O(n^2)。
时间复杂度是O(n ^ 2)
对数阶
- 由于每次i*2之后,就举例n更近一步,假设有X个2相乘后大于或等于n,则会退出循环。
- 于是由2^x= n得到x= log(2)n
这个循环的时间复杂度是O(lg n)
P5_时间复杂度和空间复杂度3
函数调用的时间复杂度分析
- 例1:
function函数的时间复杂度是O(1)
整体的时间复杂度就是循环的次数O(n)
- 例2:
function内部的循环次数随count的增加(接近n)而减少
根据攻略,算法的时间复杂度为O(n^2)
- 例3:
O(1) + O(n) + O(n^2) + O(n^2) —> O(n^2)
常见的时间复杂度
常用的时间复杂度所耗费的时间从小到大依次是:
O(1) < O(lg n) < (n) < O(nlg n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
最坏情况与平均的情况
- 算法的分析也是类似,我们查找一个有n个随机数字数组中的某个数字,最好的情况是第一个数字就是,那么算法的时间复杂度为O(1),但也有可能这个数字就在最后一个位置,那么时间复杂度为O(n)。
- 平均运行时间是期望的运行时间。
- 最坏运行时间是一种保证。在应用中,这是一种最重要的需求,通常除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
算法的空间复杂度
- 我们可以用空间来换时间。
- 算法的空间复杂度通过计算算法所需的存储空间实现,算法的空间复杂度的计算公式记作:
- S(n)=O(f(n))
- 其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。
- 通常用“时间复杂度”来指运行时间的需求,用“空间复杂度”指空间需求。
- 当直接要让我们求“复杂度”时,通常指的是时间复杂度。