• 小朋友学算法(1)


    求质数

    (一)质数

    质数,又称为素数,指在一个大于1的自然数中,除了1和此整数自身外,无法被其他自然数整除的数(只有1和本身两个因数的数)。

    (二)思路

    如果m不能被 2~m的平方根 中的任何一个数整除,则m为素数。

    证明(反证法):
    由i = m/i ==> i = sqrt(m)
    这样,对于i属于[2, sqrt(m)],假如i为m的因子,因为i * m/i = m,则m/i也为m的因子。这样,m就不是质数。
    反过来,对于i属于[2, sqrt(m)],假如所有的i都不为m的因子,因为i * m/i = m,则m/i也为m的因子。

    (三)程序

    例1:输入一个数,判断这个数是否为质数

    #include <iostream>
    #include <math.h>
    using namespace std;
    
    bool isPrime(int m)
    {
        if(m > 1)
        {
            for(int i = 2; i <= sqrt(m); i++)
            {
                if(0 == m % i)
                {
                    return false;
                }
            }
    
            return true;
        }
    
        return false;
    }
    
    int main()
    {
        int num;
        cin >> num;
        if(isPrime(num))
        {
            cout << num << " is a prime" << endl;
        }
        else
        {
            cout << num << " is not a prime" << endl;
        }
    
        return 0;
    }
    

    运行结果:

    23
    23 is a prime
    

    例2:求1~100之间的全部质数

    #include <iostream>
    #include <math.h>
    using namespace std;
    
    bool isPrime(int m)
    {
        if(m > 1)
        {
            for(int i = 2; i <= sqrt(m); i++)
            {
                if(0 == m % i)
                {
                    return false;
                }
            }
    
            return true;
        }
    
        return false;
    }
    
    int main()
    {
        for(int i = 2; i <= 100; i++)
        {
            if(isPrime(i))
            {
                cout << i << " ";
            }
        }
    
        return 0;
    }
    

    运行结果:

    2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97


    最大公约数

    一、最大公约数(Greatest Common Divisor)

    几个自然数,公有的因数,叫做这几个数的公约数;其中最大的一个,叫做这几个数的最大公约数。例如:12、16的公约数有1、2、4,其中最大的一个是4,4是12与16的最大公约数,一般记为(12、16)=4。12、15、18的最大公约数是3,记为(12、15、18)=3。

    二、编程求两个数的最大公约数

    求最大公约数有多种方法,没有专门学过方法的人,首先可能会联想到穷举法。

    (一)穷举法

    #include <stdio.h>
    
    // 穷举法 
    int gcd(int num1, int num2) 
    {
        // 求最小的那个数 
        int divisor = num1 < num2 ? num1 : num2; 
        for(; divisor >= 1; divisor--)
        {
            if(0 == num1 % divisor && 0 == num2 % divisor) 
            {
                // 找到最大公约数,跳出循环 
                break;
            }
        }
        
        return divisor; 
    }
    
    int main() 
    {
        int a, b;
        printf("Please input 2 numbers, seperated by space: ");
        scanf("%d %d", &a, &b);
        
        while(a > 0 && b > 0) 
        {
            printf("The greatest common divisor is: %d
    ", gcd(a,b));
            printf("Please input 2 numbers, seperated by space: ");
            scanf("%d %d", &a, &b);
        }
        
        printf("Loop end! Program will be finished!");
        
        return 0;
    }
    

    运行结果:

    Please input 2 numbers, seperated by space: 4 6
    The greatest common divisor is: 2
    Please input 2 numbers, seperated by space: 7 9
    The greatest common divisor is: 1
    Please input 2 numbers, seperated by space: 9 81
    The greatest common divisor is: 9
    Please input 2 numbers, seperated by space: 100 128
    The greatest common divisor is: 4
    Please input 2 numbers, seperated by space: 10000 15000
    The greatest common divisor is: 5000
    Please input 2 numbers, seperated by space: 0 0
    Loop end! Program will be finished!
    

    分析:
    穷举法虽然简单,但是有一个很大的缺点,就是效率低。比如咱们输入10000和15000,那么程序是从10000开始自减,一直减到5000,才得出了结果。这个过程for共执行了10000-5000+1 = 5001次。
    所以求最大公约数,通常不用穷举法。

    那么有没有其他求最大公约数的方法呢?
    有的。
    常见的有辗转相除法、相减法、短除法等。

    (二)辗转相除法

    思路:
    有两整数a和b
    ① a%b得余数c
    ② 若c=0,则b即为两数的最大公约数
    ③ 若c≠0,则a=b,b=c,再回去执行①

    例子: a = 10000 b = 15000,则运算过程为
    ① c = a % b = 10000 % 15000 = 10000, a = b = 15000, b = c = 10000
    ② c = a % b = 15000 % 10000 = 5000, a = b = 10000, b = c = 5000
    ③ c = a % b = 10000 % 5000 = 0, 则b = 5000即为最大公约数

    程序:

    #include <stdio.h>
    
    // 辗转相除法 + 递归 
    int gcd(int num1, int num2) 
    {
        if(0 == num2) 
        {
            return num1;
        }
        
        return gcd(num2, num1 % num2);
    }
    
    int main() 
    {
        int a, b;
        printf("Please input 2 numbers, seperated by space: ");
        scanf("%d %d", &a, &b);
        
        while(a > 0 && b > 0) 
        {
            printf("The greatest common divisor is: %d
    ", gcd(a,b));
            printf("Please input 2 numbers, seperated by space: ");
            scanf("%d %d", &a, &b);
        }
        
        printf("Loop end! Program will be finished!");
        
        return 0;
    }
    

    运行结果:

    Please input 2 numbers, seperated by space: 4 6
    The greatest common divisor is: 2
    Please input 2 numbers, seperated by space: 7 9
    The greatest common divisor is: 1
    Please input 2 numbers, seperated by space: 9 81
    The greatest common divisor is: 9
    Please input 2 numbers, seperated by space: 100 128
    The greatest common divisor is: 4
    Please input 2 numbers, seperated by space: 10000 15000
    The greatest common divisor is: 5000
    Please input 2 numbers, seperated by space: 0 0
    Loop end! Program will be finished!
    

    分析:
    与穷举法相比,求10000和15000的最大公约数,辗转相除法只循环了三次,就得到了结果。效率提高了很多。

    (三)相减法

    又叫更相减损法、等值算法,起源于《九章算术》。
    思路:
    有两整数a和b
    ① 若a>b,则a = a - b
    若a<b,则b = b - a
    ② 若a=b,则a(或b)即为两数的最大公约数
    若a≠b,则再回去执行①

    例子:求27和15的最大公约数过程为:
    ① a = a - b = 27-15=12
    ② b = b - a = 15-12=3
    ③ a = a - b = 12-3=9
    ④ a = a - b = 9-3=6
    ⑤ a = a - b = 6-3=3,此时a = b = 3,则3即为所求。

    程序:

    #include <stdio.h>
    
    // 相减法 
    int gcd(int num1, int num2) 
    {
        while(num1 != num2)
        {
            if(num1 > num2)
            {
                num1 -= num2;
            }
            else 
            {
                num2 -= num1;
            }
        }
        
        return num1;    
    }
    
    int main() 
    {
        int a, b;
        printf("Please input 2 numbers, seperated by space: ");
        scanf("%d %d", &a, &b);
        
        while(a > 0 && b > 0) 
        {
            printf("The greatest common divisor is: %d
    ", gcd(a,b));
            printf("Please input 2 numbers, seperated by space: ");
            scanf("%d %d", &a, &b);
        }
        
        printf("Loop end! Program will be finished!");
        
        return 0;
    }
    

    运行结果:

    Please input 2 numbers, seperated by space: 4 6
    The greatest common divisor is: 2
    Please input 2 numbers, seperated by space: 7 9
    The greatest common divisor is: 1
    Please input 2 numbers, seperated by space: 9 81
    The greatest common divisor is: 9
    Please input 2 numbers, seperated by space: 100 128
    The greatest common divisor is: 4
    Please input 2 numbers, seperated by space: 10000 15000
    The greatest common divisor is: 5000
    Please input 2 numbers, seperated by space: 0 0
    Loop end! Program will be finished!
    

    (四)短除法

    思路:

     
    1.png

    左边部分的因子相乘,即为最大公约数。
    所以,12与16的最大公约数为2 * 2 = 4

    程序:

    #include <stdio.h>
    
    // 短除法 
    int gcd(int m, int n) 
    {
        int min = m < n ? m : n;
        int s = 1;
        int i;
        for(i = 2; i <= min ; i++)
        {
            // 四个条件只要有一个不满足,while循环结束 
            while(m > 0 && n > 0 && 0 == m % i && 0 == n % i)
            {
                m /= i;
                n /= i;
                s *= i;
            }
        }
        
        return s;   
    }
    
    int main() 
    {
        int a, b;
        printf("Please input 2 numbers, seperated by space: ");
        scanf("%d %d", &a, &b);
        
        while(a > 0 && b > 0) 
        {
            printf("The greatest common divisor is: %d
    ", gcd(a,b));
            printf("Please input 2 numbers, seperated by space: ");
            scanf("%d %d", &a, &b);
        }
        
        printf("Loop end! Program will be finished!");
        
        return 0;
    }
    

    运行结果:

    Please input 2 numbers, seperated by space: 4 6
    The greatest common divisor is: 2
    Please input 2 numbers, seperated by space: 7 9
    The greatest common divisor is: 1
    Please input 2 numbers, seperated by space: 9 81
    The greatest common divisor is: 9
    Please input 2 numbers, seperated by space: 100 128
    The greatest common divisor is: 4
    Please input 2 numbers, seperated by space: 10000 15000
    The greatest common divisor is: 5000
    Please input 2 numbers, seperated by space: 0 0
    Loop end! Program will be finished!

    最小公倍数

    一、最小公倍数(Least Common Multiple)

    几个自然数公有的倍数,叫做这几个数的公倍数,其中最小的一个,叫做这几个数的最小的一个,叫做这几个数的最小公倍数。例如:4的倍数有4、8、12、16,……,6的倍数有6、12、18、24,……,4和6的公倍数有12、24,……,其中最小的是12,一般记为[4、6]=12。
    12、15、18的最小公倍数是180。记为[12、15、18]=180。

    二、编程求两个数的最小公倍数

    (一)穷举法

    #include <stdio.h>
    
    // 穷举法 
    int lcm(int m, int n)
    {
        // 取两个数较大的那个。因为最小公倍数不可能比大的那个数还小
        int num = m < n ? n : m;
        // m*n一定是m和n的公倍数,所以做为循环的结束条件 
        for(; num  <= m * n; num++)
        {
            if(0 == num % m && 0 == num % n)
            {
                break;
            }   
        }
        
        return num ;
    }
    
    
    int main() 
    {
        int a, b;
        printf("Please input 2 numbers, seperated by space: ");
        scanf("%d %d", &a, &b);
        
        while(a > 0 && b > 0) 
        {
            printf("The least common multiple is: %d
    ", lcm(a,b));
            printf("Please input 2 numbers, seperated by space: ");
            scanf("%d %d", &a, &b);
        }
        
        printf("Program will be finished!");
        
        return 0;
    }
    

    运行结果:

    Please input 2 numbers, seperated by space: 4 6
    The least common multiple is: 12
    Please input 2 numbers, seperated by space: 7 13
    The least common multiple is: 91
    Please input 2 numbers, seperated by space: 1000 1500
    The least common multiple is: 3000
    Please input 2 numbers, seperated by space: 0 0
    Program will be finished!
    

    穷举法的优点是思路简单,缺点是效率低。多数情况下,都不能使用穷举法。但是穷举法本身是一种非常重要的思想。

    (二)利用最大公约数求最小公倍数

    思路:
    lcm(a, b) = a * b / gcd(a, b)

    例子:
    gcd(12, 16) = 4
    lcm(12, 16) = 12 * 16 / gcd(12, 16) = 48

    程序:

    #include <stdio.h>
    
    // 辗转相除法求最大公约数 
    int gcd(int m, int n) 
    {
        // remainder,余数 
        int remainder; 
        while(n != 0) 
        {
            remainder = m % n;
            m = n;
            n = remainder;
        }
        
        return m;    
    }
    
    // 最小公倍数 = 两数相乘 / 最大公约数
    int lcm(int x, int y)
    {
        return x * y / gcd(x, y);
    } 
    
    int main() 
    {
        int a, b;
        printf("Please input 2 numbers, seperated by space: ");
        scanf("%d %d", &a, &b);
        
        while(a > 0 && b > 0) 
        {
            printf("The least common multiple is: %d
    ", lcm(a,b));
            printf("Please input 2 numbers, seperated by space: ");
            scanf("%d %d", &a, &b);
        }
        
        printf("Program will be finished!");
        
        return 0;
    }
    

    运行结果:

    Please input 2 numbers, seperated by space: 4 6
    The least common multiple is: 12
    Please input 2 numbers, seperated by space: 7 13
    The least common multiple is: 91
    Please input 2 numbers, seperated by space: 1000 1500
    The least common multiple is: 3000
    Please input 2 numbers, seperated by space: 0 0
    Program will be finished!
    

    (三)短除法
    思路:

     
    1.png

    左边与底部的因子相乘,即为最小公倍数
    所以,12与16的最小公倍数为2 * 2 * 3 * 4 = 48

    程序:

    #include <stdio.h>
    
    // 短除法 
    int lcm(int m, int n) 
    {
        int min = m < n ? m : n;
        int s = 1;
        int i;
        for(i = 2; i <= min ; i++)
        {
            // 四个条件只要有一个不满足,while循环结束 
            while(m > 0 && n > 0 && 0 == m % i && 0 == n % i)
            {
                m /= i;
                n /= i;
                s *= i;
            }
        }
        
        return s * m * n;   
    }
    
    int main() 
    {
        int a, b;
        printf("Please input 2 numbers, seperated by space: ");
        scanf("%d %d", &a, &b);
        
        while(a > 0 && b > 0) 
        {
            printf("The least common multiple is: %d
    ", lcm(a,b));
            printf("Please input 2 numbers, seperated by space: ");
            scanf("%d %d", &a, &b);
        }
        
        printf("Program will be finished!");
        
        return 0;
    }
    

    运行结果:

    Please input 2 numbers, seperated by space: 4 6
    The least common multiple is: 12
    Please input 2 numbers, seperated by space: 7 13
    The least common multiple is: 91
    Please input 2 numbers, seperated by space: 1000 1500
    The least common multiple is: 3000
    Please input 2 numbers, seperated by space: 0 0
    Program will be finished!

    寻找发帖水王

    (一)题目

    Tango是微软亚洲研究院的一个试验项目。研究院的员工和实习生们都很喜欢在Tango上面交流灌水。传说,Tango有一大“水王”,他不但喜欢发帖,还会回复其他ID发的每个帖子。坊间风闻该“水王”发帖数目超过了帖子总数的一半。如果你有一个当前论坛上所有帖子(包括回帖)的列表,其中帖子作者的ID也在表中,你能快速找出这个传说中的Tango水王吗?

    (二)分析

    思路一:
    先对ID进行排序,再遍历排序后的序列,统计每个ID的次数,从而寻找到最大次数的ID。

    思路二:
    如果每次删除两个不同的ID(不管是否包含“水王”的ID),那么,在剩下的ID列表中,“水王”ID出现的次数仍然超过总数的一半。看到这一点之后,就可以通过不断重复这个过程,把ID列表中的ID总数降低(转化为更小的问题),从而得到问题的答案。新的思路,总的时间复杂度只有O(N),且只需要常数的额外内存。

    对比思路一和思路二,第一种思路效率更低,实现起来更复杂。所以这里咱们采用思路二。

    (三)代码

    #include<iostream>
    using namespace std;
    
    int Find(int* ID, int N)
    {   
        int candidate;
        int nTimes = 0;
        int i;
        
        for(i = 0; i < N; i++)      
        {   
            if(nTimes == 0) 
            {   
                candidate = ID[i];
                nTimes = 1;     
            }
            else    
            {   
                if(ID[i] == candidate)  
                {
                    nTimes++;
                }    
                else
                {
                    nTimes--;
                }         
            }
        }
            
        return candidate;   
    }
    
    int main(void)
    {
        int id[] = {1, 2, 2, 4, 2, 4, 2, 2};
        int cnt = sizeof(id) / sizeof(int);
        int res = Find(id, cnt);
        cout << "The water king's id is " << res << endl;
        
        return 0;
    }
    

    运行结果:

    The water king’s id is 2
    

    说明:
    int Find(int* ID, int N)等价于int Find(int ID[], int N),这两种写法是一样的。
    这是因为,数组名在做形式参数时,自动退化为指针,这个指针指向了数组的首地址。

    分析:
    这种算法题,要对循环里的数据逐个分析。
    i = 0时,ID[0] = 1, candidate = ID[0] = 1, nTimes 赋值 1
    i = 1时,ID[1] = 2, nTimes 自减后为 0(至此,相当于把ID[0] 和 ID[1]删掉)
    i = 2时,ID[2] = 2, candidate = ID[2] = 2, nTimes 赋值 1
    i = 3时,ID[3] = 4, nTimes 自减后为 0 (至此,相当于把ID[2] 和 ID[3]删掉)
    i = 4时,ID[4] = 2, candidate = ID[4] = 2, nTimes 赋值 1
    i = 5时,ID[5] = 4, nTimes = 自减后为 0 (至此,相当于把ID[4] 和 ID[5]删掉)
    i = 6时,ID[6] = 2, candidate = ID[6] = 2, nTimes 赋值 1
    i = 7时,ID[7] = 2, nTimes 自加后为 2
    i = 8时,for循环结束。
    最终, candidate = 2即为所求。此时nTimes = 2,表示删除之后,ID为2的帖子还剩下两个。

    (四)结论

    在这个题目中,有一个计算机科学中很普遍的思想,就是如何把一个问题转化为规模较小的若干个问题。分治、递推和贪心等都是基于这样的思路。在转化过程中,小的问题跟原问题本质上一致。这样,我们可以通过同样的方式将小问题转化为更小的问题。因此,转化过程是很重要的。
    像上面这个题目,我们保证了问题的解在小问题中仍然具有与原问题相同的性质:水王的ID在ID列表中的次数超过一半。
    转化本身计算的效率越高,转化之后问题规模缩小得越快,则整体算法的时间复杂度越低。



    求幂pow函数的四种实现方式

    在math.h中,声明了一个函数pow(x, n),用于求x的n次方。
    假如咱们不调用math.h中的pow函数,如何实现求x ^ n的算法呢?

    一、while非递归

    #include <stdio.h>
    
    double pow1(double x, unsigned int n)
    {
        int res = 1;
        while(n--)
        {
            res *= x;
        }
    
        return res;
    }
    
    int main()
    {
        printf("2 ^ 10 = %f
    ", pow1(2, 10));
        printf("5 ^ 3 = %f
    ", pow1(5, 3));
        printf("10 ^ 0 = %f
    ", pow1(10, 0));
    
        return 0;
    }
    

    运行结果:

    2 ^ 10 = 1024.000000
    5 ^ 3 = 125.000000
    10 ^ 0 = 1.000000
    

    二、递归方法1

    #include <stdio.h>
    
    double pow2(double x, unsigned int n)
    {
        if(0 == n)
        {
            return 1;
        }
        if(1 == n)
        {
            return x;
        }
    
        return x * pow2(x, n - 1);
    }
    
    int main()
    {
        printf("2 ^ 10 = %f
    ", pow2(2, 10));
        printf("5 ^ 3 = %f
    ", pow2(5, 3));
        printf("10 ^ 0 = %f
    ", pow2(10, 0));
    
        return 0;
    }
    

    三、递归方法2

    #include <stdio.h>
    
    double pow3(double x, unsigned int n)
    {
        if(0 == n)
        {
            return 1;
        }
        if(1 == n)
        {
            return x;
        }
    
        if(n & 1)   // 如果n是奇数
        {
            // 这里n/2会有余数1,所以需要再乘以一个x
            return pow3(x * x, n / 2) * x;
        }
        else        // 如果x是偶数
        {
            return pow3(x * x, n / 2);
        }
    }
    
    int main()
    {
        printf("2 ^ 10 = %f
    ", pow3(2, 10));
        printf("5 ^ 3 = %f
    ", pow3(5, 3));
        printf("10 ^ 0 = %f
    ", pow3(10, 0));
    
        return 0;
    }
    

    四、快速幂

    上面三种方法都有一个缺点,就是循环次数多,效率不高。举个例子:
    3 ^ 19 = 3 * 3 * 3 * … * 3
    直接乘要做18次乘法。但事实上可以这样做,先求出3的2^k次幂:
    3 ^ 2 = 3 * 3
    3 ^ 4 = (3 ^ 2) * (3 ^ 2)
    3 ^ 8 = (3 ^ 4) * (3 ^ 4)
    3 ^ 16 = (3 ^ 8) * (3 ^ 8)
    再相乘:
    3 ^ 19 = 3 ^ (16 + 2 + 1)
    = (3 ^ 16) * (3 ^ 2) * 3
    这样只要做7次乘法就可以得到结果:
    3 ^ 2 一次,
    3 ^ 4 一次,
    3 ^ 8 一次,
    3 ^ 16 一次,
    乘四次后得到了3 ^ 16
    3 ^ 2 一次,
    (3 ^ 2) * 3 一次,
    再乘以(3 ^ 16) 一次,
    所以乘了7次得到结果。

    如果幂更大的话,节省的乘法次数更多(但有可能放不下)。
    即使加上一些辅助的存储和运算,也比直接乘高效得多。

    我们发现,把19转为2进制数:10011,其各位就是要乘的数。这提示我们利用求二进制位的算法:

    所以就可以写出下面的代码:

    #include <stdio.h>
    
    double pow4(double x, int n)
    {
        double res = 1;
        while (n)
        {
            if (n & 1)        // 等价于 if (n % 2 != 0)
            {
                res *= x;
            }
    
            n >>= 1;
            x *= x;
        }
    
        return res;
    }
    
    int main()
    {
        printf("2 ^ 10 = %f
    ", pow4(2, 10));
        printf("5 ^ 3 = %f
    ", pow4(5, 3));
        printf("10 ^ 0 = %f
    ", pow4(10, 0));
        printf("3 ^ 19 = %f
    ", pow4(3, 19));
    
        return 0;
    }
    

    运行结果:

    2 ^ 10 = 1024.000000
    5 ^ 3 = 125.000000
    10 ^ 0 = 1.000000
    3 ^ 19 = 1162261467.000000
    

    五、效率比较

    #include <stdio.h>
    #include <math.h>
    #include <time.h>
    using namespace std;
    
    #define COUNT 100000000
    
    
    double pow1(double x, unsigned int n)
    {
        int res = 1;
        while(n--)
        {
            res *= x;
        }
    
        return res;
    }
    
    
    double pow2(double x, unsigned int n)
    {
        if(0 == n)
        {
            return 1;
        }
        if(1 == n)
        {
            return x;
        }
    
        return x * pow2(x, n - 1);
    }
    
    
    double pow3(double x, unsigned int n)
    {
        if(0 == n)
        {
            return 1;
        }
        if(1 == n)
        {
            return x;
        }
    
        if(n & 1)   // 如果n是奇数
        {
            // 这里n/2会有余数1,所以需要再乘以一个x
            return pow3(x * x, n / 2) * x;
        }
        else        // 如果x是偶数
        {
            return pow3(x * x, n / 2);
        }
    }
    
    
    double pow4(double x, int n)
    {
        double result = 1;
        while (n)
        {
            if (n & 1)
                result *= x;
            n >>= 1;
            x *= x;
        }
        return result;
    }
    
    
    int main()
    {
        int startTime, endTime;
        
        startTime = clock();
        for (int i = 0; i < COUNT; i++)
        {
            pow(2.0, 100.0);
        }
        endTime = clock();
        printf("调用系统函数计算1亿次,运行时间%d毫秒
    ", (endTime - startTime));
    
        startTime = clock();
        for (int i = 0; i < COUNT; i++)
        {
            pow1(2.0, 100);
        }
        endTime = clock();
        printf("调用pow1函数计算1亿次,运行时间%d毫秒
    ", (endTime - startTime));
    
        startTime = clock();
        for (int i = 0; i < COUNT; i++)
        {
            pow2(2.0, 100);
        }
        endTime = clock();
        printf("调用pow2函数计算1亿次,运行时间%d毫秒
    ", (endTime - startTime));
    
        startTime = clock();
        for (int i = 0; i < COUNT; i++)
        {
            pow3(2.0, 100);
        }
        endTime = clock();
        printf("调用pow3函数计算1亿次,运行时间%d毫秒
    ", (endTime - startTime));
    
    
        startTime = clock();
        for (int i = 0; i < COUNT; i++)
        {
            pow4(2.0, 100);
        }
        endTime = clock();
        printf("调用pow4函数计算1亿次,运行时间%d毫秒
    ", (endTime - startTime));
    
        return 0;
    }
    

    运行结果:

    调用系统函数计算1亿次,运行时间189毫秒
    调用pow1函数计算1亿次,运行时间795670毫秒
    调用pow2函数计算1亿次,运行时间89756毫秒
    调用pow3函数计算1亿次,运行时间6266毫秒
    调用pow4函数计算1亿次,运行时间3224毫秒
    

    从运行结果可以看出来,
    最快的是math.h提供的函数pow,
    接下来依次是pow4、pow3、 pow2,
    最慢的是pow1。

    六、math.h中的pow函数源码

    我使用的编译器是CodeBlocks,没法查看math.h的源码。
    但是我在网络上找到了微软的math.h源码 http://www.bvbcode.com/cn/z9w023j8-107349
    这里有关于pow函数的实现

    template<class _Ty> inline
            _Ty _Pow_int(_Ty _X, int _Y)
            {unsigned int _N;
            if (_Y >= 0)
                    _N = _Y;
            else
                    _N = -_Y;
            for (_Ty _Z = _Ty(1); ; _X *= _X)
                    {if ((_N & 1) != 0)
                            _Z *= _X;
                    if ((_N >>= 1) == 0)
                            return (_Y < 0 ? _Ty(1) / _Z : _Z); }}
    

    这个实现思路跟pow4的实现思路是一致的。

    七、结论

    在实际编程时,可以直接调用math.h提供的pow函数;
    如果在特定场合需要自己定义的话,使用pow4的方式。


    贪心算法与动态规划算法

    一、贪心算法

    例子

    假设有1元,5元,11元这三种面值的硬币,给定一个整数金额,比如28元,最少使用的硬币组合是什么?

    分析

    碰到这种问题,咱们很自然会想起先用最大的面值,再用次大的面值……这样得到的结果为两个11元,一个5元,一个1元,总共是四个硬币。

    C语言实现

    #include<stdio.h>
    
    void greed(int m[],int k,int total);
    
    int main(void)
    {
        int money[] = {11, 5, 1};
        int n;
        n = sizeof(money)/sizeof(money[0]);
        greed(money, n, 28);
    
        return 0;
    }
    
    /*
    *  m[]:存放可供找零的面值,降序排列
    *  k:可供找零的面值种类数
    *  total:需要的总金额
    */
    void greed(int m[],int n,int total)
    {
        int i;
        for(i = 0; i < n; i++)
        {
          while(total >= m[i])
          {
              printf("%d ", m[i]);
              total -= m[i];
          }
        }
    }
    

    运行结果:

    11 11 5 1
    

    思想

    贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。

    不足

    上面的例子,total = 28,得到的“11 11 5 1”恰巧是最优解。
    假如total = 15呢?
    total = 15时,结果为“11 1 1 1 1”,共用了五枚硬币。但是这只能算是较优解,不是最优解。因为最优解是“5 5 5”,共三枚硬币。
    所以贪心算法只能保证局部最优(第一枚11就是局部最优),不能保证全局最优。

    二、动态规划算法

    咱们仍以15为例,换一种思路,看看如何得到最优解。
    (1)面值为1时,最少需要一个一元硬币

    (2)面值为2时,最少需要两个一元硬币

    (3)面值为3时,最少需要三个一元硬币

    (4)面值为4时,最少需要四个一元硬币

    (5)面值为5时,有两个方案:
    ① 在面值为4的基础上加一个1元的硬币,需要五个硬币
    ② 挑一个面值为5元的硬币,需要一个硬币
    取最小值,需要一个硬币

    (6)面值为6时,两个方案:
    ① 比1元(一个硬币)多了5元(一个硬币),需要两个硬币
    ② 比5元(一个硬币)多了1元(一个硬币),需要两个硬币
    取最小值,需要两个硬币

    (7)面值为7时,两个方案:
    ① 比1元(一个硬币)多了6元(两个硬币),需要三个硬币
    ② 比5元(一个硬币)多了2元(两个硬币),需要三个硬币
    取最小值,需要三个硬币

    (8)面值为8时,两个方案:
    ① 比1元(一个硬币)多了7元(三个硬币),需要四个硬币
    ② 比5元(一个硬币)多了3元(三个硬币),需要四个硬币
    取最小值,需要四个硬币

    (9)面值为9时,两个方案:
    ① 比1元(一个硬币)多了8元(四个硬币),需要五个硬币
    ② 比5元(一个硬币)多了4元(四个硬币),需要五个硬币
    取最小值,需要五个硬币

    (10)面值为10时,两个方案:
    ① 比1元(一个硬币)多了9元(五个硬币),需要六个硬币
    ② 比5元(一个硬币)多了5元(一个硬币),需要两个硬币
    取最小值,需要两个硬币

    (11)面值为11时,三个方案:
    ① 比1元(一个硬币)多了10元(两个硬币),需要三个硬币
    ② 比5元(一个硬币)多了6元(两个硬币),需要三个硬币
    ③ 取面值为11元的硬币,需要一个硬币
    取最小值,需要一个硬币

    (12)面值为12时,三个方案:
    ① 比1元(一个硬币)多了11元(一个硬币),需要两个硬币
    ② 比5元(一个硬币)多了7元(三个硬币),需要四个硬币
    ③ 比11元(一个硬币)多了1元(一个硬币),需要两个硬币
    取最小值,需要两个硬币

    (13)面值为13时,三个方案:
    ① 比1元(一个硬币)多了12元(两个硬币),需要三个硬币
    ② 比5元(一个硬币)多了8元(四个硬币),需要五个硬币
    ③ 比11元(一个硬币)多了2元(两个硬币),需要三个硬币
    取最小值,需要三个硬币

    (14)面值为14时,三个方案:
    ① 比1元(一个硬币)多了13元(三个硬币),需要四个硬币
    ② 比5元(一个硬币)多了9元(五个硬币),需要六个硬币
    ③ 比11元(一个硬币)多了3元(三个硬币),需要四个硬币
    取最小值,需要四个硬币

    (15)面值为15时,三个方案:
    ① 比1元(一个硬币)多了14元(四个硬币),需要五个硬币
    ② 比5元(一个硬币)多了10元(两个硬币),需要三个硬币
    ③ 比11元(一个硬币)多了4元(四个硬币),需要五个硬币
    取最小值,需要三个硬币

    最终,得到的最小硬币数是3。并且从推导过程可以看出,计算一个数额的最少硬币数,比如15,必须把它前面的所有数额(1~14)的最少硬币数都计算出来。这够成了一个递推(注意不是递归)的过程。

    上述推导过程的Java实现:

    public class CoinDP {
    
        /**  
         * 动态规划算法  
         * @param values:    保存所有币值的数组  
         * @param money:     金额  
         * @param minCoins:  保存所有金额所需的最小硬币数 
         */ 
        public static void dp(int[] values, int money, int[] minCoins) {  
    
            int valueKinds = values.length;
            minCoins[0] = 0;
            
            // 保存1元、2元、3元、……、money元所需的最小硬币数  
            for (int sum = 1; sum <= money; sum++) {  
    
                // 使用最小币值,需要的硬币数量是最多的
                int min = sum;  
    
                // 遍历每一种面值的硬币
                for (int kind = 0; kind < valueKinds; kind++) {               
                    // 若当前面值的硬币小于总额则分解问题并查表  
                    if (values[kind] <= sum) {  
                        int temp = minCoins[sum - values[kind]] + 1;  
                        if (temp < min) {  
                            min = temp;  
                        }  
                    }  else {
                        break;
                    }
                } 
                
                // 保存最小硬币数  
                minCoins[sum] = min;  
    
                System.out.println("面值为 " + sum + " 的最小硬币数 : " + minCoins[sum]);  
            }  
        }  
    
        public static void main(String[] args) {  
    
            // 硬币面值预先已经按升序排列
            int[] coinValue = new int[] {1,5,11};  
            
            // 需要的金额(15用动态规划得到的是3(5+5+5),用贪心得到的是5(11+1+1+1+1)
            int money = 15;  
            
            // 保存每一个金额所需的最小硬币数,0号单元舍弃不用,所以要多加1  
            int[] coinsUsed = new int[money + 1];  
    
            dp(coinValue, money, coinsUsed);  
        }  
    } 
    

    运行结果:

    面值为1的最小硬币数:1
    面值为2的最小硬币数:2
    面值为3的最小硬币数:3
    面值为4的最小硬币数:4
    面值为5的最小硬币数:1
    面值为6的最小硬币数:2
    面值为7的最小硬币数:3
    面值为8的最小硬币数:4
    面值为9的最小硬币数:5
    面值为10的最小硬币数:2
    面值为11的最小硬币数:1
    面值为12的最小硬币数:2
    面值为13的最小硬币数:3
    面值为14的最小硬币数:4
    面值为15的最小硬币数:3
    

    三、贪心算法与动态规划的区别

    (1)贪心是求局部最优,但不定是全局最优。若想全局最优,必须证明。
    dp是通过一些状态来描述一些子问题,然后通过状态之间的转移来求解。般只要转移方程是正确的,答案必然是正确的。

    (2)动态规划本质上是穷举法,只是不重复计算罢了。结果是最优的。复杂度高。
    贪心算法不一定最优。复杂度一般较低。

    (3)贪心只选择当前最有利的,不考虑这步选择对以后的选择造成的影响,眼光短浅,不能看出全局最优;动规是通过较小规模的局部最优解一步步最终得出全局最优解。

    (4)从推导过程来看,动态规划是贪心的泛化,贪心是动态规划的特例


  • 相关阅读:
    2020年面向对象程序设计寒假作业1_实践题
    2020年面向对象程序设计寒假作业1_问答题
    实验5:开源控制器实践——POX
    实验4:开源控制器实践——OpenDaylight
    实验3:OpenFlow协议分析实践
    实验2:Open vSwitch虚拟交换机实践
    第一次个人编程作业
    实验1:SDN拓扑实践
    第一次博客作业
    面向对象程序设计寒假作业3
  • 原文地址:https://www.cnblogs.com/alan-blog-TsingHua/p/9607653.html
Copyright © 2020-2023  润新知