【数据结构与算法】生成圆周率并用KMP算法查找自己的生日

实验名称

生成圆周率并用KMP算法查找自己的生日

实验内容

利用圆周率生成程序生成圆周率

在此基础上实现KMP算法查找自己生日

实验目的

  1. 掌握生成圆周率的方法
  2. 理解并熟练掌握KMP算法及其应用
  3. 在圆周率中找到自己的生日

实验步骤

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

        }

}

  1. 书写KMP算法和next数组进行字符串的匹配,用来在圆周率中找到自己的生日。(同前一次作业)

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

  1. 编写int型转化为string型的函数,将圆周率小数点后的int型数字转换为string型,用于后面字符串的匹配。

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;

}

  1. 案例测试

输出圆周率小数点后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);
}

  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
生成圆周率的常用算法有很多,其中较为简单的是莱布尼茨公式(Leibniz formula): π / 4 = 1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + …… 根据该公式,我们可以不断累加前面的项来逼近圆周率。具体实现如下: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_DIGITS 1000000 // 圆周率的最大位数 int main() { int i, j, k; int sign = 1; // 符号位 int digit = 0; // 当前计算到的圆周率位数 char pi[MAX_DIGITS + 1] = "3.14"; // 初始化为 3.14 char tmp[MAX_DIGITS + 1]; // 临时存放当前项的结果 // 计算圆周率 for (i = 3; digit < MAX_DIGITS; i += 2) { sprintf(tmp, "%.2f", 4.0 / i * sign); // 计算当前项并转换为字符串 sign = -sign; // 更新符号位 for (j = 0; tmp[j] != '.'; j++) // 将当前项的整数部分加入 pi 中 { if (digit < MAX_DIGITS) { pi[digit++] = tmp[j]; } } if (digit < MAX_DIGITS) { pi[digit++] = tmp[j++]; // 将小数点加入 pi 中 for (k = 0; k < 2 && tmp[j + k] != '\0'; k++) // 将当前项的小数部分加入 pi 中 { pi[digit++] = tmp[j + k]; } } } pi[digit] = '\0'; // 将 pi 结尾 printf("圆周率的前 %d 位为:\n", MAX_DIGITS); printf("%s\n", pi); return 0; } ``` 在程序中,我们使用 sprintf 函数将计算出的当前项转换为字符串,然后将其整数部分加入 pi 中,最后加入小数点和小数部分。由于圆周率的位数可能非常大,我们需要使用一个字符数组来存储。在本程序中,我们假设圆周率的位数不超过 100 万位。 需要注意的是,由于计算机的浮点数精度限制,使用莱布尼茨公式计算圆周率的位数可能会受到一定的影响。如果需要计算更高精度的圆周率,可以使用其他算法,如马青公式(Machin formula)、高精度计算等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值