zoukankan      html  css  js  c++  java
  • 算法设计标准

    一、基础定义

    算法定义:

    算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作,每一个操作都有特定的功能

    算法的基本特点:

    (1)输入输出:算法具有0个或多个输入,至少有一个或多个输出;

    (2)有穷性:指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成;

    (3)确定性:算法的每一步骤都具有确定的含义,不会出现二义性。

    (4)可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成;

    算法设计要求:

    (1)正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求、能够得到问题的正确答案;

    (2)可读性:算法设计的另一目的是为了便于阅读、理解和交流

    (3)健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果;

    (4)时间效率高存储量低:时间效率指的是算法的执行时间,对于同一个问题,如果有多个算法能够解决,执行时间短的算法效率高,执行时间长的效率低。存储量需求指的是算法在执行过程中需要的最大存储空间,主要指算法程序运行时所占用的内存或外部硬盘存储空间。设计算法应该尽量满足时间效率高和存储量低的需求。

    二、算法效率检测

    算法的执行效率大都指算法的执行时间,我们如何衡量一个算法程序的执行效率呐,可以通过事后统计和事前估算两种方式来衡量。
    (1)事后统计方法:这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低;该种方式有两个特点,第一:必须要通过设计好的测试程序和数据进行效率检测,但是通过少量的数据无法很好的判断算法的好坏,所以我们需要创造大量的测试数据进行检验,对于这个这个数据大量,我们很难进行评估和设计;第二:该种方式需要花费大量的时间和精力去编制好程序,如果最终结果发现这是一套很糟糕的算法,就前功尽弃;
    (2)事前估算方法:在计算机程序编制前,依据统计方法对算法进行估算,我们可以发现,一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
    1、编译产生的代码质量;
    2、算法采用的策略、方法;
    3、问题的输入规模;
    4、机器执行指令的速度;
    这其中,1和4 分别由软件和计算机硬件因素有关,所以我们只能从算法的策略和输入规模进行衡量,根据数据的输入规模和计算方式,我们有以下两个指标可以估算算法的执行效率:

      时间复杂度:

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

    计算时间复杂度(大O阶)三条法则:
    1.用常数 1 取代运行时间中的所有加法常数。
    2.在修改后的运行次数函数中,只保留最高阶项。
    3.如果最高阶项存在且不是 1,则去除与这个项相乘的常数。得到的结果就是大 O 阶。
    我们对以下几类代码进行分析:
    (1)常数阶:
    int sum = 0,n = 100; /*执行一次*/
    sum = (1+n)*n/2; /*执行一次*/
    printf("%d", sum); /* 执行一次*/
    
    可以看出算法的函数执行次数函数为f(n)= 3;按照我们计算大O阶的三条法则的第一条,所有常数项使用1来代替,所以该代码的时间复杂度为O(1) 而不是O(3); 如果 我们将中间的 sum = (1+n)*n/2; 执行10次,那么我们的执行次数函数为 f(n)=12,结果依旧是常数,它的时间复杂度依旧为O(1),对于这种分支结构而言,执行次数始终是恒定的,不会随着n 变化而变化,所以他们的时间复杂度始终未O(1)
    (2)线性阶:
    for(int i=0; i<n ;i++){
        print(""+i);
    }
    
    它的循环的时间复杂度为 O(n),因为循环体中的代码须要执行 n次
    (3)对数阶:
    int count = 1;
    while (count < n){
        count = count * 2; /*时间复杂度为 O(1)的程序步骤序列*/
    }
    
    由于每次count乘以 2 之后,就距离n更近了一分。也就是说,有多少个 2 相乘后大于n,则会退出循环。由 2 x =n得到x=log 2 n。所以这个循环的时间复杂度为O(logn)
    (4)平方阶:
    for(int i=0; i<n ;i++){
      for(int j=0; j<n ;j++){
        print(""+i+j);
      }
    }
    
    对于外层的循环,不过是内部这个时间复杂度为O(n)的语句,再循环n次。所以这段代码的时间复杂度为O(n^2)。
    注意以下代码片段:
    int i,j;
      for(i = 0; i < n; i++){
        for (j = i; j < n; j++) /*注意 int j = i 而不是 0*/
        {
          print("结果"+i+j);
       }
    }
    
    由于当 i = 0 时,内循环执行了 n 次,当 i = 1 时,执行了 n-1 次,……当 i = n-1 时,内循环执行了 1 次。所以总的执行次数:
    n+(n-1)+(n-2)+(n-3)..........+1= n^2 /2 +n/2(等差数列求和)
    按照我们推断大O阶的第二条和第三条,只保留最高阶,并且去掉最高阶的常数,最终得到结果为O(n^2);
    常用的时间复杂度所耗费的时间从小到大依次是:
    O(1)<O(log n)<O(n)<O(n*logn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n);
     空间复杂度

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

    通常, 我们都使用"时间复杂度"来指运行时间的需求,使用"空间复杂度"指空间需求。当不用限定词地使用"复杂度'时,通常都是指时间复杂度。

    三、总结

    很多伙伴说,现在 CPU 升级越来越快,根本不用考虑算法的优劣,实现功能即可,用户感觉不到算法好坏造成的快慢,但是做个假设:如果CPU在短短几年间,速度提高了 100 倍,这其实已经很夸张了。而我们的某个算法本可以写出时间复杂度是O(n)的程序,却写出了O(n 2 )的程序,仅仅因为容易想到,也容易写。即在O(n 2 )的时间复杂度算法程序下,速度其实只提高了 10 倍,而对于O(n) 复杂度的算法说,那才是真的 100 倍。 也就是说,一台老式CPU的计算机运行O(n)的程序和一台速度提高 100 倍新式CPU运行O(n 2 )的程序。最终效率高的胜利方却是老式CPU的计算机,原因就在于算法的优劣直接决定了程序运行的效率,所以算法的重要性可想而知。
    最后附上一张常用排序算法的效率比较图:
     
     
    参考内容:
    《大话数据结构》
  • 相关阅读:
    POJ 1251 Jungle Roads
    1111 Online Map (30 分)
    1122 Hamiltonian Cycle (25 分)
    POJ 2560 Freckles
    1087 All Roads Lead to Rome (30 分)
    1072 Gas Station (30 分)
    1018 Public Bike Management (30 分)
    1030 Travel Plan (30 分)
    22. bootstrap组件#巨幕和旋转图标
    3. Spring配置文件
  • 原文地址:https://www.cnblogs.com/hello-alan/p/11394681.html
Copyright © 2011-2022 走看看