假如一个算法中包含了某些冗余的计算过程,那么一定有方法能够继续优化。
关于算法效率的一些考虑
减少冗余计算
假如一个算法中包含了某些冗余的计算过程,那么一定有方法能够继续优化。
比方双重递归
#下面是一个求2的幂的运算
def powerOfTwo1 (n):
if n == 0:
return 1
else if n == 1:
return 2
else
return powerOfTwo1( n//2 )*powerOfTwo1( (n+1)//2 )
这里powerOfTwo(n//2)
和powerOfTwo((n+1)//2)
两局部的计算是有冗余的,当n是偶数时,2个结果一样,但是却反复了计算;而当n是奇数时,powerOfTwo((n+1)//2) = 2 * powerOfTwo(n//2)
, 同样计算呈现冗余,时间复杂度到达O(2^n),所以能够进一步优化。
def powerOfTwo2 (n):
if n == 0:
res = 1
else
res = 2*powerOfTwo2(n-1)
return res
优化后时间复杂度为O(n)
加大计算粒度
当然上面的powerOfTwo2算法还不算太好,某种水平上来说里面还存在冗余,由于有太多反复的 乘2 操作了,如何进一步减少这些操作呢?那就是在运算过程中动态地改动计算粒度。
def powerOfTwo3 (n):
if n == 0:
return 1
table = [2]
res = 1
while(n):
if len(table) >= n:
res *= table[n-1]
n = 0
else:
table.append(2*table[-1])
res *= table[-1]
n -= len(table)
return res
这次powerOfTwo3会动态地改动每一次计算的粒度,从最小的2开端,之后可能为更大的4、8、16等。
比方计算2的13次方幂,运算过程为2 * 4 * 8 * 16 * 8, 即2 * 2^2 * 2^3 * 2^4 * 2^3
此时时间复杂度变为O(logN)
当然上面的指数n是经过减法衰减的( n -= len(table)
), 因而还能更快,比方运用除法来更快地衰减n,比方下面
#求k的n次方, powerOfTwo4(n) = power4(2,n)
def power4 (k, n):
if n == 0:
return 1
else:
if n % 2 == 0:
return power4( k*k, n//2 )
else:
return k * power4( k*k, n//2 )
这种办法每次运算基于的底数都不同,即新的运算粒度,因而需求保管新的底数,就像power4函数中的参数k,但是这点空间换来的效率提升是完整值得的。
这里之所以能加大计算粒度,是由于每次幂运算都是基于相同的底数,即2,因而相当于另一种方式的“冗余计算”,但是有时分在计算中或许不能进一步加大计算粒度,比方下面的阶乘计算:
#计算n!
def fac(n):
if n == 0:
return 1
else
return n*fac(n-1)
阶乘里每一次运算操作的对象都不同,因而不存在冗余,不能进一步加大粒度。
加大计算粒度的办法很多,详细多大的粒度才最好,这还要看需求处置的数据的范围。
减少乘除
有时分某个算法的粒度曾经不小,而且操作过程也没有明显的冗余,这时分某些细微的运算过程或许还潜藏着冗余操作,能够从减少乘除运算的角度去进一步优化。
下面是一个求整数平凡根的程序,eg. intRoot(7) = 2
def increase( r, n ):
return r if (r+1)*(r+1) > n else r+1
def intRoot(n) :
if n == 0:
return 0
else:
return increase( intRoot( n // 4 ) * 2, n )
操作的参数n每次以4倍的速度衰减,貌似曾经没有冗余。但是increase里的(r+1)*(r+1)
性能耗费还是有点大,能够看看能否进一步优化,如下:
def intRootOptimize(n):
if n == 0:
return [0,0]
else:
preR, preR2 = intRootOptimize( n//4 )
incR2 = 4*preR2 + 4*preR + 1
if incR2 > n:
return [ 2*preR, 4*preR2 ]
else:
return [ 2*preR+1, incR2]
incR2 = 4*preR2 + 4*preR + 1
即前面的(2r+1)*(2r+1)
,这里的运算更合适编译器优化,能够优化为移位运算,效率进步许多。
原文地址:https://www.cnblogs.com/1654kjl/p/12573068.html
- linux系统环境变量一文就够
- 百度搜索 “Java面试题” 前200页(面试必看)
- C# 给枚举类型增加一个备注特性
- 依赖注入(IOC)二
- Linux Token Auth 一次性密码认证
- WPF备忘录(7)WPF图片资源路径介绍
- 植入式攻击入侵检测解决方案
- 神经网络太臃肿?教你如何将神经网络减小四分之一
- WPF中ListView如何改变选中条背景颜色
- WPF Trigger for IsSelected in a DataTemplate for ListBox items
- C#基础知识回顾--BackgroundWorker介绍
- Elasticsearch 瞬间入门
- 使用OpenLDAP 操作 Windows Active Directory
- 优化算法:到底是数学还是代码?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux查看ip的实例方法
- Ubuntu18.04通过源码安装Odoo14的教程
- Linux系统中SSH服务基于key认证实践的过程
- linux防墙iptables详细介绍、配置方法与案例
- Linux unlink函数和删除文件的操作方法
- Linux seq命令的使用详解
- Linux运维工具Supervisor的安装使用(进程管理工具)
- Linux whatis命令的使用方法
- Linux tr命令的使用方法
- Linux man命令的具体使用
- Linux locate命令的使用方法
- 解决linux系统中运行node进程却无法杀死进程的问题
- 5分钟搭建一个WebRTC视频聊天
- redis妙用-应用场景
- 详解Linux中几个获取硬件详细信息的命令