算法笔记(0)
来源:互联网 发布:手机淘宝5.9.0版本 编辑:程序博客网 时间:2024/06/05 04:44
最近入手一本《算法新解(刘新宇 ◎ 著)》,单单看完前言小例子就让人大呼过瘾,将算法讲的很透彻。趁着最近闲暇之余,留个笔记,记录一下学习进程。
本文算法描述及代码实现来自《算法新解(刘新宇 ◎ 著)》,感谢作者
这是书中的第一个算法小例子:
“求最小可用ID”
找到最小可分配的ID,例如:当前已使用ID:
[18, 4, 8, 9, 16, 1, 14, 7, 19, 3, 0, 5, 2, 11, 6];
求不在该列表中的最小非负整数, 即 10 .
由于数据较少,只有15个,直接数就可以数出来答案为 10 。
乍一看似乎不难,直接从0到最大值遍历一遍,看当前值是否已经使用,算法描述如下:
function Min-Free(A) x ← 0 loop if x ∉ A then return x else x ← x + 1
其中 ∉ 符号的实现如下:
function '∉' (x, X) for i ←1 to |X| do if x == X[i] then return False return True
但是对于长度为n的ID列表,该算法的时间复杂度为O(n²),当n的值为10万、100万时,这个算法的性能就不敢恭维了
改进一
改进这一解法的关键基于这一事实:对于任何n个非负整数x1,x2,···,xn ,如果存在小于n的可用整数,必然存在某个xi不在[0,n)范围内。否则这些整数一定是 0,1,···,n-1的某个排列,在这种情况下,最小可用非负整数为n。于是有如下结论:
minfree(x1,x2,···,xn) <= n
根据这一结论,我们可以使用一个长度为n+1的数组,来标记区间[0, n]内的某个整数是否可用:
function Min-Free(A) F ← [False, False, ···,False] where |F| = n + 1 for ∀x ∈ A do if x < n then F[x] ← True for i ← 0 to n do if F[i] = False return i
其中步骤2将标志数组中所有值初始化为False, 需要O(n)时间,接着步骤6遍历A中的所有元素,只要小于n就将对应标志位置为True, 这一步也需要O(n)时间,故整个算法是线性时间O(0)。
以下为C语言实现代码
#include<stdio.h>#define N 1000000 //100万#define WORD_LENGTH sizeof(int) * 8 void setbit(unsigned int *bits, unsigned int i) { bits[i / WORD_LENGTH] |= 1<<(i % WORD_LENGTH);}int testbit(unsigned int *bits, unsigned int i) { return bits[i / WORD_LENGTH] & (1 << (i % WORD_LENGTH));}unsigned int bits[N / WORD_LENGTH + 1];int min_free(int *xs, int n) { int i, len =N / WORD_LENGTH +1; for(i = 0; i < len; i++) { bits[i] = 0; } for(i = 0; i < n; i++) { if(xs[i] < n) setbit(bits, xs[i]); } for(i = 0; i <= n; i++) { if(!testbit(bits, i)) return i; }}int main() { int test[15] = {18, 4, 8, 9, 16, 1, 14, 7, 19, 3, 0, 5, 2, 11, 6}; printf("%d", min_free(test, 15));}
在上面min_free()函数中,最后一个for循环可以进一步优化,从数组第一个int开始,以int为单位(每次检查32个位)检查该int的比特位是否全为1,若不等于0xffffffff,则说明最小的可分配ID在该int范围内,再遍历该int值得32个比特位。如下:
if((!bits[i]) != 0){ for(j = 0; ; ++j) if(!testbit(bits, i*WORD_LENGTH + j)) return i*WORD_LENGTH + j; }
以上在我看来O(n)已经是最为快速的算法了,然而大神还有另一种方法,那就是分治策略。
改进二
在改进一种以空间的消耗为代价做了速度上的改进,由于维护一个长度为n+1的标志数组,当n很大时,空间上的性能就成了新的瓶颈。
分而治之的典型策略是将问题分解为若干规模较小的子问题,然后逐步解决它们以得到最终的结果。
思路:
将所有满足xi ≦ [n/2] 的整数放入子序列A’,并将剩余的整数放入另一个序列A’中,如果| A’| == [n/2], 说明前一半整数已满,为 0 ~ [n/2] 的一个排列,故最小可分配整数一定可以在A”中递归找到;否则可以在A’中递归找到。通过如此划分,问题的规模减小了。
需要注意的是在A”中递归查找时,边界情况发生了一些变化:不在是从0开始寻找,寻找的下界为[n/2]+1,因此算法定义为minfree(A,l,u),其中l为下界,u为上界。
minfree(A) = search(A,0,|A|-1)
其中: m = [n/2] + 1
A’ = { x | ∀x∈A & x ≦ m}
A” = { x | ∀x∈A & x > m}
函数式语言Haskell实现如下:
import Data.ListminFree xs = bsearch xs 0 (length xs - 1)bsearch xs l u | xs == [] = l | length as == m - l + 1 = bsearch bs (m + 1) u | otherwise = bsearch as l m where m = (l + u) `div` 2 (as, bs) = partitiopn (<= m) xs
哈哈,是不是很神奇,跟天书似得,然而这就是函数式语言,和我们以前学过的C++、Java等都截然不同,有兴趣的可以推敲推敲,我也是特地找了些Haskell的资料才看懂上面的代码,这种迥异于命令式语言的函数式语言就像另一种思维方式,别有一番风趣。
别急,下面贴出将递归转换为迭代的C语言代码:
int min_free(int *xs, int n) { int l = 0; int u = n - 1; while(n) { int m = (l + u) / 2; int right, left = 0; //将小于m的值放入left左部 for(right = 0; right < n; ++ right) if(xs[right] <= m) { swap(xs[left], xs[right]); ++left } if(left == m - l + 1) { //前半个数组满 xs = xs + left; //寻找的下界变为 [n/2] + 1 n = n - left; l = m + 1; } else { //寻找的值在left左部 n = left; u = m; } } return l;}
前言第一个小例子完毕,还需继续加油~
阅读全文
0 0
- 算法笔记(0)
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 算法笔记
- 《算法》笔记
- jquery.form.js
- html div 水平垂直居中
- 修改nginx站点根目录总结经验
- 链表面试题(十三)---求两个都不带环的链表相交的结点
- 柔性数组
- 算法笔记(0)
- python3 urlopen的解码问题
- JDK8的@CallerSensitive
- 如何使得eclipse中jsp编辑模块识别HTML5和CSS3文档
- 什么函数不能申明为虚函数
- eclipse打包APK出现 Errors occurred during the build问题
- iOS订阅型内购要点
- 七龙珠
- 文件操作(2)-读取多文件内容