有哪些算法惊艳到了你?

有哪些算法当你刚接触的时候,心里有“居然还可以这样算..”的想法?
关注者
28,895
被浏览
9,659,890
登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏

首先映入我脑海里的是 John Carmack 的Quake引擎里的一段代码, 求一个数的平方根的倒数(注意是倒数,不是导数). John Carmack 是id software的创始人, 也是经典第一视角游戏 Doom 和 Quake 的作者. 他对自己的Quake游戏引擎进行开源, 极大地促进了整个3D游戏行业前进的步伐. 连比尔盖茨都将 Carmack 列为自己的崇拜对象!

一般能想到的办法是:

1. 调用系统库函数: sqrt, 然后再除1;

2. 二分法逼近 (之前Facebook的有道面试题即是);

3. 牛顿迭代法

而在Quake渲染引擎中, 有这么一段代码 (将其标号: #4 Quake InvSqrt 算法):

float InvSqrt (float x){
    float xhalf = 0.5f*x;
    int i = *(int*)&x;
    i = 0x5f3759df - (i>>1);
    x = *(float*)&i;
    x = x*(1.5f - xhalf*x*x);
    return x;
}

如果有兴趣的朋友自己去写一个程序去测试, 发现上述4中方法的运算效率:

#4 > #1 > #3 > #2.

借用一位朋友的照片:

看见Carmack代码里面的方法比起系统方法还快一倍以上, 而且实现方式短小且神奇. 其本质上也是使用了牛顿迭代法, 但是通过预先猜测的一个神奇数字 0x5f3759df, 来将迭代次数降到极限的一次 (另外通过整形数的移位来进一步加速). 这让我不由得想到苏联飞机米格29引擎的暴力美学. 详细解说看这个:

Beyond3D - Origin of Quake3's Fast InvSqrt()

P.S. 为什么InvSqrt在Quake引擎中被重新实现? 这是因为3D变换过程中要涉及到大量的平方根翻转操作 (比如3D向量的normalization), 所以对于这个函数的优化对于3D引擎的效率非常关键. 这也直接促使nVIDIA在最新的GPU的指令集中自带"nrm"; 此命令来借用硬件加速直接完成3D向量的normalization操作.

类似的, Quake3里的开方函数源代码:

double sqrt( double x ) {
    float   y;
    float   delta;
    float   maxError;
 
    if ( x <= 0 ) {
        return 0;
    }

    // initial guess
    y = x / 2;

    // refine
    maxError = x * 0.001;
 
    do {
        delta = ( y * y ) - x;
        y -= delta / ( 2 * y );
    } while ( delta > maxError || delta < -maxError );
 
    return y;
}

同样也是短小精妙. 另外附带

Quake引擎架构分析:

Quake Source Code Review

Quake3代码分析:

Quake 3 Source Code Review: Architecture

Quake代码的GitHub页:

id-Software/Quake · GitHub

附带我偶像John Carmack的一张照片 (祝Oculus在Facebook帝国的支持下欣欣向荣):


另外, 收到

@陈然

答案的启发 (那个等概率选数问题我在Facebook的最后一面上刚好碰到, 因为之前面试根本没有准备过, 导致自己最后面试上绞尽脑汁...), 所以我说一个自己对Facebook一道面试题经过多年没事想想后的顿悟!!!

计算Fibonacci(斐波那契)数列的第n项: 这个之前在我的Facebook面试总结里面有写过, 可以递归也可以递推, 这样的复杂度是O(N), 也可以用矩阵相乘的算法, 这样可以O(LogN)的复杂度解决. 本来也就这样作罢, 但是最近看了"星际穿越"之后, 突然顿悟到这个Fibonacci和星际穿越有异曲同工之妙:

在一维世界中只能通过公式 F(n) = F(n-1) + F(n-2) 来计算. 这时, 线性的算法(O(N))是一维世界下的极限. 要如何加速呢? 类推到星际穿越中的话, 可行的方法就是升维, 从一维升到二维空间 (一维的F(n)升级到二维矩阵), 也就是说我们用升维将一维空间进行扭曲从而将距离缩短. 于是 [F(n), F(n-1)] = M^(n-1) [1, 1] ( M 是一个二维的常数矩阵). 这样我们便可以更快地从 [1, 1] 到达 [F(n), F(n-1)]. 看! 多么像一个数学世界里面的时空穿越呀!

先就到这里, 想到更多有趣的, 再进行补充.