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²):平方级,表示算法执行时间与问题规模的平方成正比
......