经典算法-3

本文介绍了C语言中处理超长整数的运算方法,包括加减乘除函数,以及如何计算长PI、最大公因数、最小公倍数、因式分解和完美数。此外,还探讨了阿姆斯壮数的寻找算法。这些内容展示了在C语言中实现数学问题的高效解决方案。
摘要由CSDN通过智能技术生成

其他经典例题跳转链接

C语言经典算法-1
1.汉若塔 2. 费式数列 3. 巴斯卡三角形 4. 三色棋 5. 老鼠走迷官(一)6. 老鼠走迷官(二)7. 骑士走棋盘8. 八皇后9. 八枚银币10. 生命游戏

C语言经典算法-2
字串核对、双色、三色河内塔、背包问题(Knapsack Problem)、蒙地卡罗法求 PI、Eratosthenes筛选求质数

C语言经典算法-3
超长整数运算(大数运算)、长 PI、最大公因数、最小公倍数、因式分解、完美数、阿姆斯壮数

C语言经典算法-4
最大访客数、中序式转后序式(前序式)、后序式的运算、洗扑克牌(乱数排列)、Craps赌博游戏

C语言经典算法-5
约瑟夫问题(Josephus Problem)、排列组合、格雷码(Gray Code)、产生可能的集合、m元素集合的n个元素子集

C语言经典算法-6
数字拆解、得分排行,选择、插入、气泡排序、Shell 排序法 - 改良的插入排序、Shaker 排序法 - 改良的气泡排序

C语言经典算法-7
排序法 - 改良的选择排序、快速排序法(一)、快速排序法(二)、快速排序法(三)、合并排序法

C语言经典算法-8
基数排序法、循序搜寻法(使用卫兵)、二分搜寻法(搜寻原则的代表)、插补搜寻法、费氏搜寻法

C语言经典算法-9
稀疏矩阵、多维矩阵转一维矩阵、上三角、下三角、对称矩阵、奇数魔方阵、4N 魔方阵、2(2N+1) 魔方阵

16.超长整数运算(大数运算)

说明基于记忆体的有效运用,程式语言中规定了各种不同的资料型态,也因此变数所可以表达的最大整数受到限制,例如123456789123456789这样的 整数就不可能储存在long变数中(例如C/C++等),我们称这为long数,这边翻为超长整数(避免与资料型态的长整数翻译混淆),或俗称大数运算。
解法一个变数无法表示超长整数,则就使用多个变数,当然这使用阵列最为方便,假设程式语言的最大资料型态可以储存至65535的数好了,为了计算方便及符合使用十进位制的习惯,让每一个阵列元素可以储存四个位数,也就是0到9999的数,例如:
在这里插入图片描述

很多人问到如何计算像50!这样的问题,解法就是使用程式中的乘法函式,至于要算到多大,就看需求了。

由于使用阵列来储存数值,关于数值在运算时的加减乘除等各种运算、位数的进位或借位就必须自行定义,加、减、乘都是由低位数开始运算,而除法则是由高位数开始运算,这边直接提供加减乘除运算的函式供作参考,以下的N为阵列长度。

void add(int *a, int *b, int *c) { 
    int i, carry = 0; 

    for(i = N - 1; i >= 0; i--) { 
        c[i] = a[i] + b[i] + carry; 
        if(c[i] < 10000) 
            carry = 0; 
        else { // 进位 
            c[i] = c[i] - 10000; 
            carry = 1; 
        } 
    } 
} 

void sub(int *a, int *b, int *c) { 
    int i, borrow = 0; 
    for(i = N - 1; i >= 0; i--) { 
        c[i] = a[i] - b[i] - borrow; 
        if(c[i] >= 0) 
            borrow = 0; 
        else { // 借位 
            c[i] = c[i] + 10000; 
            borrow = 1; 
        } 
    } 
} 

void mul(int *a, int b, int *c) { // b 为乘数 
    int i, tmp, carry = 0; 
    for(i = N - 1; i >=0; i--) { 
        tmp = a[i] * b + carry; 
        c[i] = tmp % 10000;    
        carry = tmp / 10000; 
    } 
} 

void div(int *a, int b, int *c) {  // b 为除数 
    int i, tmp, remain = 0; 
    for(i = 0; i < N; i++) { 
        tmp = a[i] + remain; 
        c[i] = tmp / b; 
        remain = (tmp % b) * 10000; 
    } 
} 

17.长 PI

说明圆周率后的小数位数是无止境的,如何使用电脑来计算这无止境的小数是一些数学家与程式设计师所感兴趣的,在这边介绍一个公式配合 大数运算,可以计算指定位数的圆周率。
解法首先介绍J.Marchin的圆周率公式:

PI = [16/5 - 16 / (353) + 16 / (555) - 16 / (757) + …] -
[4/239 - 4/(3
2393) + 4/(52395) - 4/(72397) + …]

可以将这个公式整理为: PI = [16/5 - 4/239] - [16/(53) - 4/(2393)]/3+ [16/(55) -
4/(2395)]/5 + …

也就是说第n项,若为奇数则为正数,为偶数则为负数,而项数表示方式为: [16/52n-1 - 4/2392n-1] / (2*n-1)

如果我们要计算圆周率至10的负L次方,由于[16/52n-1 -
4/2392
n-1]中16/52n-1比4/2392n-1来的大,具有决定性,所以表示至少必须计算至第n项: [16/52n-1 ]
/ (2
n-1) = 10-L

将上面的等式取log并经过化简,我们可以求得: n = L / (2log5) = L / 1.39794

所以若要求精确度至小数后L位数,则只要求至公式的第n项,其中n等于: n = [L/1.39794] + 1

在上式中[]为高斯符号,也就是取至整数(不大于L/1.39794的整数);为了计简方便,可以在程式中使用下面这个公式来计简第n项:
[Wn-1/52- Vn-1 / (2392)] / (2*n-1)

这个公式的演算法配合大数运算函式的演算法为: div(w, 25, w); div(v, 239, v); div(v, 239,
v); sub(w, v, q); div(q, 2*k-1, q)

至于大数运算的演算法,请参考之前的文章,必须注意的是在输出时,由于是输出阵列中的整数值,如果阵列中整数位数不满四位,则必须补上0,在C语言中只要 使用格式指定字%04d,使得不足位数部份自动补上0再输出,至于Java的部份,使用 NumberFormat来作格式化。

#include <stdio.h> 
#define L 1000 
#define N L/4+1 

// L 为位数,N是array长度 

void add(int*, int*, int*); 
void sub(int*, int*, int*); 
void div(int*, int, int*); 

int main(void) { 
    int s[N+3] = {0}; 
    int w[N+3] = {0}; 
    int v[N+3] = {0}; 
    int q[N+3] = {0}; 
    int n = (int)(L/1.39793 + 1); 
    int k; 

    w[0] = 16*5; 
    v[0] = 4*239; 

    for(k = 1; k <= n; k++) { 
        // 套用公式 
        div(w, 25, w); 
        div(v, 239, v); 
        div(v, 239, v); 
        sub(w, v, q); 
        div(q, 2*k-1, q); 

        if(k%2) // 奇数项 
            add(s, q, s); 
        else    // 偶数项 
            sub(s, q, s); 
    } 

    printf("%d.", s[0]); 
    for(k = 1; k < N; k++) 
        printf("%04d", s[k]); 
    printf("\n"); 
    return 0; 
} 

void add(int *a, int *b, int *c) { 
    int i, carry = 0; 

    for(i = N+1; i >= 0; i--) { 
        c[i] = a[i] + b[i] + carry; 
        if(c[i] < 10000) 
            carry = 0; 
        else { // 进位 
            c[i] = c[i] - 10000; 
            carry = 1; 
        } 
    } 
} 

void sub(int *a, int *b, int *c) { 
    int i, borrow = 0; 
    for(i = N+1; i >= 0; i--) { 
        c[i] = a[i] - b[i] - borrow; 
        if(c[i] >= 0) 
            borrow = 0; 
        else { // 借位 
            c[i] = c[i] + 10000; 
            borrow = 1; 
        } 
    } 
} 

void div(int *a, int b, int *c) {  // b 为除数 
    int i, tmp, remain = 0; 
    for(i = 0; i <= N+1; i++) { 
        tmp = a[i] + remain; 
        c[i] = tmp / b; 
        remain = (tmp % b) * 10000; 
    } 
} 

18.最大公因数、最小公倍数、因式分解

说明最大公因数使用辗转相除法来求,最小公倍数则由这个公式来求:

GCD * LCM = 两数乘积

解法最大公因数可以使用递回与非递回求解,因式分解基本上就是使用小于输入数的数值当作除数,去除以输入数值,如果可以整除就视为因数,要比较快的解法就是求出小于该数的所有质数,并试试看是不是可以整除,求质数的问题是另一个课题,请参考 Eratosthenes 筛选求质数。

实作(最大公因数、最小公倍数)
#include <stdio.h> 
#include <stdlib.h> 

int main(void) { 
    int m, n, r; 
    int s;

    printf("输入两数:"); 
    scanf("%d %d", &m, &n); 
    s = m * n;

    while(n != 0) { 
        r = m % n; 
        m = n; 
        n = r; 
    } 

    printf("GCD:%d\n", m); 
    printf("LCM:%d\n", s/m); 

    return 0; 
} 
实作(因式分解)
 
C(不用质数表) 
#include <stdio.h> 
#include <stdlib.h> 

int main(void) { 
    int i, n; 

    printf("请输入整数:"); 
    scanf("%d", &n); 
    printf("%d = ", n); 

    for(i = 2; i * i <= n;) { 
        if(n % i == 0) { 
            printf("%d * ", i); 
            n /= i; 
        } 
        else 
            i++; 
    } 

    printf("%d\n", n); 

    return 0; 
} 
C(使用质数表) 
#include <stdio.h> 
#include <stdlib.h> 

#define N 1000 

int prime(int*);  // 求质数表 
void factor(int*, int);  // 求factor 

int main(void) { 
    int ptable[N+1] = {0}; 
    int count, i, temp; 

    count = prime(ptable); 

    printf("请输入一数:"); 
    scanf("%d", &temp); 
    factor(ptable, temp); 
    printf("\n"); 
    return 0; 
 } 

 int prime(int* pNum) { 
    int i, j; 
    int prime[N+1]; 
    for(i = 2; i <= N; i++) 
        prime[i] = 1; 

    for(i = 2; i*i <= N; i++) { 
        if(prime[i] == 1) { 
            for(j = 2*i; j <= N; j++) { 
                if(j % i == 0) 
                    prime[j] = 0; 
            } 
        } 
    } 

    for(i = 2, j = 0; i < N; i++) { 
        if(prime[i] == 1) 
            pNum[j++] = i; 
    } 
    return j; 
} 

void factor(int* table, int num) { 
    int i; 

    for(i = 0; table[i] * table[i] <= num;) { 
        if(num % table[i] == 0) { 
            printf("%d * ", table[i]); 
            num /= table[i]; 
        } 
        else 
            i++; 
    } 
    printf("%d\n", num); 
}

19.完美数

说明如果有一数n,其真因数(Proper factor)的总和等于n,则称之为完美数(Perfect Number),例如以下几个数都是完美数:

6 = 1 + 2 + 3
28 = 1 + 2 + 4 + 7 + 14
496 = 1 + 2 + 4 + 8 + 16 + 31 +62 + 124 + 248

程式基本上不难,第一眼看到时会想到使用回圈求出所有真因数,再进一步求因数和,不过若n值很大,则此法会花费许多时间在回圈测试上,十分没有效率,例如求小于10000的所有完美数。
解法如何求小于10000的所有完美数?并将程式写的有效率?基本上有三个步骤:

  1. 求出一定数目的质数表
  2. 利用质数表求指定数的因式分解
  3. 利用因式分解求所有真因数和,并检查是否为完美数

步骤一 与 步骤二 在之前讨论过了,问题在步骤三,如何求真因数和?方法很简单,要先知道将所有真因数和加上该数本身,会等于该数的两倍,例如:
2 * 28 = 1 + 2 + 4 + 7 + 14 + 28

等式后面可以化为:
2 * 28 = (20 + 21 + 22) * (70 + 71)

所以只要求出因式分解,就可以利用回圈求得等式后面的值,将该值除以2就是真因数和了;等式后面第一眼看时可能想到使用等比级数公式来解,不过会使用到次方运算,可以在回圈走访因式分解阵列时,同时计算出等式后面的值,这在下面的实作中可以看到。

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

#define N 1000 
#define P 10000 

int prime(int*);  // 求质数表 
int factor(int*, int, int*);  // 求factor 
int fsum(int*, int);  // sum ot proper factor 

int main(void) { 
    int ptable[N+1] = {0}; // 储存质数表 
    int fact[N+1] = {0};   // 储存因式分解结果 
    int count1, count2, i; 

    count1 = prime(ptable); 

    for(i = 0; i <= P; i++) { 
        count2 = factor(ptable, i, fact); 
        if(i == fsum(fact, count2)) 
            printf("Perfect Number: %d\n", i); 
    } 
    
    printf("\n"); 

    return 0; 
} 

int prime(int* pNum) { 
    int i, j; 
    int prime[N+1]; 

    for(i = 2; i <= N; i++) 
        prime[i] = 1; 

    for(i = 2; i*i <= N; i++) { 
        if(prime[i] == 1) { 
            for(j = 2*i; j <= N; j++) { 
                if(j % i == 0) 
                    prime[j] = 0; 
            } 
        } 
    } 

    for(i = 2, j = 0; i < N; i++) { 
        if(prime[i] == 1) 
            pNum[j++] = i; 
    } 

    return j; 
} 

int factor(int* table, int num, int* frecord) { 
    int i, k; 

    for(i = 0, k = 0; table[i] * table[i] <= num;) { 
        if(num % table[i] == 0) { 
            frecord[k] = table[i]; 
            k++; 
            num /= table[i]; 
        } 
        else 
            i++; 
    } 

    frecord[k] = num; 

    return k+1; 
} 

int fsum(int* farr, int c) { 
    int i, r, s, q; 

    i = 0; 
    r = 1; 
    s = 1; 
    q = 1; 

    while(i < c) { 
        do { 
            r *= farr[i]; 
            q += r; 
            i++; 
        } while(i < c-1 && farr[i-1] == farr[i]); 
        s *= q; 
        r = 1; 
        q = 1; 
    } 

    return s / 2; 
} 

20.阿姆斯壮数

说明
在三位的整数中,例如153可以满足13 + 53 + 33 = 153,这样的数称之为Armstrong数,试写出一程式找出所有的三位数Armstrong数。
解法
Armstrong数的寻找,其实就是在问如何将一个数字分解为个位数、十位数、百位数…,这只要使用除法与余数运算就可以了,例如输入 input为abc,则:

a = input / 100
b = (input%100) / 10
c = input % 10

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

int main(void) { 
    int a, b, c; 
    int input; 

    printf("寻找Armstrong数:\n"); 

    for(input = 100; input <= 999; input++) { 
        a = input / 100; 
        b = (input % 100) / 10; 
        c = input % 10; 
        if(a*a*a + b*b*b + c*c*c == input) 
            printf("%d ", input); 
    } 

    printf("\n"); 

    return 0; 
} 

系类好文
上一篇:
C语言经典算法-2
字串核对、双色、三色河内塔、背包问题(Knapsack Problem)、蒙地卡罗法求 PI、Eratosthenes筛选求质数
下一篇:
C语言经典算法-4
最大访客数、中序式转后序式(前序式)、后序式的运算、洗扑克牌(乱数排列)、Craps赌博游戏

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Token_w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值