整数排序
1千万个数放在文件中,每个整数为7位正整数,最多出现一次,内存只有1M,运行时间10几秒
思考:
1. 如果使用32位Integer存储则需要40M存储空间,远超于内存,则只能使用外部排序。
2. 1M /4B=250K,为一次可在内存中排序的整数个数,则创建 9999999 / 250k 个临时文件,每个文件存储相应范围的整数,如[k*250KB, (k+1)*250KB ] 。
3. 扫描原始文件,将每个整数保存到相应文件中
4. 按顺序对每个文件在内存中做快速排序并写到对应结果文件
5. 按顺序合并结果文件
分析:
按以上思路需要40读取和写入文件,原因是需要40MB的存储,是否可采用一种合适的表达方式,将10,000,000个数字使用更少的bit来表示,如使用位图或位向量(即n位的二进制序列表示最大值为n的数,当值为i的数存在时,第i位为1)来表示集合,则表示1千万需要1.25MB 存储。这种思路利用了排序数据中不常见的属性:输入数据在较小范围内;数据没有重复;对每条记录无关联数据
解法:
1. 将所有位置零,扫描文件对相应位置1
2. 按顺序校验每一位,如果该位为1则输出相应的整数
总结:
1. 位图数据结构表示有限定义域中的稠密集合,其中的每一个元素最多出现一次,且没有其他任何数据与该数据关联,即使有关联,也可将该值作为更复杂表格的索引。
2. 当需要同时减少空间和时间时,有两个方法:减少处理的数据;保存在内存中。当然了只有原始设计不是最佳方案时才能实现
3. 简单设计:设计不能再减少东西了
4. 如果限定在1M中,则2次遍历,第一次只处理0-4999999,第二次处理5000000-9999999.
5. 每个数字最多出现10次时? 对每个数字使用4bit存放
缺失的1个整数
问题:
有 1000,000 个整数,每个整数在 0,3000,000之间,每个数只出现一次
解法:
首先要遍历一遍数据,找出最大数M和最小数N,计算出中位数的大小,然后统计中位数以上和以下的数字个数,选择个数不到(M-N+1)/2的一段重复上述的过程。
向量旋转
N元字符串向左旋转i个位置,如abcdefgh向左旋转 3得到 defghabc。要求几十字节的额外空间,O(n)时间内。类似问题:交换相邻的不同大小的内存块,拖动交换两块内存中的文字。
解法1:
Forj=0 to i {
t=x[j];
for(h=0;i*h+j<n;h++) {
X[(h-1)*i+j]=x[i*h+j]; //将 a[3]->a[0] a[6]->a[3]
}
X[i*(h-1)+j]=t; // t->a[6]
}
解法2:
将字符串分成 ab1b2三部分,其中a为前i个字符,b2和a长度相同。先将a和b2交换得到 b2b1a, 则a在正确位置,继续对b2和b1进行递归调用。
程序步骤
1. 问题定义:了解问题的背景,问题的陈述包含了问题的解法,但也别只停留在这种解法中;对问题抽象为通用问题,如3抽象为n
2. 研究输入、输出,选择合适的数据结构
3. 怎么办:针对问题采取哪些基本算法,如排序、二分搜索
4. 考虑尽可能的解法
5. 从多种解法中选择一种实现
6. 完成程序框架后,对性能进行初步估算
性能优化
优化内容
算法和数据结构(选择更高效算法,根据特定问题对算法优化,根据算法构造数据结构)
代码调优(系统无关的:如实数使用float,系统相关的:如对hot pot使用汇编实现)
系统软件:数据库(infobright代替mysql)、操作系统等
硬件;
优化步骤
a) 明确对性能的具体要求
b) 了解当前程序的性能
c) 找到程序的性能瓶颈
d) 采取适当的措施来提高性能
e) 只进行某一方面的修改来提高性能
f) 返回到步骤c,继续作类似的工作,一直达到要求的性能为止。
算法设计技术
问题
N个浮点数的向量,找出连续子向量中的最大和
思考:
1. 最简单三重循环,计算Aij的值,并记录最大值 ,为O(n^3)
2. 发现Aij的计算中有重复子结构,Aij=Ai(j-1)+aj 为O(n^2)
3. Aij=A0j-A0i, 为O(n^2)
4. n^2考虑了所有的子向量,因为存在O(n^2)的子向量,所以至少需要平方和的实现,通过想办法避免监测所有的子向量,获得更短时间的算法,使用分治法,将n分为大小类似的a和b两段,则最大向量为 Aa、Ab、起点在a,终点在b 三种情况的最值,前两种是递归,后一种对于a从 length[a]向前的最大值+b[0]向后的最大值,则F(n)=2F(n/2)+O(n) 为 O(nlgn)
5. 扫描算法:I 0 to n-1 计算从0到i的最大值,该值有两种可能(0, i-1)间和以i结尾, A0(i-1)已经存在,以i结尾的最值 max( maxending+a[i] ) 为 O(n)
技术:
保存状态,避免重复计算
将信息预处理到数据结构,如算法3
分治算法:有可能达到nlgn
扫描算法:与数组相关的算法一般可通过思考如何将 a[0,i-1]扩展到a[0,i]来实现。
累积:范围问题,如 3到10月销售额,可通过至10月销售额-至2月销售额
下界:证明本问题的算法下界
代码调优
代码调优的最重要原理就是尽量少用它,原因如下
效率的角色:其他特性也很重要,不要轻易优化
度量工具:找到hotpot进行优化,其他先不动
设计层面:如果效率重要,在设计时考虑
双刃剑:是否影响到其他部分
节省空间
简化
通过仔细研究原始问题,使用一个简单问题替换原始问题,选择另一种数据结构,或者表达方式,如使用稀疏矩阵。
数据空间技术:减少数据所需的存储空间
不存储,重新计算
稀疏数据结构
数据压缩
分配策略:有时候如何使用空间比使用多少空间更重要,如动态分配,将两个对称矩阵共享同一二维数组
垃圾回收
代码空间技术: 减少程序本身的规模
函数定义: 从重复代码中重构出函数
解释程序: 如海龟图的实现
翻译成机器语言:用于内存宝贵的系统,如数字信号处理器
原理
空间开销: 节省空间还是有意义滴,如使用的内存增加10%,则在内存非常小的系统中就不能用了;通过网络传输的时间将增加10%。
空间热点: 有些格式的数据将占用大部分的内存,应该针对这种格式专门优化
空间度量: 使用查看内存使用的工具,如profile
折中:有时候需要牺牲 功能、性能和可维护性,这是最后的办法
与环境协作:包括编译器、运行时表示方式、内存分配策略以及分页策略
使用正确的工具: 从 简化、四种数据策略、三种代码策略中选择正确的方法
取样问题
N个[0,k]之间随机数
for i =[0, n)
x[i]= n%k
for i =[0,n)
swap(i, randint(i, n-1))
print x[i]
三种创新的方法
原创性,
新发现,
用低技术含量的答案来解决高技术含量的问题
字符串
最长重复字串
问题:查找一个文本文件中,出现的最长重复子字符串,要求O(nlgn)时间内
解法: 使用“后缀数组”的数据结构(字符指针数组 a,a[0]指向整个,a[1]指向第二位开始的,a[2]为第三位到结束),如果某个字符串出现两次,则a[i]和a[j]的前面几位都是该字符串。步骤如下:1. 构造后缀数组;2. 使用qsort进行排序;3.for i 0 to n 从开始比较 a[i] 和a[i+1] 得到重复子字符串,求得其最长值
生成文本
生成看起来更像英文的文本
先读取样本,统计每个字母之后出现的次数;在写随机文本时,使用当前字母的一个随机函数生成下一个字母,这是一节文本,如果使用前面四个字母生成下一个字母就非常类似于英文单词了
在单词级别生成随机文本
读取样本,对每个单词计数,根据概率选择下一个输出的单词,如果生成下一个单词时考虑前面几个单词的马尔科夫链,则会等到更感兴趣的文本
算法分类
排序,应用于:有序输出, 收集相同的项,有 插入排序,快速排序, 基数排序,位图排序(见第一章)
搜索,应用于:拼写检查器、搜索棋盘得到最优解,有散列、二分搜索树、
字符串算法
向量和矩阵算法
随机对象
代码调优法则
空间换时间法则:
修改数据结构: 根据操作选择数据结构,对数据结构进行扩展和修改
存储中间结果
高速缓存
懒加载
时间换空间法则:
堆积: 密集存储和压缩通过增加检索时间来减少存储开销
解释程序: 操作系列以一种紧凑方式表示
循环法则:
删除赋值: 如果很多开销来自于赋值
循环合并:两个相邻循环作用于同一组元素
逻辑法则
等价的代数表达式:如果逻辑表达式开销太大
测试条件排序: 低开销,经常成功的放前面
预先计算逻辑函数:
过程法则
使用inline或宏替代函数
高效处理常见情况
递归函数转换
并行性