笔记-数据结构和算法

一、数据结构
1、定义和起源
  • 定义:是指相互之间存在一种或多种特定关系的数据元素的集合。简单理解,就是把一堆基本的数据,按照某种顺序给揉成一坨。
  • 起源:早期是人们用计算机来处理现实中非数值类型的问题,而使用的科学手段,随后被外国人慢慢系统化。
2、数据与结构

  数据结构顾名思义,可以拆分为数据和结构2部分,接下来我们先将数据结构,拆分为数据和结构来分析。

(1)、关于数据
  基本概念和术语

  • 数据:是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。这里的符号说的就是数据,可被输入计算机中,能被程序处理。

  • 数据元素:组成数据的,有一定意义的基本单位。在计算机中通常作为整体处理,也成为记录。简单理解就是,组成数据的单条记录。比如一个班级,是由所有学生共同组成的。那么班里每一个学生数据就是班级数据的一个数据元素。比如学生张三,李四就是是班级数据中的2条数据元素,记录在计算机中,也可以称为2条记录。

  • 数据项:一个数据元素可以由若干个数据想组成。数据项是数据不可分割的最小单位。比如人有眼,耳,鼻,嘴等。可以简单类比为类中的基本类型属性。

  • 数据对象:是性质相同的数据元素的集合, 是数据的子集。

 eg:对于上面的概念和定义不是很清楚,我用一个例子说明一下。比如有一个学校,那么班级就是数据对象,学生就是数据对象中的数据元素,学生的年龄,性别就是数据元素中的数据项。如下图:

 数据:所有班级的数据表时间复杂度分析
 数据对象:每张表就是一个数据对象
 数据元素:每一条记录,就是一条数据元素。
 数据项:姓名,年龄,性别为数据项。

(2)、关于结构
 结构的定义:简单的理解就是关系, 比如分子结构。严格点说, 是指各个组成部分相互搭配和排列的方式。
 数据结构的定义:数据结构就是指相互之间存在一种或多种特定关系的数据元素的集合。有2中结构逻辑结构与物理结构。

  a、逻辑结构
  是指数据对象中数据元素之间的相互关系。 其实这也是我们今后最需要关注的问题。逻辑结构分为以下四种:

  集合结构:集合结构中的数据元素除了同属于一个集合外, 它们之间没有其他关系。 类似于数学中的集合,如哈希表。

  线性结构: 线性结构中的数据元素之间是一对一的关系。如数组,栈,队列等。

  树形结构: 树形结构中的数据元素之间存在一种一对多的层次关系。

  图形结构: 图形结构的数据元素是多对多的关系。

  b、物理结构

  是指数据的逻辑结构在计算机内存中的存储形式。存储结构形式有两种:

  顺序存储结构:是把数据元素存放在地址连续的存储单元里, 其数据间的逻辑关系和物理关系是一致的。如数组。
  - 优点:灵活查找,随机存取表中元素。
  - 缺点:插入和删除操作需要移动元素。

  链式存储结构: 是把数据元素存放在任意的存储单元里, 这组存储单元可以是连续的, 也可以是不连续的。如链表。
   - 优点:存储灵活。
   - 缺点:如果某个链点丢失,则无法知道其后的相关链点。

(3)、常见数据结构
  在各个编程语言中,常用到的数据结构有数组,栈,队列,链表,树,图等等,接下来我们要学习和了解的也是这些数据结构。

二、算法
  • 算法定义:是解决特定问题求解步骤的描述, 在计算机中表现为指令的有限序列, 并且每条指令表示一个或多个操作。
  • 与数据结构关系:程序=数据结构+算法,两者组成了这个程序世界,所以是相互区别但又不可分割的。数- - 据结构是数据间的有机关系,算法是对数据的操作步骤。
  • 衡量标准:执行时间和占用空间是衡量一个算法的基本标准.
  • 算法特性: 输入、 输出、 有穷性、 确定性和可行性。
  • 算法设计的要求:正确性、 可读性、健壮性、执行时间快和内存存储省。

算法效率的度量方法

1、事后统计法
  运行一遍代码,统计出执行时间和占用的内存空间,来确定算法的高低。但这样有很大的局限性,具体如下:
需要事先编写好代码,再去运行代码,浪费时间和精力
结果非常依赖测试环境。如不同硬件配置的电脑,执行效率是不一样的。
结果受数据规模的影响很大。如数据的有序度和数目不同,执行所花费的时间是有很大不同的。

2、事前分析估算法
  基于事后统计法的缺陷,人们研究出了一种,在做事前分析估算的方法。即在计算机程序编制前,依据统计方法对算法进行估算。特点如下:不依懒于测试环境;不用具体的测试数据来测试。

3、怎么去估算呢?
  人们发现影响程序在计算机上运行时所消耗的时间因素有下列几种:
  (1)、算法采用的策略、方法;
  (2)、编译产生的代码质量;
  (3)、问题的输入规模;
  (4)、机器执行指令的速度;

  上述因素中(2)和(4)依赖与测试的硬件和软件环境我们可以抛开不计,(1)是算法本身的好坏,那么就只剩(3)即算法本身和问题的输入规模,所以我们只要研究一个程序的运行时间,只需要研究(3)算法的效率和输入规模的关系就可以了。

  这种关系就是我们要说的复杂度了,所以可以桶复杂度分析来度量一个算法的优劣。相比于事前统计,复杂度分析有不依懒于测试环境,不用具体的测试数据来测试等优点。

4、算法的时间和空间复杂度
  什么是复杂度?复杂度也叫渐进复杂度,包括时间复杂度和空间复杂度,用来分析算法执行效率(执行时间与存储空间)与数据规模之间的增长关系。用公式表示就是:

   T(n) = O(f(n))

  其中T(n)表示算法执行总时间,n表示数据的规模的大小,f(n)表示每行代码执行总次数。这样用大写O( )来体现算法时间复杂度的记法, 我们称之为大O记法。这是我们学习算法的重中之重。

三、大O表示法

  我们知道了复杂度,但怎么去分析和表述一个算法的复杂度呢?这个就是我们要重点学习的大O复杂度表示法了。通过大O表示法,来分析一个算法代码的时间复杂度和空间复杂。

1、表示方法
  假设:每一行代码的执行时间为 1 个单位时间 (unit_time)。
  公式:T(n) = O(f(n)) ,其中T(n)表示算法执行总时间,n表示数据的规模的大小,f(n)表示每行代码执行总次数。
  规律:所有代码的执行时间 T(n) 与每行代码的执行次数成正比。

示例:

for(int i=0; i<n; i++){ 
	sum = 1 + 2 * n;
 }

  这个算法的执行次数函数为f(n)=n,随着n增大,执行次数随也之增加,执行时间也会随之增加。

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

分析法则:

  • 单段代码看高频:在单段代码中只关注循环执行次数最多的一段代码。比如循环。
  • 多段代码取最大:在多段代码中只关注循环执行次数最多的一段代码,总复杂度等于量级最大的那段代码的复杂度。比如一段代码中有单循环和多重循环,那么取多重循环的复杂度。
  • 嵌套代码求乘积:比如递归、多重循环等。
  • 多个规模求加法:代码的复杂度由两个数据的规模来决定。比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加。

示例:
(1) 常数阶O(1)

这里的O(1)表示的是常量级时间复杂度的一种表示方法,并不是只执行一行代码。如下,

int a = 0;   /**执行了一次**/
int b = 0;   /**执行了一次**/
int c = a+b;  /**执行了一次**/

  虽然执行了三次,但是复杂度也是O(1),而不是O(3)。总结一下就是,代码执行时间不随n的增大而增长,那么其复杂度就为O(1)。一帮情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行代码,其时间复杂度也是O(1)。

(2) 线性阶O(n)

线性阶的循环结构会复杂很多,下面的代码的时间复杂度为O(n),因为循环体重的代码需要执行n次。如下,

int a = 0;
int i;
for (int i=0; i<n; i++){
    /* 时间复杂度为O(1)的程序步骤序列 */
}

  我们可以分析一下,上面的时间复杂度,O(1)+O(1)+O(n) 。当n很大的时候,O(1)对于时间的影响就微乎其微了,所以可以忽略,则时间复杂度为O(n)。

3、最好、最坏、平均浅析
  一种方法是计算所有情况的平均值, 这种时间复杂度的计算方法称为平均时间复杂度。另一种方法是计算最坏情况下的时间复杂度, 这种方法称为最坏时间复杂度。 一般在没有特殊说明的情况下,都是指最坏时间复杂度。

  假设,我们查找一个有n 个随机数字数组中的某个数字,最好的情况是第一个数字就是,那么算法的时间复杂度为O(1),但也有可能这个数字就在最后一个位置上待着,那么算法的时间复杂度就是O(n),这是最坏的一种情况了。

  最坏情况运行时间是一种保证, 那就是运行时间将不会再坏了。 在应用中, 这是一种最重要的需求, 通常, 除非特别指定, 我们提到的运行时间都是最坏情况的运行时间。

  而平均运行时间也就是从概率的角度看, 这个数字在每一个位置的可能性是相同的, 所以平均的查找时间为n/2次后发现这个目标元素。

  平均运行时间是所有情况中最有意义的, 因为它是期望的运行时间。 也就是说, 我们运行一段程序代码时, 是希望看到平均运行时间的。 可现实中, 平均运行时间很难通过分析得到, 一般都是通过运行一定数量的实验数据后估算出来的。

示例:

int find(int[] array, int n, int x) { 
    int i = 0; 
    int pos = -1; 
    for ( ; i < n; i++) { 
        if (array[i] == x) { 
            pos = i; 
            return pos; 
        } 
    } 
    return pos; 
}

  查找变量 x 出现在数组的位置。最好情况下,如果数组array的第一个数据为要寻找的值x,那么时间复杂度就为O(1)。最坏情况下,如果是最后一个,那么时间复杂度为O(n)。平均情况下,我们知道要查找的位置有n+1中情况:在数据0~n-1位置中和不在数组中,我们只需要算出在每个情况,查找需要遍历的元素个数累加起来,然后除以n+1则为:

 (1+2+3+...+n+n) / (n+1) = n(n+3)/2(n+1) = O(n)

  忽略掉系数、低阶、常量的话,最后简化得到平均时间复杂度为O(n)。这个公式计算稍微有的问题,因为,每种情况下的概率并不是一样的。我们将在数组和不在数组的概率默认一样为1/2,另外,要查的的0~n-1中任意位置的概率也一样,为1/n,那么根据乘法法则,要查找的数据出现在 0~n-1 中任意位置的概率就是 1/(2n)。公式为:

(1+2+3+...+n)/2n + n/2 = (3n+1)/4 = O(n)

4、空间复杂度分析
  空间复杂度全称就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系。分析过时间复杂度,那么空间复杂度就很简单了。对了,我们一般说的复杂度,没有加空间前缀,通常指的是时间复杂度,这个要了解。

示例:

void int i=0;
int[] a = new int[n]
for (i; i < n ; i++) {
	a[i] = i * i;
}

  跟时间复杂度一样,第一行占用了1个存储单位,常量阶的,跟数据规模n没有关系,所以可以忽略。第二行占用了n个存储单位。第三行后面的几行,也是常量阶,可以忽略。所以整个空间复杂度为O(n)。

5、常见复杂度:
  非多项式量级:随着数据规模n越来越大,算法的执行时间会噩梦般的增加。如:O(2^n)(指数阶)、O(n!)(阶乘阶)。
  多项式量级:随着数据规模的增长,算法的执行时间和空间占用,按照多项式的比例增长。如:O(1)(常数阶)、O(n)(线性阶)、O(logn)(对数阶)、O(nlogn)(线性对数阶)、O(n2)(平方阶)、O(n3)(立方阶)。

四、总结

数据结构:

  • 数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。
  • 数据结构按数据间的逻辑关系和存储逻辑关系分为2种结构:逻辑结构与物理结构。
  • 逻辑结构中,按元素之间的关系又包含为:集合结构,线性结构,树形结构,图形结构。
  • 物理结构中,按元素存储的关系分为:顺序存储逻辑,链式存储逻辑。

算法:

  • 算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列, 并且每条指令表示一个或多个操作。
  • 执行时间和占用空间是衡量一个算法好坏的基本标准。
  • 程序=数据结构+算法,两者组成了这个程序世界,所以是相互区别但又不可分割的。数据结构是数据间的- - 有机关系,算法是对数据的操作步骤。
  • 算法具有 输入、 输出、 有穷性、 确定性和可行性的特性。
  • 复杂度也叫渐进复杂度,包括时间复杂度和空间复杂度,用来分析算法执行效率(执行时间与存储空间)与数据规模之间的增长关系。

大O表示法:

  • 时间复杂度全称就是渐进时间复杂度,描述的是算法执行时间与数据规模的增长变化趋势。
  • 空间复杂度全称就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系。
  • 时间复杂度分析分析4原则:单段代码看高频、多段代码取最大、嵌套代码求乘积、多个规模求加法。
  • 常见的复杂度。暴增的非多项式量级和按比例增长的多项式量级。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值