全部学习汇总: https://github.com/GreyZhang/g_SICP

1146_SICP学习笔记_幂运算_p2p

    这是一个幂运算的求解,使用了递归的方式实现。按照我过去多年的经验,其实类似的求解我可能首先会想到一个迭代的算法。我觉得SICP一直在讲递归的算法是因为递归更加符合计算机的思维,很多推导的过程其实是计算机自己去完成的。而迭代计算,其实更多时候是在做一个过程的重复。

1146_SICP学习笔记_幂运算_算法_02

    这是一个线性迭代的实现,其实看着也有自我调用的过程,但是这个跟前面求解前n个自然数的乘积的算法类似。从过程的存储的角度来思考,其实是一个迭代。因为计算的过程的传递不是通过未解函数,而是通过中间变量。

    这是一个很有意思的实现方式,同样的实现,我可以用C语言来写一遍。大概如下:

#include "stdio.h"

#include "stdint.h"

uint32_t expt(uint32_t b, uint32_t n);

static void expt_iter(uint32_t b, uint32_t *p_counter, uint32_t *p_product);

uint32_t expt(uint32_t b, uint32_t n)

{

    uint32_t result = 1U;

    uint32_t n_temp = n;

    expt_iter(b, &n_temp, &result);

    return result;

}

static void expt_iter(uint32_t b, uint32_t *p_counter, uint32_t *p_product)

{

    if(*p_counter == 0U)

    {

        return;

    }

    else

    {

        *p_counter -= 1U;

        *p_product *= b;

        expt_iter(b, p_counter, p_product);

    }

}

int main(void)

{

    printf("2 ** 5 = %d\n", expt(2, 5));

    printf("2 ** 10 = %d\n", expt(2, 10));

    return 0;

}

    测试效果如下:

1146_SICP学习笔记_幂运算_算法_03

    上面的软件设计,使用了指针操作外部的存储,这样在调试上会更加便捷。软件当然也可以完全照搬lisp的模式:

#include "stdio.h"

#include "stdint.h"

uint32_t exp(uint32_t b, uint32_t n);

uint32_t exp_iter(uint32_t b, uint32_t counter, uint32_t product);

uint32_t exp_iter(uint32_t b, uint32_t counter, uint32_t product)

{

    if(counter == 0U)

    {

        return  product;

    }

    else

    {

        return exp_iter(b, counter - 1, b * product);

    }

}

uint32_t exp(uint32_t b, uint32_t n)

{

    exp_iter(b, n, 1);

}

int main(void)

{

    printf("%d\n", exp(2, 5));

    printf("%d\n", exp(2, 10));

    return 0;

}

    这样的函数的实际价值可能不是很大,尤其是做准确的数据运算,毕竟C语言在计算上有数据类型上的限制可能出现溢出。我测试使用的也是程序员非常熟知的运算,这样可以一下子就知道计算结果的正确与否。那么,为什么这样的设计不是递归而是迭代呢?其实,这里面有一个自我调用的过程,但是反复自我调用的时候也不过是重复一个简单的规则而已。重复的过程中,并不会用到上一次的函数的返回结果。这样,整个处理过程其实是一个按照规则修改外部存储的过程。递归的求解巧妙转换成了一个迭代的过程。

1146_SICP学习笔记_幂运算_p2p_04

1146_SICP学习笔记_幂运算_sicp_05

    这里给出了一个优化的过程,让计算的复杂度由指数变成了对数的特性。处理的方式也非常巧妙,按照奇数偶数的属性来进行分类处理。而每一次的处理,进一步做了一个计算上的复杂度简化,让递归的内容进一步减少了。这样的处理的确是非常值得去斟酌思索,我们在各种逻辑处理的时候会反复考虑各种二分法、替代运算等计算方式。其实在大规模的程序设计之前,基础的算法中类似的思想也是可以采用的。久而久之,我们的软件设计从思维上到质量上肯定都会是一个质的飞跃。

    如果不容易用迭代来实现转换的递归算法一般在设计的时候就是选择递归来设计了,如果容易转换或者说一看就容易通过迭代来实现的算法其实很容易实现一个没有for或者while循环的算法。这从另一个角度思考其实也容易理解,排除算法实现,直接从编程语言的角度来看能够做软件实现的前提其实是图灵完备。而完备的图灵表达模式之中,其实并没有对这两个表达式有一个必要的需求。