04.算法

04.算法

算法概念与特征

算法定义:解决问题的方法与步骤

设计算法的目的:给出解决问题的逻辑描述,根据算法描述进行实际编程

算法特征

有穷性:算法在每种情况下都可以在有限步后终止

确定性:算法步骤的顺序和内容没有二义性

输入:算法有零个或多个输入

输出:算法至少具有一个输出

有效性:所有操作都具有明确含义,并能在有限时间内完成

正确性:不是算法的特征,算法的正确性需要数学证明

算法描述

伪代码

混合自然语言与计算机语言、数学语言的算法描述方法

优点:方便,容易表达设计者思想,能够清楚描述算法流程,便于修改

缺点:不美观,复杂算法不容易理解

流程图(程序框图)

使用图形表示算法执行逻辑

优点:美观,算法表达清晰

缺点:绘制复杂,不易修改,占用过多篇幅

算法设计与实现

构造算法解决问题

按照自顶向下、逐步求精的方式进行

使用程序设计语言编程实现

典型示例

一、判断给定的某个自然数n(大于2)是否为素数。

算法逻辑

输入:大于2的正整数n

输出:该数是否为素数,若为素数返回true,否则返回false

step1.设除数i为2

step2.判断除数i是否已为n,若为真返回true,否则继续

step3.判断n%i是否为0,若为0返回false,否则继续

step4.将除数i递增,重复step2

bool IsPrime(unsigned int n)
{
    unsigned int i = 2;
    while(i < n)
    {
        if(n % i == 0)
        {
            return false;
            i++;
        }
    }
    return true;
}

实际上没有必要整除到n-1,而只需整除到√n,改进后

bool IsPrime(unsigned int n)
{
    unsigned int i = 2;
    while(i <= (unsigned int)sqrt(n))
    {
        if(n % i == 0)
            return false;
        i++;
    }
    return true;
}

注:Linux环境中g++编译使用<cmath>中函数时,应在g++命令后加-lm参数

再次改进后

bool IsPrime(unsigned int n)
{
    unsigned int i = 3;
    if(n % 2 == 0)
        return false;
    while(i <= (unsigned int)sqrt(n))
/*sqrt()方法的结果为浮点数,计算机内部存储浮点数有一定误差,若将11存为10.999...之类,在转为无符号整型时被截掉小数部分,变为10,导致结果出错。故需做如下改变*/
  //while(i <= (unsigned int)sqrt(n) + 1)
    {
        if(n % i == 0)
            return false;
        i += 2;
    }
    return true;
}

实际上,以上算法还可以再做改进。因为sqrt(n)在算法中只起一个边界的作用,只需计算一次就够了,没有必要每次循环都计算一次。因此,可在算法开始处定义t = (unsigned int)sqrt(n) + 1;

二、求两个正整数x与y的最大公约数

穷举法

unsigned int gcd(unsigned int x, unsigned int y)
{
    unsigned int t;
    t = x < y ? x : y;
    while(X % t != 0 || y % t != 0)
        t--;
    return t;
}

欧氏算法(辗转相除法)

step1.x整除以y,记余数为r

step2.若r为0,则最大公约数即为y,算法结束

step3.否则将y作为新x,将r作为新y,重复step1step2。

unsigned int gcd(unsigned int x, unsigned int y)
{
    unsigned int r;
    while(true)
    {
        r = x % y;
        if(r == 0)
            return y;
        x = y;
        y = r;
    }
}

算法选择

算法选择的权衡指标

正确性:算法是否完全正确

效率:在某些场合,对程序效率的追求具有重要意义

可理解性:算法是否容易理解,也是必须考虑的

算法评估

衡量算法的好坏,主要是效率

递归算法

递归问题的引入

递推公式:数学上非常常见

例一:阶乘函数:1! = 1, n! = n * (n - 1)!

例二:斐波那契数列函数:f(1) = f(2) = 1, f(n) = f(n - 1) + f(n - 2)

递推函数一定是分段函数,具有初始表达式

递推函数的计算逻辑:逐步简化问题规模

递推的工作步骤

递推过程:逐步分解问题,使其更简单

回归过程:根据简单情形组装最后的答案

例子的不同实现

一、阶乘问题

循环实现

unsigned int GetFactorial(unsigned int n)
{
    unsigned int result = 1, i = 0;
    while(++i <= n)
        result *= i;
    return result;
}

递归实现

unsigned int GetFactorial(unsigned int n)
{
    unsigned int result;
    if(n == 1)
        result = 1;
    else
        result = n * GetFactorial(n - 1);
    retrn result;
}

二、斐波那契数列函数

循环实现

unsigned int GetFibonacci(unsigned int n)
{
    unsigned int i, f1, f2, f3;
    if(n == 1 || n == 2)
        return 1;
    f1 = 1;
    f2 = 1;
    for(i = 3;i <= n;i++)
    {
        f3 = f2 + f1;
        f1 = f2;
        f2 = f3;
    }
    return f3;
}

递归实现

unsigned int GetFibonacci(unsigned int n)
{
    if(n == 1 || n == 2)
        return 1;
    else
        return GetFibonacci(n - 1) + GetFibonacci(n - 2);
}

循环与递归的比较

循环使用显式的循环结构重复执行代码段,递归使用重复的函数调用执行代码段

循环在满足其终止条件时终止执行,而递归则在问题简化到最简单情形时终止执行

循环的重复是在当前迭代结束时进行,而递归的重复则是在遇到对同名函数的调用时进行

循环和递归都可能隐藏程序错误,循环的条件测试可能永远为真,递归可能永远简化不到最简单情形

理论上,任何递归程序都可以使用循环迭代的方法解决

递归函数的代码更加短小精悍;递归程序更易理解

容错

容错的定义:允许错误的发生

错误的处理

很少见的特殊情况或普通错误:忽略该错误,不对程序运行结果产生影响

用户输入错误:通知用户错误性质,提醒用户更正输入。

致命错误:通知用户错误的性质,停止执行

典型容错手段

数据有效性检查

程序流程的提前终止

算法复杂度

引入算法复杂度的目的

度量算法的效率与性能

大O表达式

算法效率与性能的近似表示(定性描述)

算法执行时间与问题规模的关系

表示原则

忽略所有对变化趋势影响较小的项,例如多项式忽略高阶项之外的所有项

忽略所有与问题规模无关的常数,例如多项式的系数

标准算法复杂度类型

O(1):常数级,表示算法执行时间与问题规模无关

O(log(n)):对数级,表示算法执行时间与问题规模的对数成正比

O(sqrt(n)):平方根级,表示算法执行时间与问题规模的平方根成正比

O(n):线性级,表示算法执行时间与问题规模成正比

O(n*log(n)): n*log()级,表示算法执行时间与问题规模的n*log(n)成正比

O(n²):平方级,表示算法执行时间与问题规模的平方成正比

......

posted @ 2020-09-06 22:59  bear-Zhao  阅读(256)  评论(0编辑  收藏  举报