[初学者笔记]从 求区间内完全平方数 中学到的

[初学者笔记]从 求区间内完全平方数 中学到的

Hanker rank上遇到一个求区间内完全平方数的题目,具体题目要求如下:

  • 要求:输入两个正整数A, B,求[A, B]内完全平方数的数量。( 1 ≤ A ≤ B ≤ 10^9 )

测试时输入有极大的数,如:385793959 712365911 ,因此要用long类型存储。

正在学C++,所以用C++实现,一开始用了特别笨的算法,但是在改进这个笨算法的时候也学到了很多,就把过程记录下来吧。

C/C++中测量程序运行时间的方法

要比较不同算法的效率,可以通过比较不同算法得出结果的运行时间来衡量。

使用标准库<time.h>/中的clock()函数,计时精度为ms级。使用方法举例如下:

#include<iostream>
#include<ctime>
void main()
{
   clock_t start,finish;
   double totaltime;
   start=clock();

   /*这里是要计时的代码*/

   finish=clock();
   totaltime=(double)(finish-start)/CLOCKS_PER_SEC;
   cout<<"该段程序花费时间:"<<totaltime<<"s. "<<endl;
}

另外在这里还提到了其他的计时方法,可供参考。

解决本题的算法与改进过程

算法1:

之前做了许多用循环解决的题,一上来就想了个最笨的办法:从a到b依次寻找开平方后为整数的数

    int count = 0;
    for (long i = a;i <= b;i++) {
        if (sqrt(i) == (long)sqrt(i)) {
            count++;
        }
    }

当输入的数都比较小的时候,这样运算看不出慢,但当数很大时,因为要验证每一个数开方后是否是整数,这种算法算一个区间就要很长时间,于是思考改进方法。

改进1:

上面这种算法要算两次sqrt,效率比较低,可以改为算一次开方和一次乘法。

    int count = 0;
    for (long i = a;i <= b;i++) {
        long root = (long)sqrt(i);
        if (root*root == i) {
        count++;
        }
    }
改进2:

继续改进,因为尾数不是1, 4, 9, 6, 5的整数一定不是完全平方数,先进行一次取余运算并判断,可省去一半多的开方运算,改进后:

    int count = 0;
    for (long i = a;i <= b;i++) {
        int tail = i % 10;
        if (tail == 0 ||tail == 1 || tail == 4 || tail == 5 || tail == 6 || tail == 9 )
        {
            long root = (long)sqrt(i);
            if (root*root == i) {
                count++;
            }
        }
    }
 改进3:

但上面这样还是很慢,就思考能不能从sqrt()上改进,于是搜索了很多开方的算法。发现开方计算的程序实现一般有二分法(binary search)、牛顿迭代法(Newton Raphson Method)等算法。

  • 二分法:
float bsqrt(long n) {
    float low = 0.0;
    float high = (float)n + 1;
    while ((high - low) > 0.00001f) {
        float mid = (low + high) / 2;
        if (mid*mid < n) {
            low = mid;
        }
        else {
            high = mid;
        }
    }
    return low;
}
  • 牛顿迭代法:
float SqrtByNewton(long x)
{
    float guess = x/2.0f;
    while (abs(guess*guess - x) > 0.00001f){ 
        guess = (guess + x / guess) / 2.0f;
    }
    return guess;
}

牛顿迭代法在求方程根时的应用十分广泛,且原理简单易懂,值得研究。

  • 一种据传是卡马克在Quake III中使用的一个计算平方根倒数(inverse square root)的算法,其中的0x5f3759df十分经典。稍加修改可以用来求平方根。
float Q_sqrt(float number)
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;
  
    x2 = number * 0.5F;
    y = number;
    i = *(long *)&y;                            // evil floating point bit level hacking
    i = 0x5f3759df - (i >> 1);                  // what the fuck? 
    y = *(float *)&i;
    y = y * (threehalfs - (x2 * y * y));        // 1st iteration
    //y  = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

    return number*y;
}
  • 还有人测试过各种开方算法的速度,并与标准库中的sqrt()进行过比较,链接在此

其中最快的一种是调用硬件fsqrt指令的算法,比标准库中的要快4倍多:

double inline __declspec (naked) __fastcall sqrt14(double n){
    _asm fld qword ptr[esp + 4]
    _asm fsqrt
    _asm ret 8
}

所以这次改进就选择了最快的开方算法 sqrt14() 函数。

算法2:

针对本题目还有一种特殊的方法:

每个完全平方数 x 都可以展开为 x = 1 + 3 + 5 + 7 + 9 + ... + (2n-1) 即奇数等差数列前n项和的形式。原因:等差数列 1 3 5 ... 2n-1 的前n项和 S = n(1+2n-1)/2 = n^2 ,倒推即可得。所以验证一个数是否为完全平方数,可以将此数减去1、3、5 … 直到得数为0或者负数,如果为0则说明这个数是完全平方数,如果为负数则说明不是。如果限制不允许进行开方计算,则可以使用此方法。

bool isroot(long n) {
    for (long odds = 1; ;odds += 2){
        n -= odds;
        if (n < 0)
            return false;
        else if (n == 0)
            return true;
    }
}

算法3:

这个题目实际也就是初中水平,一行代码就可以解决:(a、b 表示区间范围)

cout << (int)(floor(sqrt(b)) - ceil(sqrt(a)) + 1) << endl;

分别计算区间两端数的平方根,ceil返回不小于x的下一个整数,floor返回小于或等于x的最大整数。

例如区间是3到10,开平方后是1.732到3.162,所以取2到3,3减2再加上端点的1,答案就是2。无论数的范围有多大,都只用一步就算得出来。

当然,一上来就该用这个办法的。

测试结果

#1#1.1#1.2#1.3#2#3
100 10000000.075s0.041s0.031s0.011s1.322s0s
100 100000000.764s0.417s0.308s0.103s41.203s0s
385793959 71236591127.222s15.042s10.934s3.728sN/A0s

可见当输入的数逐渐变大时,上述改进是有效果的。#2算法由于要从n减到0,因此当数变大时速度明显变慢,最后一次输入几分钟都出不来结果。而用#3算法,则几乎不用耗费时间。

有时候连续做题做多了思维也会僵化,所以花在思考题目上的时间还是应该更多一点。

转载于:https://www.cnblogs.com/howlclat/p/6289573.html

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL初学者笔记是一份由一位学习据库的初学者总结的详细笔记,其中包含了MySQL的基础知识和高级应用。这份笔记包括了MySQL的高级查询方法和存储过程的编写,旨在帮助初学者更好地理解和应用MySQL据库。这份笔记中还提供了一些具体的代码示例,可以帮助开发者进行实践和应用。 在MySQL初学者笔记中,还涵盖了一些基本的语法规则和约束类型,比如非空、主键、唯一等。例如,在创建表时,可以在字段名和类型后面追加约束类型来设置列级约束。其中,支持的约束类型有:默认、非空、主键、唯一(除了外键都支持)。 这份笔记可以作为MySQL初学者的学习参考,特别是对于想要掌握MySQL的高级查询方法和利用存储过程编写复杂程序逻辑的学习者来说,是非常有用的。可以根据自己的需要,重点学习存储过程部分,并通过实践来提高自己的开发效率。 希望这份MySQL初学者笔记能为初学者们提供帮助,并带领他们更深入地学习和应用MySQL据库。如果有任何不对的地方,也欢迎指正和纠正。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [很详细的mysql据库笔记.pdf](https://download.csdn.net/download/dafeidouzi/12043750)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [MySQL学习笔记2-高级查询与存储.md](https://download.csdn.net/download/weixin_52057528/88240999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [尚硅谷MySQL基础学习笔记](https://blog.csdn.net/qq_21579045/article/details/98111827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值