整数平方根:整数开方及大整数开方解决方法

求整数N的开方,精度在0.001

二分法

若N大于1,则从[1, N]开始,low = 1, high = N, mid = low + (high - low) >> 1开始进行数值逼近

若N小于1,则从[N, 1]开始,low = 0, high = N, mid = low + (high - low) >> 1开始进行数值逼近

#include <stdio.h>
#include <stdlib.h>
#include <math.h>  

#define ACCURACY 0.001  

double newSqrt(double n)
{
    double low, high, mid, tmp;  

    // 获取上下界
    if (n > 1)   {
        low = 1;
        high = n;
    } else {
        low = n;
        high = 1;
    }  

    // 二分法求开方
    while (low <= high) {
        mid = (low + high) / 2.000;  

        tmp = mid * mid;  

        if (tmp - n <= ACCURACY && tmp -n >= ACCURACY * -1) {
            return mid;
        } else if (tmp > n) {
            high = mid;
        } else {
            low = mid;
        }
    }  

    return -1.000;
}  

int main(void)
{
    double n, res;  

    while (scanf("%lf", &n) != EOF) {
        res = newSqrt(n);
        printf("%lf\n", res);
    }  

    return 0;
}  

牛顿迭代法思路求解:

#include<iostream>
using namespace std;
int main()
{
    int N;
    cout<<"输入N的值:";
    cin>>N

    double x1 = 1;//初值
    double x2 = x1/2.0+N/2.0/x1;
    while( fabs(x2-x1)>0.001)
    {
        x1 = x2;
        x2 = x1/2.0+N/2.0/x1;
    }
    cout<<x1<<endl;

    return 0;
}

整数开方定义

所谓整数平方根即

算法

算法1.猜试法

利用等差级数公式:

这样的话, 从1开始一直算到数列的前项和第一次大于x的时候,即是所求。下面给出source
code(C):

unsigned linear_search(unsigned long x)

{

unsigned long sum_n = 1;

unsigned n = 1;

if(x <= 1)

{

return x;

}

while(sum_n <= x)

{

n++;

sum_n += (n<<1) - 1;

}

return (n-1);

}

这种方法无异于穷举法,其唯一的优点是:每次的迭代用到了前面迭代的结果,所以会有一些效率的增益。对于该算法的改进就是不穷举,改用我们熟悉的二分查找法来做。(二分逼近法)

unsigned bi_search(unsigned long x)

{

unsigned long sum_n = 0;

unsigned n = (x >> 1);

unsigned top = x;

unsigned bottom = 0;

if (x <= 1)

{

return x;

}

for (;;)

{

sum_n = n * n;

if (sum_n < x)

{

bottom = n;

n += ((top - bottom) >> 1);

if (n == bottom)

return n;

}

else

if (sum_n > x)

{

top = n;

n -= ((top - bottom) >>1);

if (n == top)

return n-1;

}

else

{

return n;

}

}

}

算法2 Newton 法

把这个问题转换为方程求根问题,即:,求x。

而方程求根的问题可以用Newton 法来解决。现在的问题有一点不同,即所求的根必须是整数。通过证明,我们可以发现,Newton迭代公式是适用于整数情况的,于是有:

至于是怎么证明的,可以参考hacker’s delight

另外,初值的选择也是很重要的一环,这里我们选择大于等于的最小的2的幂次数。

OK,下面给出程序:

unsigned newton_method(unsigned long x)

{

unsigned long x1 = x - 1;

unsigned s = 1;

unsigned g0,g1;

/* 初值设定  通常将初始值设为1,但是只有1的开方才会是1,通过预处理找到更精确地初始值a[n]*/

if (x1 > 65535) {s += 8; x1 >>= 16;}

if (x1 > 255)   {s += 4; x1 >>= 8;}

if (x1 > 15)    {s += 2; x1 >>= 4;}

if (x1 > 3)     {s += 1; x1 >>= 2;}

/*迭代*/

g0 = 1 << s;

g1 = (g0 + (x >> s)) >> 1;

while(g1 < g0)

{

g0 = g1;

g1 = (g0 + x/g0) >> 1;

}

return g0;

}

算法3 逐比特确认法

逐比特确认法认为一个32位整数求根,结果应该是一个16位整数。求这个16位整数,其实质是确认每位的比特是0还是1.我们把这个根分为两个相加的部分,一部分是已确认的值,另一部分是未确认的值。从高位到低位,每次迭代确认一位。初始时,已确认部分为0。则问题的初始形式为:

算法发明者为:James Ulery  论文:Computing Integer Square Roots

下面给出源代码:

unsigned bitwise_verification(unsigned long x)

{

unsigned long temp = 0;

unsigned v_bit = 15;

unsigned n = 0;

unsigned b = 0x8000;

if (x <= 1)

return x;

do{

temp = ((n << 1) + b) << (v_bit--);

if (x >= temp)

{

n += b;

x -= temp;

}

}while (b >>= 1);

return n;

}

性能比较

在0~1000000范围内对四种算法进行了遍历性的测试,得到测试结果:

显见四种算法的遍历性能以逐比特确认法为最好,逐比特确认法从本质上来说是一种二分查找法,而且其查找范围为整个16位整数域;而我们实现的二分查找法的查找范围到已知变量为止,从范围上来说比逐比特确认法来得小,但是最后平均性能却不及逐比特确认法。其原因在于:逐比特确认法把问题分解为相同的子问题的集合,采用递推的方法,很好地利用了前面步骤的结果,不用什么都从头算起,从而避免了重复劳动,用加减法和移位操作代替了乘除法操作,最终获得了性能上的增益。

需要注意的是,虽然平均性能有如此的关系。并不代表每个数或每组数都有这样的关系。实际上,我们每组产生1000个随机数,并对每组的算法性能进行了测试,各个算法都有获得优胜的时候。至于具体是什么场合用什么算法,需要分析和经验的支撑。目前,我所能归纳出的概要指导准则为:

(1)在大多数情况下,牛顿迭代都能获得不错的性能,

(2)逐比特确认法更适合运算数比较大的场合。

平方根手写算法

此文算是为大整数开根做好理论准备

1>    曾经写过迭代求平方根的算法,很是简单。假设求x的平方根,那么可以假设两个非常逼近的数a[n]和a[n+1],它们的平方都非常接近x:

a[n]*a[n]≈a[n]*a[n+1]≈a[n+1]*a[n+1]≈x;   (1)

2a[n+1]*a[n]≈a[n]*a[n]+x;                           (2)

a[n+1]=0.5*(a[n]+x/a[n]);                            (3)

因此求x的平方根,只要有一个初始值,然后用式(3)无限迭代下去就可以就算出足够精度的开方值。迭代求平方根的算法对于浮点数64位范围内的数计算是比较准确且迅速的,C函数库math.h中的sqrt函数貌似就是使用这种算法计算平方根的。

上述迭代求平方根的算法能够应用到大整数的开根中呢。理论上是没有任何问题的,实际中也是有人就这么做的,并且貌似还很开心的AC了。但使用此算法求大整数开根的话,要实现大整数加法和除法等基本运算,想想还是放弃了。

2>  
 接下来介绍一种手动模拟求平方根算法。非常巧妙,非常神奇。可能自己智商有点问题哈,仔细琢磨了几个小时才彻底明白。先仔细介绍如何使用这种算法模拟求平方根,然后再简要说一说它的原理吧!

(a)首先将要开方根的数从小数点分别向右及向左每两个位一组分开,如98765.432内小数点前的65是一组,87是一组,9是一组,小数点后的43是一组,之后是单独的一个2,要补一个0而得20是一组。

(b)将最左一组的数减去最接近又不大于它的平方数,并将该平方数的开方(应该是个位数)记下。

(c) 将上一步所得之差乘以100,和下一组数加起来。

(d)将记下的数乘以20,然后将它加上某个个位数,再乘以这个个位数,令这个积不大于又最接近上一步所得之差,并将该个位数记下,且将上一步所得之差减去所得之积。

(e)记下的数依次隔两位记下。

(f)重复第3步,直到找到答案。

(g) 可以在数字的最右补上多组的00,以求得理想的精确度未止。

原理网上说是(a+b)^2 = a^2 + b^2 + 2ab = a^2 + (2a+b)*b。并没有完全看明白,这里就不班门弄斧了,等以后搞清楚,再补上吧!

/* 大整数开方 ,模拟手工开方*/
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <math.h>
# include <time.h>

# define MAXN 1001

void bigN_sqrt(char *s);
int  bigN_cmp(char *a, char *b, int lim);
void bigN_mul(char *a, int k, int lim);
void bigN_add(char *a, int k);
void bigNN_minus(char *a, char *b, int lim);
int  Newtonsqrt(double x);  //牛顿迭代可求求64位数的平方根
char str[MAXN];

int main()
{
      freopen("hugeint.in", "r", stdin);
      freopen("hugeint.out", "w", stdout);

    while (~scanf("%s", str))
        bigN_sqrt(str);

 //    printf("time cost %.3lfs.\n", (double)clock()/CLOCKS_PER_SEC);
    return 0;
}

int bigN_cmp(char *a, char *b, int lim)
{
    int i;
    for (i = lim-1; i >= 0; --i)
        if (a[i] < b[i]) return 1;
        else if (a[i] > b[i]) return -1;
    return 0;
}
void bigN_mul(char *a, int k, int lim)
{
    int i, tmp, c;
    for (c=i=0; i < lim; ++i) {
        tmp = a[i]*k + c;
        c = tmp / 10;
        a[i] = tmp - 10*c;
    }
}
void bigN_add(char *a, int k)
{
    int i = 0;
    while (k > 0) {
        a[i++] += k%10;
        k /= 10;
    }
}
void bigNN_minus(char *a, char *b, int lim) // b = b - a;
{
    int i, tmp, c;
    for (c=i=0; i < lim; ++i) {
        tmp = b[i] - a[i] + c;
        c = (tmp<0 ? -1:0);
        b[i] = (tmp+10) % 10;
    }
}

int Newtonsqrt(double x)
{
	double x1 = 1;//初值
    double x2 = x1/2.0+x/2.0/x1;
    while( fabs(x2-x1)>0.1)
    {
        x1 = x2;
        x2 = x1/2.0+x/2.0/x1;
    }
    return floor(x1);
}
void bigN_sqrt(char *s)
{
    short int i, k, slen;           // 根的一个十进制位
    char res[MAXN];                 // 试方余数
    char cur[MAXN];                 // 试方上限
    char tmp[MAXN];
    int lim;

    memset(res, 0, sizeof(res));
    memset(cur, 0, sizeof(cur));

    lim = slen = strlen(s);
    if (slen < 18) {
	//非大整数,直接调用sqrt()计算平方根,结束 。sqrt()计算平方根并非完全正确,在测试的过程中
	// sqrt(8456552264) =  91960 ,而实际上 8456552264的平方根是91959 ,因此采用Newton迭代法求解
    //    printf("%.0lf\n", sqrt(atof(s)));
    //    printf("%.0lf\n", sqrt(8456552264));
        double value=atof(s);
        printf("%d",Newtonsqrt(value));
        return ;
    }
    if (slen & 0x1) {
        k = -1;
        cur[0] = s[0] - 48;
    } else {
        k = 0;
        cur[1] = s[0] - 48;
        cur[0] = s[1] - 48;
    }
    while (1) {
        i = 0;
        while (1) {
            ++i;
            memcpy(tmp, res, MAXN);
            bigN_mul(tmp, i*20, lim);
            bigN_add(tmp, i*i);
            if (-1 == bigN_cmp(tmp, cur, lim)) break;     // break until tmp > cur;
        }
        --i;
        printf("%d", i);

        memcpy(tmp, res, MAXN);     //cur -= res*i*20+i*i;
        bigN_mul(tmp, i*20, lim);
        bigN_add(tmp, i*i);
        bigNN_minus(tmp, cur, lim);

        bigN_mul(res, 10, lim);        // res = res*10+i;
        bigN_add(res, i);

        k += 2;
        if (k >= slen) break;
        else {
            bigN_mul(cur, 100, lim);
            bigN_add(cur, ((s[k]-48)*10+(s[k+1]-48)));
        }
    }
    printf("\n");
}
时间: 2024-10-14 02:46:34

整数平方根:整数开方及大整数开方解决方法的相关文章

用bootstrap兼容ie各大浏览器的解决方法

以bootstrap为框架常常会出现不兼容ie各大浏览器的问题,用以下代码基本可以解决,一般在<head></head>加入以下代码后,网页可能还一些比较不美观,再写一点css hack就可以了,如果加入以下代码网页还是特别乱,请检查一下你的css和js的文件顺序,有加载顺序的... <!--[if lte IE 6]> <link rel="stylesheet" type="text/css" href="st

用Java的大整数类Integer来实现大整数的一些运算

import java.io.*; import java.util.*; import java.math.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); BigInteger a, b; while(sc.hasNext()) { a = sc.nextBigInteger(); b = sc.nextBigInteger(); Syste

SQL Server 2008 R2占用内存越来越大两种解决方法

SQL Server 2008 R2运行越久,占用内存会越来越大. 第一种:有了上边的分析结果,解决方法就简单了,定期重启下SQL Server 2008 R2数据库服务即可,使用任务计划定期执行下边批处理: net stop sqlserveragentnet stop mssqlservernet start mssqlservernet start sqlserveragent 第二种:进入Sql server 企业管理器(管理数据库和表的,这个都不知道就不用往下看了),在数据库服务器名称

du与df区别以及出现较大差距时解决方法

在linux下查看磁盘使用率,最常用到的是df和du,但是有时就会发现du 和df出来的结果并不完全相同,有时候还有很大差距 1. 两个命令的解释 du  --disk usage df  -- disk free 2.du 和df 工作原理 2.1 du工作原理 du命令会对待统计的文件逐个调用fstat这个系统调用,来获取文件大小.它的数据是基于文件获取的,有很大的灵活性.不一定非 要针对一个分区,可以跨多个分区操作,如果针对的目录中文件的个数较多,du的速度就会很慢了. 2.2 df 工作

git提交大文件的解决方法

不知是 GitHub 在国内又被封了还是因为本文的原因,我个人是在执行了下述操作后成功提交了. 以作记录,内容如下: Compressing objects: 100% (163/163), done. error: RPC failed; result=52, HTTP code = 0 fatal: The remote end hung up unexpectedly Writing objects: 100% (365/365), 3.39 MiB | 0 bytes/s, done.

win2003/win7 iis7与IIS6对无法上传大文件的解决方法

Windows 2003 Server的解决办法 Windows 2003 Server下无法上传附件,无论是文章还是软件上传都会出现:Request 对象 错误 'ASP 0104 : 的错误,请按照下面的说明一步步操作: 更改win2003的IIS 6.0对asp教程的上传文件大小为200k限制,aspx的上传程序没有影响.在IIS6.0中,默认设置是特别严格和安全的,最大只能传送 204,800 个字节,这样可以最大限度地减少因以前太宽松的超时和限制而造成的攻击.IIS 6 出于安全考虑,

[转]ASP.NET MVC Json()处理大数据异常解决方法 json maxjsonlength

本文转自:http://blog.csdn.net/blacksource/article/details/18797055 先对项目做个简单介绍: 整个项目采用微软的ASP.NET MVC3进行开发,前端显示采用EasyUI框架,图表的显示用的是Highcharts,主要进行曲线图的绘制,这样比较形象地描绘出变化的趋势.由于数据量比较大(大于1000,000条记录),而highcharts接受的数据类型为json格式,所以controller从数据库中取出的数据需要先格式化成json,然后再传

第二大整数

第二大整数 问题描述 编写一个程序,读入一组整数(不超过20个),当用户输入0时,表示输入结束.然后程序将从这组整数中,把第二大的那个整数找出来,并把它打印出来.说明:(1)0表示输入结束,它本身并不计入这组整数中.(2)在这组整数中,既有正数,也可能有负数.(3)这组整数的个数不少于2个. 输入格式:输入只有一行,包括若干个整数,中间用空格隔开,最后一个整数为0. 输出格式:输出第二大的那个整数. 输入输出样例 样例输入 5 8 -12 7 0 样例输出 7 #include <stdio.h

斐波那契 [ Fibonacci] 数列之大整数求和

之前做到一题, 不过由于Honor Code的缘故就不说是啥了, 很多人都知道 (-_-) 大概是说有n个牌,每个牌只有A,B两种状态. 当出现连续3个牌的状态一样时,认为不完美. 给出一个[1, 10000]的整数, 让求出完美的排列个数 那么我们就可以分析一下: /*-------------------------------------------------------------------------------分析:    首先要求出不美观的个数,但是尝试可以发现美观的排列更容易