数据结构与算法学习之时间复杂度和空间复杂度

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/printfcc/article/details/79167384

转载请注明出处谢谢: http://blog.csdn.net/printfcc/article/details/79167384

开始学习数据结构与算法,努力坚持更新,加油加油~ 下面对学习到的知识做一下记录,也希望能帮到其他小伙伴学习。如有错误望大神指出。ps:答应我,你要看到最后。

目录

首先让我们来回顾一下一些数学基础

这里写图片描述
这里写图片描述
这里写图片描述

数据结构与算法的关系

okok帮你们回顾完上面的数学基础(其实是自己忘记想看,okok:) ,可能有人会好奇
为什么 数据结构和算法要在一起教呢?为什么叫数据结构与算法?什么?没人会问?

这里写图片描述

在1968年,美国的高德纳(DonaU E. Knuth)教授在其所写的《计算机程序设计艺术》第一卷《基本算法》中,
较系统地阐述了数据的逻辑结构和存储结构及其操作, 开创了数据结构的课程体系。
同年,数据结构作为一门独立的课程,在计算机科学的学位课程中开始出现。之后,70年代初人们越来越重视。
从而有了这么一条等式: 程序设计 = 数据结构 + 算法
其实拆开数据结构来讲也是可以的,但是如果把相应的算法结合起来讲效果会比单独讲来的好。

那么可能小伙伴又会问数据结构是什么?emmm什么?还是没人问?

这里写图片描述

1.首先 数据 是什么意思,怎么组成的?

数据是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别并输入给计算机处理的符号集合。
(数据不仅仅包括整型、实型等数据类型,还包括字符以及声音、图像、视频等非数值类型),组成数据的基本是 数据元素
通俗易懂的讲: 数据就是能输入到计算机中并且让计算机程序能处理的 符号。

2.数据元素是什么?

数据元素是组成数据的基本单位,在计算机中通常作为整体处理。数据元素也被称为结点记录
比如:人类是一组数据,其中组成元素 人 就是 数据元素。
再比如: 牛、羊、鸡、鸭等动物就是 禽类 的数据元素。
还有还有不得不提一下组成 数据元素 的基本单位是 数据项。

3.数据项是什么?

数据项一个数据元素可以由若干个数据项组成
比如 : 人这样的数据元素,有眼耳口鼻等数据项,也有姓名、年龄等数据项
数据项是数据不可分割的最小单位

4.还有一个词叫 数据对象,什么意思呢?

数据对象是性质相同的数据元素的集合,是数据的子集。
什么叫性质相同呢?是指数据元素具有相同数量和类型的数据项。再举个例子:一个学校的全体学生为数据,数据元素是 某一个学生 ,数据对象则为 隶属于同一班级下的学生的集合

5.说了那么多,那数据结构是个什么呢?

结构可以简单理解为关系,比如分子结构,就是说组成分子的原子之间的排列方式,严格点说,
结构是指各个组成部分相互搭配和排列的方式,在现实世界中数据元素之间不是独立的,
而是存在特定的关系,我们将这些关系称为结构,那数据结构是什么?
数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。

数据结构又分为逻辑结构和物理结构

逻辑结构:是指数据对象中数据元素之间的相互关系。(这是我们今后最需要关注的问题)

逻辑结构分为一下四种:
1.集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系。各个数据元素之间是平等的,共同的属性就是同属于一个集合,有点类似与数学中的 集合
这里写图片描述
2.线性结构:线性结构中的数据元素之间是一对一的关系
这里写图片描述
3.树形结构:树形结构中的数据元素之间存在一种一对多的层次关系
这里写图片描述
4.图形结构:图形结构的数据元素是多对多的关系
这里写图片描述

物理结构:是指数据的逻辑结构在计算机中的存储形式

  • 物理结构实际上就是如何把数据元素存储到计算机中的存储器中,存储器主要是针对内存而言。像硬盘、光驱等外部存储器的数据组织通常用文件结构来描述
  • 数据的存储结构应正确反映出数据元素之间的逻辑关系,这也实现物理结构的重点和难点

数据元素的存储结构形式有两种: 顺序存储 和 链式存储

顺序存储结构:是把数据元素存放在地址连续的存储单元里面,其数据间的逻辑关系和物理关系是一致的。
这里写图片描述
数组就是这种顺序存储结构。

链式存储结构 :是把数据元素存放在任意的存储单元里,这组存储单元可续的,也可以是不连续的;数据元素的存储关系并不能反映其逻辑关系,因此需要用一个指针存放数据元素的地址来找到相关联数据元素的位置
这里写图片描述

小结: 逻辑结构是面向问题的,而物理结构就是面向计算机的,其基本的目标就是将数据及其逻辑关系存储到计算机的内存中

那算法是什么?

定义:算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作
算法能让我们更好的理解数据结构。他们是互辅

算法的特性:

  • 输入 : 算法具有零个或多个输入
  • 输出: 算法至少有一个或者多个输出
  • 有穷性: 指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成
  • 确定性: 算法的每一步骤都具有确定的含义,不会出现二义性。(一定条件下,只有一条执行路径,相同的输入只能有唯一的输出结果)
  • 可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。

    算法的设计要求:

  • 正确性

  • 可读性
  • 健壮性
  • 时间效率高和存储量低

算法效率的度量方法: 事后统计方法 和 事前分析估算方法

事后统计方法:这种方法只要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。
缺点:
1.必须事先依据算法编制好测试程序 。
2. 时间的比较依赖计算机硬件和软件等环境因素,有时会掩盖算法本身的优势
3.算法的测试数据设计太难,由于程序运行时间与测试数据的规模有很大关系,很难判断到底用多少数据来测试。

事前分析估算方法:在计算机程序编制前,依据统计方法对算法进行估算
经分析,我们可以知道一个算法运行时间取决与下列因素:
1.算法采用的策略、方法
2.编译产生的代码质量
3.问题的输入规模
4.机器执行指令的速度

测定运行时间最可靠的方法就是计算对运行时间有消耗的基本操作的执行次数,运行时间与这个计数成正比
我们在分析一个算法的运行时间时,重要的是把基本操作的数量与输入规模关联起来,即基本操作的数量必须表示成输入规模的函数。

这里写图片描述
随着n值的越来越大,它们在时间效率上的差异也就越来越大

函数的渐近增长
这里写图片描述

我们把得到的结果理解成需要运行的次数,比如把1带入2n+3 = 5次;
当n=1时,算法A效率不如算法B,而当n=2时两者效率相同,当n>2时,算法A就开始优于算法B了,随着n的增加越来越明显于是我们给出这样的定义 :
输入规模n在没有限制的情况下,只要超过一个数值N,这个函数就总是大于另外一个函数,我们称函数是渐进增长的。

函数的渐近增长:给定两个函数f(n)和g(n),如果存在一个整数N使得对于所有的 n> N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐近快于g(n).判断一个算法的效率时,函数中的常数和其他次要项(包括与最高此项相称的常数),而更应该关注主项的阶数

那我们要用什么方法来评估算法的效率呢?

这里写图片描述

算法的效率主要由以下两个复杂度来评估:

时间复杂度:评估执行程序所需的时间。可以估算出程序对处理器的使用程度。
空间复杂度:评估执行程序所需的存储空间。可以估算出程序对计算机内存的使用程度。

什么是时间复杂度
在进行算法分析时,我们将一个算法中的语句执行次数称为语句频度时间频度。记为T(n),T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定 T(n)的数量级,算法的时间复杂度也就是算法的时间量度,记作:T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数

小知识:
大O符号是由德国数论学家保罗·巴赫曼(Paul Bachmann)在其1892年的著作《解析数论》(Analytische Zahlentheorie)首先引入的。而这个记号则是在另一位德国数论学家艾德蒙·朗道(Edmund Landau)的著作中才推广的,因此它有时又称为朗道符号(Landau symbol)。(没错,就是他,就是他,抄家伙上)

怎么分析算法的时间复杂度?
我们把用大写O()来体现算法时间复杂度的记法,称之为大O记法
常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n), 线性对数阶O(nlog2n),平方阶O(n2),立方阶O(n3),…, k次方阶O(nk),指数阶O(2n)。
常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)

推导大O阶的规则:

推导大O阶,我们可以按照如下的规则来进行推导,得到的结果就是大O表示法:

1.用常数1来取代运行时间中所有加法常数。
2.修改后的运行次数函数中,只保留最高阶项
3.如果最高阶项存在且不是1,则去除与这个项相乘的常数。

下面我们来边看代码边分析:
1.常数阶:

            int sum = 0 ,n=100;    //执行1次
             sum=(1+n)*n/2;       //执行1次
             System.out.println(sum);//执行1次

这个算法运行次数函数为f(n)=3,根据推导大O阶的规则1,我们需要将常数3改为1,则这个算法的时间复杂度为O(1)。如果sum = (1+n)*n/2这条语句再执行10遍,因为这与问题大小n的值并没有关系,所以这个算法的时间复杂度仍旧是O(1),我们可以称之为常数阶
2.线性阶

                 int i,n;
                for(i=0;i<n;i++) { //循环次数为n
                     /**
                      * 时间复杂度为O(1)的程序步骤
                      */
                     System.out.println("day day up"); //O(1)
                }

对于一个循环,它的循环体的时间复杂度为 O(n),循环次数为 m,则这个
循环的时间复杂度为 O(n×m)
3.对数阶

int count =1;
           while(count < n) {
                count = count *2;
                System.out.println("day day up");//时间复杂度为O(1)的程序步骤序列
           }

这段代码,由于每次count乘以2之后,就距离n更近了一分,也就是说,有多少个2相乘后大于n,则会退出循环,
由2^x=n得到x=log2n

4.平方阶
对于多个循环,假设循环体的时间复杂度为 O(n),各个循环的循环次数分别是a, b, c…,则这个循环的时间复杂度为 O(n×a×b×c…)。分析的时候应该由里向外分析这些循环。

  for(int i = 0; i < n; i++) {         // 循环次数为 n
        for(int j = 0; j < n; j++) {       // 循环次数为 n
           System.out.println("day day up");   // 循环体时间复杂度为 O(1)
        }
    }

此时时间复杂度为 O(n × n × 1),即 O(n^2)。

那么下面这个循环的时间复杂度是多少呢?

        for(int i=0;i<n;i++){  
                 for(int j=i;j<n;i++){
                    //复杂度为O(1)的算法
                   System.out.println("day day up");   // 循环体时间复杂度为 O(1)
                 }
             }

由于当i=0时,内循环执行了n次,当i=1时,执行了n-1次…最后 当i=n-1时,执行了1次,所以总的执行次数为:
由等差数列求和公式可知:n+(n-1)+(n-2)+…+1=n(n+1)/2= n^2/2 + n/2
根据推到法则可知: 去除加法常数,只保留最高阶,因此保留n²/2。根据第三条去掉和这个项的常数,则去掉1/2,最终这段代码的时间复杂度为O(n²)

5.对于顺序执行的语句或者算法,总的时间复杂度等于其中最大的时间复杂度。

public void Day(int n) {
    // 第一部分时间复杂度为 O(n^2)
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < n; j++) {
            System.out.println("day day up");//时间复杂度为O(1)的程序步骤序列
        }
    }
    // 第二部分时间复杂度为 O(n)
    for(int j = 0; j < n; j++) {
     System.out.println("day day up");//时间复杂度为O(1)的程序步骤序列
    }
}

此时时间复杂度为 max(O(n^2), O(n)),即 O(n^2)

6.对于条件判断语句,总的时间复杂度等于其中 时间复杂度最大的路径 的时间复杂度。

public void Day(int n) {
    if (n >= 0) {
        // 第一条路径时间复杂度为 O(n^2)
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
            System.out.println("day day up");//时间复杂度为O(1)的程序步骤序列
            }
        }
    } else {
        // 第二条路径时间复杂度为 O(n)
        for(int j = 0; j < n; j++) {
          System.out.println("day day up");//时间复杂度为O(1)的程序步骤序列
        }
    }
}

此时时间复杂度为 max(O(n^2), O(n)),即 O(n^2)

时间复杂度分析的基本策略是:从内向外分析,从最深层开始分析。如果遇到函数调用,要深入函数进行分析。

那什么是算法的空间复杂度呢?

这里写图片描述

它是类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)S(n)定义为该算法所耗费的存储空间,它也是问题规模n的函数。渐近空间复杂度也常常简称为空间复杂度空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度

算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数

一般情况下,一个程序在机器上执行时,除了需要存储程序本身的指令、常数、变量和输入数据外,还需要存储对数据操作的存储单元,若输入数据所占空间只取决于问题本身,和算法无关,这样只需要分析该算法在实现时所需的辅助单元即可,若算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为O(1);

还有一点:通常,我们都使用“时间复杂度”来指运行时间的需求,使用“空间复杂度”指空间需求。当不限定词地使用“复杂度”时,通常都是指时间复杂度。

这里写图片描述

最后很感谢您看到这里~么么扎

参考自书籍:《大话数据结构》
参考自书籍: 《数据结构与算法》
参考自大神刘望舒:http://blog.csdn.net/itachi85/article/details/54882603

猜你喜欢

转载自blog.csdn.net/printfcc/article/details/79167384