高精度计算PI值
题目描述
使用双向链表作为存储结构,请根据用户输入的一个整数(该整数表示精确到小数点后的位数,可能要求精确到小数点后 500 位),高精度计算PI值。提示:可以利用反三角函数幂级展开式来进行计算。
解题思路
求PI的算法
首先这道题是要求必须使用双向链表作为存储结构的,这个需要注意,而且也不能用数组计算完了之后挨个赋值给链表的每个节点,这是耍赖 。
那么我们开始再想,用什么公式来求 PI 呢?这是一个问题。先没管题目的提示,我去百度了一通,发现了一个很神奇的算法,用三行就可以计算到圆周率小数点后800+位。
#include<cstdio>
using namespace std;
long a=1000,b,c=2800,d,e,f[2801],g;
int main(){
for(;b-c;) f[b++]=a/5;
for(;d=0,g=c*2;c-=14,printf("%.3d",e+d/a),e=d%a)
for(b=c;d+=f[b]*a,f[b]=d%--g,d/=g--,--b;d*=b);
}
实验了一下发现居然真的是,而且效率还挺高的?看了一会实现的原理一直没看懂,作罢。
最后发现是找不到什么除了幂级展开还有啥高效率的算法了好像,还是考虑题目提示的 三角函数幂级展开。然后继续在网上搜索了一下,在学长的一个博客里发现了公式。 $$ f_{i} = \begin{cases} 1 & {i=1}\\ f_{i-1}\times \frac{i-1}{2\times i-1} & {i>1} \end{cases} $$
那么拿到了这个式子,我们就可以分析一下,怎么和链表结合起来做了。
链表的设计
双向链表,也就是每个节点有一个数据域,有前和后两个指针。我考虑到我们做加法和乘法是需要从后往前做,除法是需要从前往后做。因此需要双向遍历,我又添加了一个尾指针,记录当前链表的尾节点的地址,方便从后往前遍历,头节点可以保证从前往后遍历。设计如下:
typedef struct node
{
int data;
struct node *nxt;
struct node *pre;
struct node *tail;
node() //构造函数,用于初始化
{
nxt = NULL;
pre = NULL;
tail = this;
data = 0;
}
}
数据域就用来存储每一位数字,好比 3.1415926 就从第一个节点到第八个节点依次存 31415926 。
乘法的实现
另出一个函数,函数声明类似于 void Multi(List L,int k)
乘法我们模拟竖式的乘法运算,考虑到这是一个 高精度大数 * 低精度整数 ,因此我们只需要从尾部到头部依次对每一位做乘法即可,考虑到进位问题,可以有两种办法
- 可以是先做完乘法,然后再回到尾部,再从尾到头依次处理进位,这样的好处是这两种操作分隔开了,操作起来难度不大,也比较好想。
- 可以是边做乘法边进位,我们定义一个
temp
用来存储低位到高一位的进位,这里要注意的是,对于某一位的操作不是先加上进位再做乘法,是先做乘法,再加低位的进位。
但是有一个问题我们需要注意,就是好比 52 * 3 ,这时候原来的两位数变成三位数了,因此需要我们在头节点和第一个节点之间增加新的节点,并且可能增加的不只是一个,只要 temp
这个进位大于等于10就需要一直创建新的节点来保证进位。
除法的实现
另出一个函数,函数声明类似于 void Division(List L,int k)
除法也是模拟竖式运算,这个是从头到尾进行处理,这一位的数据做完除法,余数作为下一位的**“进位”** ,这里可以边做边 “进位” 。这里这个进位不是乘法的那种进位,注意区分。
需要注意的是最后可能有除不尽的情况,这样我们就可以一直在尾部插入节点,然后处理一直处理数据到最大位数 或者 到 “进位” 为0为止 。最后不要忘记重置一下尾指针,指向新的尾节点。
加法的实现
函数声明类似于 void Sum(List L,List p)
。
加法的实现也是需要从后往前遍历,然后依次对每一位做加法。先取两个链表的尾节点出来,然后依次向前遍历相加,我们可以把相加的答案放在前面那个链表里面,注意这样是不需要返回值的,因为链表内部是通过地址索引的,我们改变的就是传入链表的值。
最后的整合
最后我们总共需要一个和链表,一个 $f_{i}$。 和链表用于计算所有式子的累加和,而 $f_{i-1}$ 其实就是 $f_i$ 的上个阶段 。我们每次把 $f_i$ 和 和链表进行累加即可。
最后再对和链表乘2 即可。
代码实现
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxsize = 600; // 设定求小数点后多少位
typedef struct node // 双向链表的结构体
{
int data;
struct node *nxt;
struct node *pre;
struct node *tail;
node() //构造函数,用于初始化
{
data = 0;
tail = this;
nxt = NULL;
pre = NULL;
}
}Node,*List;
void Mult(List Head,int k); // 对链表每一位 *k
void Divi(List Head,int k); // 对链表每一位 ÷k
void Sum(List a,List b); // 对两个链表的数求和,所得数放在前面链表中
void InitList(List &L); // 初始化链表,并把第一个节点值设为 1
void InitSum(List &L); // 初始化最后存和的链表,并把第一个节点置为1
void Output(List L,int k); // 输出一个链表,保留 k 位小数
int main()
{
int n = 0;
scanf("%d",&n);
List Head,S;
InitList(Head);
InitSum(S);
for(int i=2;i<=3000;i++) //计算pi值
{
Mult(Head,i-1);
Divi(Head,2*i-1);
Sum(S,Head);
}
Mult(S,2);
Output(S,n);
return 0;
}
void Mult(List Head,int k) // 对以Head为头节点的链表中的每一位做乘法
{
List p = Head->tail; // 先把指针指向链表的末尾,方便从后往前做乘法
while(p != Head) // 从前往后开始算乘法
{
p->data *= k;
p = p->pre;
// printf("I have done Multi.\n");
}
p = Head->tail;
while(p != Head->nxt) //开始处理进位
{
p->pre->data += p->data /10;
p->data %= 10;
p = p->pre;
}
while( p->data > 10) //一直处理最高位的进位
{
List s = (List)malloc(sizeof(Node));
s->data = p->data/10;
p->data %= 10;
s->pre = Head;
s->nxt = p;
p->pre = s;
Head->nxt = s;
p = s;
}
}
void Divi(List Head,int k) // 对链表每一位除k
{
int temp = 0,depth = 0; //temp用于进位计算 ,depth 用于计算链表长度
List p = Head->nxt;
List t; // 存尾部节点
while(p != NULL) //模拟做除法
{
depth++;
p->data += temp*10;
temp = p->data % k;
p->data /= k;
t = p;
p = p->nxt;
}
p = t;
while(temp!=0 && depth <= maxsize) // 如果除不尽,就一直往后拓展节点,但注意不要超过最大位数
{
// printf("I have done Division!\n");
depth++;
List s = (List)malloc(sizeof(Node));
s->data = temp*10;
s->nxt = NULL;
s->pre = p;
temp = s->data % k;
s->data /= k;
p->nxt = s;
p = s;
}
Head->tail = p;
}
void Sum(List a,List b) // 对两个链表的数求和,所得数放在前面链表中
{
List p = a->tail,k = b->tail; // 先指向各自的尾部,开始从前往后加
while(p!=a && k!=b) // 遍历到有一个到头节点为止
{
p->data += k->data;
p->pre->data += p->data / 10;
p->data %= 10;
p = p->pre;
k = k->pre;
}
}
void InitList(List &L) // 初始化链表,并把第一个节点值设为 1
{
L = (List)malloc(sizeof(Node));
L->data = 0;
List s = (List)malloc(sizeof(Node));
s->data = 1;
s->pre = L;
L->nxt = s;
L->pre = NULL;
L->tail = s;
s->nxt = NULL;
}
void InitSum(List &L) // 初始化最后存和的链表,并把第一个节点置为1
{
L = (List)malloc(sizeof(Node));
L->nxt = NULL;
L->pre = NULL;
L->data = 0;
List p = L;
int depth = 0;
while(depth <= maxsize)
{
List s = (List)malloc(sizeof(Node));
s->data = 0;
s->pre = p;
p->nxt = s;
s->nxt = NULL;
p = s;
depth++;
}
L->nxt->data = 1;
L->tail = p;
}
void Output(List L,int k) //输出一个列表,保留 k 位小数
{
List p = L->nxt;
printf("%d.",p->data);
p = p->nxt;
int t = 0;
while(p != NULL && t<k)
{
t++;
printf("%d",p->data);
p = p->nxt;
}
printf("\n");
}