实验名称 | 生成圆周率并用KMP算法查找自己的生日 | ||||
实验内容 | 利用圆周率生成程序生成圆周率 在此基础上实现KMP算法查找自己生日 | ||||
实验目的 |
| ||||
实验步骤 | 1.确定输入输出 输入:生成圆周率后的位数;(位数越多时间变长,最好控制在1000000内) 输出:圆周率; 输入:生日(例:20020101); 输出:圆周率中是否有有生日; 匹配成功输出从圆周率小数点后第几位开始找到生日;匹配失败输出圆周率前几位找不到生日。 2.编写生成圆周率的程序 (1)根据求pi的一种方法,数学反三角函数幂次展开式的公式如下:
| ||||
实验步骤 | 将公式展开如下: (2)通过双向循环链表的存储结构计算a,即先算k* a(k-1)再算 k* a(k-1)/(2k+1),并将此数据放入链表p中,之后将p与sum两个链表所表示的数据相加,不断重复上述过程,计算次数越多越精确。我们选择了2000次进行计算。 while(s<2000) { son=(2*s-1)*(2*s-1); mother=8*s*(2*s+1); Multiply(p,son); Divide(p,mother); Sum(p,sum); s++; } (3)定义了一个结构体 list 来构建一个链表中的元素,由于 π 的小数部分的位保存到链表中,顺次向后连接。 typedef struct node { int data; struct node*next; struct node*pre; }node,*list; (4)公式中大数乘、除、加相关操作来实现求pi公式的计算: 其中因为展开式中各项运算的数据比较大,我们这里一定要采取大数运算,将计算的值和进位(借位)分别用不同变量表示。 大数加法: void Sum(list q,list sum)//加,从低位开始 { list a=q->pre,b=sum->pre;// a指向每一项的值(此时该项已经在之前计算出来了),b指向之前已经计算好的sum值,准备与新计算出来的项num相加 int tmp; while(b!=sum) { tmp=a->data+b->data;//存放当前位加上进位的值 b->data=tmp%10;//非进位部分,留在当前位上 b->pre->data+=tmp/10;//进位部分 a=a->pre;//指针前移一位 b=b->pre;//指针前移一位 } } 大数乘法: void Multiply(list q,int n)//乘,从低位即链表尾部开始计算 { list a=q->pre; int ret=0,tmp=0;//ret存当前项的计算值,tmp存进位 for(;a!=q;a=a->pre)//a=a->pre表示每循环一次指针前移一位 { tmp=(a->data)*n+ret;//存放当前位计算后的值,之后根据当前位的计算值计算进位tmp和a->data ret=tmp/10;//进位的部分,存在tmp里面,之后在进位时参与计算(计算ret) a->data=tmp%10;//进位的部分,留在当前位上 } tmp=(a->data)*n+ret; ret=tmp/10; a->data=tmp%10; } 大数除法: void Divide(list q,int n)//除,从高位开始计算 { list a=q->next; int tmp=0,ret=0;//tmp存计算的值,ret存余数 for(;a!=q;a=a->next)//循环一次,指针后移一次 { tmp=a->data+10*ret;//除法借位b需要乘以10,加上当前p->data,成为当前需要计算的值 a->data=tmp/n;//商可以直接留在当前位上 ret=tmp%n;//余数需要在下一位进行借位,即成为ret } }
int Index_KMP(string S, string T, int pos, int next[]) { // 利用模式串T的next函数求T在主串S中的第pos个 //字符之后的位置的KMP算法。其中,T非空, // 1≤ pos ≤ StrLength(S) int i = pos; int j = 1; while (i <= S.size() && j <= T.size()) { //0下标存储字符串长度 if (j == 0 || S[i - 1] == T[j - 1]) { ++i; ++j; } // 继续比较后继字符 else j = next[j]; // 模式串向右移动 } if (j > T.size()) return i-T.size(); // 匹配成功 else return 0; } // Index_KMP void get_next(string T, int next[]) { // 求模式串T的next函数值并存入数组next int i = 1; next[1] = 0; int j = 0; while (i < T.size()) { if (j == 0 || T[i - 1] == T[j - 1]) { ++i; ++j; next[i] = j; } else j = next[j]; } } // get_next
string int2str(int number)//int型转化为string型 { if (number == 0 ) return "0" ; string str = "" ; int number_ = number > 0 ? number : - 1 * number; while (number_) { str = (char)(number_ % 10 + 48 ) + str; number_ /= 10; } return str; }
输出圆周率小数点后100000位,在第6285位开始找到了字符串0926。 输出圆周率小数点后100000位,没有找到字符串20020926 | ||||
实验总结 | 通过本次实验的学习与完成,我了解了到多种高精度圆周率的求法,其中利用反函数展开式的求法是相对较为准确的一种,对于这种方法通过代码的书写,我加深了对求pi的展开式的理解,同时更熟练地掌握了链表和指针的相关内容,对于高精度求pi的相关方法也有了认识与理解;除此之外,本次实验也运用KMP算法找到自己的生日所在圆周率的位置,温习了之前学习的KMP算法,以及其中一个关键点next数组的求解,这是一个非常重要、应用广泛的匹配算法。总之,本次实验收获颇多,为之后的学习打下了基础。 |
参考代码:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;
string Si, Ti;
int k;
typedef struct node {
int data;
struct node*next;
struct node*pre;
}node,*list;
void init(list l)//初始化链表
{
list p=l,q;
int i;
p->next=p->pre=l;
for(i=0;i<k+1;i++)
{
q=(list)malloc(sizeof(node));
q->data=0;
p->next=q;
q->pre=p;
q->next=l;
l->pre=q;
p=q;
}
}
void destorylist(list l)//销毁链表
{
list p=l->next;
list q;
while (p!=l)
{
q = p->next;
free(p);
p = q;
} //将头结点后逐项释放
free(l); //最后释放头结点
l = NULL;
}
string int2str(int num)//int型转化为string型
{
if (num == 0 )
return "0" ;
string str = "" ;
int num_ = num > 0 ? num : - 1 * num;
while (num_)
{
str = (char)(num_ % 10 + 48 ) + str;
num_ /= 10;
}
return str;
}
void print_pi(list sum)
{
int i;
if(k==0)
printf("3\n");
else
{
sum=sum->next;
printf("%d.",sum->data);
for(i=0;i<k;i++)
{
printf("%d",sum->next->data);
Si+=int2str(sum->next->data);//将小数部分数字转化为字符串,便于之后的字符串匹配操作
sum=sum->next;
}
}
}
void Sum(list q,list sum)//加,从低位开始
{
list a=q->pre,b=sum->pre;// a指向每一项的值(此时该项已经在之前计算出来了),b指向之前已经计算好的sum值,准备与新计算出来的项num相加
int tmp;
while(b!=sum)
{
tmp=a->data+b->data;//存放当前位加上进位的值
b->data=tmp%10;//非进位部分,留在当前位上
b->pre->data+=tmp/10;//进位部分
a=a->pre;//指针前移一位
b=b->pre;//指针前移一位
}
}
void Multiply(list q,int n)//乘,从低位即链表尾部开始计算
{
list a=q->pre;
int ret=0,tmp=0;//ret存当前项的计算值,tmp存进位
for(;a!=q;a=a->pre)//a=a->pre表示每循环一次指针前移一位
{
tmp=(a->data)*n+ret;//存放当前位计算后的值,之后根据当前位的计算值计算进位tmp和a->data
ret=tmp/10;//进位的部分,存在tmp里面,之后在进位时参与计算(计算ret)
a->data=tmp%10;//进位的部分,留在当前位上
}
tmp=(a->data)*n+ret;
ret=tmp/10;
a->data=tmp%10;
}
void Divide(list q,int n)//除,从高位开始计算
{
list a=q->next;
int tmp=0,ret=0;//tmp存计算的值,ret存余数
for(;a!=q;a=a->next)//循环一次,指针后移一次
{
tmp=a->data+10*ret;//除法借位b需要乘以10,加上当前p->data,成为当前需要计算的值
a->data=tmp/n;//商可以直接留在当前位上
ret=tmp%n;//余数需要在下一位进行借位,即成为ret
}
}
int Index_KMP(string S, string T, int pos, int next[]) {
// 利用模式串T的next函数求T在主串S中的第pos个
//字符之后的位置的KMP算法。其中,T非空,
// 1≤pos≤StrLength(S)
int i = pos;
int j = 1;
while (i <= S.size() && j <= T.size()) { //0下标存储字符串长度
if (j == 0 || S[i - 1] == T[j - 1]) { ++i; ++j; } // 继续比较后继字符
else j = next[j]; // 模式串向右移动
}
if (j > T.size())
return i-T.size(); // 匹配成功
else return 0;
} // Index_KMP
void get_next(string T, int next[]) {
// 求模式串T的next函数值并存入数组next
int i = 1;
next[1] = 0;
int j = 0;
while (i < T.size()) {
if (j == 0 || T[i - 1] == T[j - 1])
{
++i;
++j;
next[i] = j;
}
else j = next[j];
}
} // get_next
int main () {
list p,sum;
int mother,son,s=1;
scanf("%d",&k);
p=(list)malloc(sizeof(node));
sum=(list)malloc(sizeof(node));
init(p);
init(sum);
p->next->data=3;
sum->next->data=3;
while(s<2000)
{
son=(2*s-1)*(2*s-1);
mother=8*s*(2*s+1);
Multiply(p,son);
Divide(p,mother);
Sum(p,sum);
s++;
}
print_pi(sum);
//cout<<endl<<Si<<endl;
int next[100];
cout << "\n请输入你的生日:";
cin >> Ti;
get_next(Ti, next);
if(Index_KMP(Si, Ti, 0, next)) {
cout << "匹配成功!"<<endl;
cout<<"从圆周率中小数点后第" << Index_KMP(Si, Ti, 0, next) << "位开始找到您的生日!" << endl;
}
else{
cout << "匹配失败!圆周率前"<<k<<"位中不存在您的生日!" << endl;
}
cout << endl;
destorylist(p);
destorylist(sum);
}