编程算法中【各种数】求法汇总,你知道哪些数?(C/Python/Java三语言版)

#

  • N多天前:今天写Python作业,又遇到了一个没听过的【完全数】,这次索性将所有常见的各种“数”做个总结,全文100%完全手搓,给个小赞赞吧家人们!
  • N多天后:好好好,没想到这几个数因为一系列原因写了好久好久,今天终于完结了!看我肝了好几周的份上,拿出王子公主的小手双击一下吧。

#

  • 所有代码都是经过反复调试的,如果内容出现问题,或者大佬你有更妙的解决思路,请务必出现在评论区!
  • 关于内容:每个数都有概念以及对应的三语言版本的代码,语言功底还有待加强,有的地方也为了方便初学者写的比较稚嫩,望见谅

1、素数(质数)

  • ·定义

素数指的是:除了“1”和该数字本身外,没有其它的因数,即只能被1和自身整除的数。

也就是质数

  • 处理思路

2开始,遍历判断从 2 到 a 有没有a的因子。

但若一个数 n 不是素数,则一定存在一个小于等于n的算术平方根的数是它的因子,故只需从2遍历到 sqrt(n),而不是从 2 到 n

  • 代码实现

C语言:
#include<math.h>
bool is_prime(int a) {

	if(a<2) //排除0和1
        return false;
    else if(a==2)
        return true;
    else if(a%2==0) //除2外的偶数必然不是素数,单拎出来处理更高效!
        return false;
        	
    int b = (int)(sqrt(a));		//如果一个数不是素数,那一定存在 小于等于 sqrt(a) 的因子
	for (int i = 2; i <= b; i++) {   //故只需判断 2 到 a^0.5 这些数中是否有 a 的因数
		if (a % b == 0)
			return false; //不是素数返回false
	return true;          //是素数返回true
}
 Python:
def is_prime(num):
    if(num<2):  #排除1和2
        return False
    elif(num==2):
        return True
    elif(num%2==0): #除2外的偶数必然不是素数,单拎出来处理更高效!
        return False

    for i in range(2,num**0.+1):
        if(num % i==0):
            return False
    return True
JAVA:
import java.lang.Math;
public class num{

    public static boolean is_prime(int num){
        if(num<2){
            return false;
        }
        else if(num==2){
            return true;
        }
        else if(num%2==0){
            return false;
        }
        int b =(int)Math.sqrt(num)+1; 
        for(int i=2;i<=b;i++){
            if(num % b==0)
                return false;
        }
        return true;
    }

    public static void main(String[] args){
        System.out.println(is_prime(13));
    }
}

2、回文数

  • 定义

回文数是指正序(从左向右)和逆序(从右向左)读都是一样的整数。换句话说,这是一个数字,它从左往右和从右往左读是一样的,比如121,555,2442等。

注意:最小回文数为0,1~9也都算回文数

  • 处理思路

1、字符串反转

将数字转换为字符串,然后进行字符串翻转操作(python和java中可通过直接访问字符串下标,双指针利用回文数性质判断),最后equals比较两个字符串是否相等。

2、数字逆序比较

通过 位拆分 获取颠倒顺序后的数,然后比较两个数的值是否相等;|| 也可在拆分后利用回文数性质比较对应位上的数值是否相等

  • 代码实现

1、字符串反转
C语言
//主函数中的要判断的数要先转换为String类型
#include <stdio.h>
#include <string.h>

int main() {
	int num = 12321;
	char str[5]; // 大小自己决定
	sprintf_s(str, "%d", num); //将 num转化为字符串类型存入str中
	
	return 0;
}

//完全没必要反写字符串,如下直接首尾开始比较即可
bool is_palindrome(char* str) {
	int length = strlen(str);

	for (int i = 0,j=length-1; i<j; i++,j--) { //双指针由两头向中间行进
		if (str[i] != str[j]) {
			return false;
		}
	}
	return true;
}


反转字符串(此题完全不需要,这里反写只为个人复习)
#include <stdio.h>
#include <string.h>

void  reverse_str(char* str) {
	char temp = '\0';
	int strlength = strlen(str);

	for (int i = 0,j=strlength-1; i < j; i++,j--) {  //实现字符串的翻转
		temp = str[i];
		str[i] = str[j];
		str[j] = temp;
	}
}
//此函数在调用后,若在主函数中还要将其翻转结果由 字符串 转换为 int ,主函数中可用 sscanf_s()函数

void main() {
		int num1 = 12321;
		int num_reverse = 0;
		char str0[5];
		sprintf_s(str0, "%d", num1);//将int型 转换为 字符串

		reverse_str(str0); //调用反写函数实现字符串反写

		sscanf_s(str0, "%d", &num_reverse);//将字符串转回 int 型

		if (num1 == num_reverse) {//判断是否为回文数
			printf("此数是回文数");
		}
		else {
			printf("此数不是回文数");
		}
	}
  
Python:
#写法一:转为字符串,双指针比较
def is_palindrome(str0):
    length = len(str0)
    i,j = 0,0
    for i,j in zip(range(length),range(length-1,-1,-1)): 
        #zip可将两个可迭代对象整合成一个,
        #这里处理等价于 for(int i=0,int j=length-1; i<j; i++,j--)
        if(str0[i]!=str0[j]):
            return False
    return True
    


#写法二:反写字符串,字符串转整型,最后数字比较

def str_reverse(str0): #写一个反写字符串的函数
    return str0[0:len(str0):-1]  
    #str0[切片开始下标 : 切片结束下标+1 : 步长],
    # 步长为-1可实现反写,其实这句可写成str0[::-1],缺省值就是 0 和 len(str0)

def is_palindrome(num):
    str0 = str(num)
    reverse_num = int(str_reverse(str0)) #反写字符串,然后强转为int
    if(reverse_num==num):
        return True
    else:
        return False

JAVA:
//写法一:不用反转字符串,双指针行进判断
public class num{    
    public static boolean is_palindrome(Integer num){
        String str0 = num.toString(); //将数字转换为字符串
        for(int i=0,j=str0.length()-1;(i!=j)&&(i+1!=j) ;i++,j--){
            //双指针首尾同时行进比较,&&后的条件是排除串长度为奇数时的情况
            if(str0.charAt(i)!=str0.charAt(j)){
                return false;
            }
        }
        return true;
    }
    public static void main(String[] args){
        System.out.println(is_palindrome(12321));
    }
}


//写法二:直接用可变字符串类反写字符串,然后equals方法判断
public class num{

    public static boolean is_palindrome(Integer num){
        String str0 = num.toString(); //将数字转换为字符串
        StringBuilder restr = new StringBuilder(str0).reverse(); //直接调用SringBuilder反写方法实现翻转
        if(restr.toString().equals(str0)){
            return true;
        }
        return false;
    }
    public static void main(String[] args){
        System.out.println(is_palindrome(12321));
    }
}
2、数字逆序比较
C语言
//写法一:反写数,两数比较

#include <stdio.h>

bool is_palindrome(int num){
    int temp = num;
    int remain=0;
    int reverse_num=0;
    while(temp>0){ //不用确定数的位数就能将其反写
        remain = temp%10; //取到当数的个位
        temp /=10;         //舍弃个位,准备好下一趟循环
        reverse_num =reverse_num*10 + remain; 
    }
    if(num != reverse_num){
        return false;
    }
    return true;
}

//写法二:回文数性质,位数拆分,对应位与位比较

bool is_palindrome(int num){

    int flag=1;  //统计最高位数
    int temp = num;

    while(temp/flag>=10){ //直到flag最高位与num最高位相同为止
        flag*=10;
    }
    int left=0,right=0;
    while(temp>0){
        left = temp/flag;
        right=temp%10;
        if(left!=right){
            return false;
        }
        temp = (temp % flag)/10; //取余去掉最高位,整除去掉最低位!
        flag /= 100; //因为同时取了最高位和最低位,故
    }
    return true;
}
Python 
#写法一:获得反写数再比较

def is_palindrome(num):
    temp = num
    remain=0
    reverse_num = 0
    while(temp>0):
        remain = temp%10  #获取当前数的个位
        reverse_num = reverse_num*10  + remain 
        temp //= 10 #去掉当前数的个位,为下次循环做准备 
        #注意python中的整除是  //
    #循环结束获得反写数 reverse_num,接下来比较即可
    if(num!=reverse_num):
        return False
    return True


#写法二:回文数性质

def is_palindrome(num):
    temp = num
    flag =1
    while(temp//flag>=10): #flag和temp两数位数一致时停止
        flag *= 10
    while(temp>0):
        left = temp//flag #获取最高位
        right = temp%10   #获取个位
        if(left!=right):
            return False
        temp = (temp % flag)//10  #取余去掉最高位,整除10去掉个位
        flag //=100  #上一步消除了两个位,flag位数也“降2”
    return True
    
JAVA
//写法一:求出反写数字后比较:
public class num{  
    
    public static boolean is_palindrome(Integer num){

        Integer reversenum = 0;
        int temp=num;  //num值后面还用来比较,故用临时变量操作
        int remain=0;
        while(temp>0){  //常用的数字反写步骤
            remain = temp%10;
            temp/=10;
            reversenum = reversenum*10+remain; 
        }
        if(reversenum.equals(num)){ //注意!不能直接用“==”判断,
                                    //==判断的是【引用】是否相等,
                                    //而equals()方法判断的是【值】是否相等
            return true;
        }
        return false;
    }
//Debug:
    public static void main(String[] args){
        System.out.println(is_palindrome(12321));
    }
}

写法二:回文数性质拆分比较
public class num{
    
    public static boolean is_palindrome(Integer num){

        int flag=1; //找出num的最高位
        int temp = num;
        while(temp/flag>=10){  //注意控制条件
            flag*=10;
        }
        while(temp>0){
            int left = temp/flag;  //整除当前的最高位,得当前最高位数
            int right = temp%10;   //取当前的个位数
            if(left!=right){
                return false;
            }
            temp = (temp%flag)/10; //去掉最高位【和】个位上的数字
            flag /=100;  //最高位降两位,而不是/10 
        }
        return true;
    }
//Dbug:
    public static void main(String[] args){
        System.out.println(is_palindrome(12321));
    }
}

3、水仙花数(Armstrong数)

定义

水仙花数,即 Armstrong数最初的定义是:一个三位数的各个位上的数字的幂次之和等于该数本身,后来其范围不局限于三位数,也有四位数、五位数的类似水仙花数的概念。

处理思路

1、数字拆分:

        不管几位数,其处理方式一致,这里以三位数为例。最直接的思路就是 数字位数拆分。

2、递归:

        而对于不定长的多位数同时求水仙花数,可以通过递归实现。

代码实现     

C语言
//思路一:位拆分(对不定位数的数,可以像回文数中加flag那样处理)

bool is_armstrong(int num){  //三位数为例

    int sum=0;

    //注意!pow函数的返回类型为double,即使强制转换也可能存在精度丢失的问题,
    //故其实在其它场景应用时,自定义立方函数更靠谱,下面写强转是为强调pow返回的是double
    sum = (int)pow((num/100),3) +(int)pow((num/10)%10,3) + (int)pow(num%10,3);
    //百十个位的立方和
    return sum==num;

}

//思路二:递归
#include<stdio.h>

int getsum(int num){ //递归函数获取各位数的立方和

    int one=0,result=0;
    if(num<10)
        return num;
    one = num % 10; //获取个位数字
    result = getsum(num/10);  //递归调用
    
    return result + pow(one,3);
}


int main(){
int num=0;
scanf("%d",&num);

if(num== getsum(num))
    printf("是水仙花数");
else    
    printf("不是水仙花数");

    return 0;
}
 Python:
#思路一:位拆分

def is_armstrong(num): #三位数为例
    sum =0
    temp,one=num,0
    while(temp>0):
        one = temp%10
        sum+=one**3
        temp //=10
    return sum == num


#思路二:递归

def get_sum(num):

    if(num<10):  #递归终止条件
        return num
    one = num % 10  #获取个位数字
    result =get_sum(num//10) #递归调用
    return  result + one**3  #返回各位数立方之和

def isarmstrong_2(num):
    sum = get_sum(num)
    return sum==num 
    
JAVA: 
//思路一:数字拆分

import java.lang.Math;

public class test {
    public static boolean isArmstrong(int num){

        //注意!pow函数的返回类型为double,即使强制转换也可能存在精度丢失的问题,
        //故其实在其它场景应用时,自定义立方函数更靠谱,下面写强转是为强调pow返回的是double

        int sum = (int)Math.pow((num/100),3) + (int)Math.pow(((num/10)%10),3)+ (int)Math.pow(num%10, 3);
        return sum==num;
    }
}

//思路二:递归
    // 递归获取数位立方和
    public static int getSum(int num){
        if(num < 10){
            return num; // 如果是个位数,直接返回该数字
        }

        int lastDigit = num % 10; // 获取个位数
        int sumOfRemainingDigits = getSum(num/10); // 递归获取其他位数的立方和
        return (int)Math.pow(lastDigit, 3) + sumOfRemainingDigits; // 返回个位数的立方与其他位数立方和的总和
    }

    // 检查是否为水仙花数
    public static boolean isArmstrong(int num){
        return getSum(num) == num; // 判断数位立方和是否等于原始数
    }

4、完全数(完全数/完备数)

定义

完全数指的是 一个等于除它本身之外所有因子的,即一个数所有因子(除它本身外)之等于它本身。

处理思路

1、暴力因子迭代法:

        穷举1~n^(1/2)之间的所有正数寻找其因子,并将因子相加,最终判断相加结果和n是否相等。

2、欧拉公式求解:

        欧拉公式可生成完全数,perfect_number = 2^{p-1}2^{p}-1) 其中p(2^p-1)均为素数,也就是说,只要找到符合条件的素数p为素数同时,(2^{p}-1)也是素数),即可得到一个完全数: 2^{p-1}2^{p}-1

复杂度分析:

  1. 因子迭代法:

    • 时间复杂度:O(√n)
    • 空间复杂度:O(1)
  2. 欧拉公式法:

    • 时间复杂度:O(logn)
    • 空间复杂度:O(1)

代码实现 

C语言:
//思路一:因子迭代法(数变大后特别低效,较大数推荐思路二)
#include<stdio.h>

bool is_perfect_number(int num){

    int sum = 0;
    for(int i=1;i<sqrt(num)+1;i++){
        if(num%i==0)
            sum += i; //是num的因子就加起来
    }
    return sum == num;
}
int main(){
int a = 6;
printf(is_perfect_number(a));
    return 0;
}


//思路二:欧拉公式

#include<stdio.h>

bool is_prime(int num){  //素数判断
    int temp = (int)(sqrt(num));
    for(int i=2;i<=temp;i++){
        if(num%i ==0 )
            return false;
    }
     return true;
}

bool is_perfect_number(int num){
    //欧拉公式直接判断
    return (is_prime(num) && is_prime((int)(pow(2,num))-1));//pow返回类型为double        
}

int main(){

    int n=5;
    int count =0;
    int begain_number =2;
    while(count<n){
        if(is_perfect_number(begain_number)){
            count+=1;
            printf("第 %d 个完全数是:%d\n",count,begain_number);
        }
        begain_number++;
    }
    return 0;
}
Python:
#思路一:因子迭代(数过大时低效)

def is_perfect_number(num):
    sum=0
    for i in range(1,num**(0.5)+1):
        if(num%i==0):
            sum += i
    return sum == num 

#思路二:欧拉公式解法

def is_prime(num): #素数判断
    if(num<2):
        return False
    elif(num==2):
        return True
    elif(num%2==0):
        return False
    for i in range(2,(int)(num**0.5+1)): #
        if(num%i==0):
            return False
    return True

def is_perfect_number(num):
    if(is_prime(num) and (is_prime((2**num)-1))):  //欧拉公式应用
        return True
    return False

n = 6   #找出前6个完全数
count =0 
begain_number =1  #设置查找起点为 1 
while(count < n):
    if(is_perfect_number(begain_number)):
        count +=1
        print(f"第{count}个完全数为:{begain_number}")
    begain_number+=1


Java:
import java.lang.Math;
public class 算法中的各种数 {

    //思路一:因子迭代
    public static boolean is_perfect_number_1(int num){
        int sum=0;
        int flag = (int)Math.sqrt(num);
        for(int i=1;i<=flag;i++) {  //注意不要将i初始值设为0!
            if(num%i==0){
                sum+=i;
            }
        }
        return sum == num;
    }

    //思路二:欧拉公式
    public static boolean is_prime(int num){  //素数判断
        if(num<2){
            return false;
        }
        else if(num==2){
            return true;
        }
        else if(num%2==0){
            return false;
        }
        int flag = (int)Math.sqrt(num); //检索上限就是num的算术平方根
        for(int i=2;i<=flag;i++){
            if(num%i==0){
                return false;
            }
        }
        return true;
    }

    public static boolean isPerfectNumber(int num){
        if(is_prime(num)&&is_prime((int)Math.pow(2,num)-1)){ //欧拉公式应用
            return true;
        }
        return false;
    }

//Debug:
    public static void main(String[] args){
        //只演示调用写法二
        int n=4; //找出前4个完全数
        int count=0; //已找到后计数
        int begain_number =1; //从1开始找

        while(count<n){
            if(isPerfectNumber(begain_number)){
                count++;
                System.out.println("第"+count+"个完全数是"+begain_number);
            }
            begain_number++;
        }
    }
}

5、斐波那契数列

定义

        斐波那契数列是指数学上的一个序列,该序列从第三项开始,每一项都等于前两项的和。

用数学方式来表示,斐波那契数列可以定义为 F(0) = 0, F(1) = 1, 以及对于每个 n >= 2,F(n) = F(n-1) + F(n-2)。因此,这个序列的前几项如下:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55,依此类推。

斐波那契数列在数学和计算机科学中有着广泛的应用,例如在递归算法、金融数学、自然科学和计算机技术中都有使用。

处理思路

1、迭代输出前n项

       通过循环迭代输出所有项,此方式效率优于递归

2、附带“备忘录”的递归输出前n项(动态规划)

        利用数组或HashMap存储递归中间的值,示例中采用数组实现。

代码实现 

C语言:
//迭代输出
#include<stdio.h>
void fibonacci(int n){  //输出前n项
    int a=0,b=1;
    if(n==1)  //输出第一项
        printf("%d\n",a);
    else if(n==2){ //输出前两项
        printf("%d, %d \n",a,b);
    }
    else{ //大于等于3项时才输出
        int temp=0,third=0;
        printf("%d\t%d\t",a,b); //先单独输出前两项
        for(int i =2;i<n;i++){
            third=a+b;
            printf("%d\t",third);
            a=b;
            b=third;
    }
    }
}

    //思路二:带“备忘录”的递归(动态规划)
int num[100];  //声明初始化一个全局变量,

int fibonacci_2(int n){
    if(num[n]!=0)
        return num[n];
    else if(n<=1){
        num[n]=1;
        return num[n];
    }else{
        num[n]=fibonacci_2(n-1)+fibonacci_2(n-2);
    }
    return num[n];
}

int main(){
    int n=6;
    for(int i=0;i<n;i++){
        printf("第 %d 项:%d\n",i+1,fibonacci_2(i));
    }
}
Python:
#1、迭代输出前n项

def fibonacci(n):
    a,b=0,1
    if(n<3):
        if(n==1):
            print(a)
        elif(n==2):
            print(f"{a}, {b}")
    elif(n>2):
        print(f"{a}, {b}",end=', ')
        for i in range(2,n): #已经输出了2项,所以从2开始
            third = a+b
            print(third,end=', ')
            a,b = b,third


#    ###思路二:带“备忘录”的递归(动态规划)

num_dict = {}  #空列表访问会出现IndexError错误,故用空字典
def fibonacci_1(n):  #仅是返回一项
    if(n in num_dict):
        return num_dict[n]
    elif(n<=1):
        return 1
    else:
        result = fibonacci_1(n-1)+fibonacci_1(n-2)
        num_dict[n] = result
    return num_dict[n]

#调用
n=6
for i in range(0,n):
    print(f'第{i+1}项:{fibonacci_1(i)}')

Java:
//1、迭代输出前n项 (效率最高)
public static void fibonacci(int n){
        int a=0,b=1; //前两项
        if(n<3)
            if(n==1)
                System.out.println(a);
            else if(n==2)
                System.out.println(a+", "+b);
        else if(n>2){
            System.out.print(a+" "+b+" ");
            for(int i=2;i<n;i++){
                int third= a+b;
                System.out.print(third+" ");
                a = b;
                b = third;
            }
        }
    }

//2、附加“备忘录”的递归 (动态规划)  //效率高于单纯递归

public class 斐波那契数列 {
    
    static int[] num;  //中间用于存储的数组
    public static int fibonacci_2(int n){  //带备忘录优化的递归版本(动态规划)  //可以输出的第n项
        if(num[n]!=0){
            return num[n];
        }
        else if(n<=1){
            num[n]=1;
            return num[n];
        }
        else{
            num[n] = fibonacci_2(n-1)+fibonacci_2(n-2);
        }
        return num[n];
    }
    
    public static void main(String[] args){
        int n =66;  //输出长度
        
        num = new int[n+1];  //初始化数组
        for(int i=0;i<n;i++){
            
            System.out.print("第"+(i+1)+" 项:"+fibonacci_2(n)+"\n");
        }
    }
}

6、数的阶乘

定义

阶乘是一个自然数 n 的乘积,表示为 n!。它定义为从 1 到 n 的所有正整数的乘积。换句话说,n 的阶乘可以表示为:n! = 1 * 2 * 3 * ... * (n-1) * n。

例如,5 的阶乘表示为 5! = 1 * 2 * 3 * 4 * 5 = 120。

处理思路

1.递归实现:

递归函数会不断调用自身,直到满足某个终止条件。所以在计算n的阶乘时,可以使用递归函数 f(n) = n * f(n-1),其中终止条件是 f(0) = 1。递归实现简洁明了,但在计算大数的阶乘时可能存在栈溢出的风险。

2.迭代实现:

可以使用循环结构来计算数的阶乘。从1循环到n,依次累乘得到最终结果。这种方法不会出现栈溢出的情况,适合计算较大数的阶乘。

3.动态规划实现:

使用动态规划可以避免重复计算阶乘,提高计算效率。可以使用一个数组来保存计算过的中间结果,每次计算时先查找数组中是否已经计算过,如果没有则计算并保存结果,避免重复计算。

代码实现 

C语言:
//递归
int factorial_1(int num){
    if(num==0) //递归终止条件
        return 1;
    return num*factorial_1(num-1);
}

//迭代
int factorial_2(int num){
    int result = 1;
    for(int i=1;i<=num;i++){
        result *= i;
    }
    return result;
}

//动态规划
int station_factorial[100]; //默认所有元素值为0 
int result =0;  //全局变量
int factorial_3(int num){
    if(station_factorial[num-1] != 0)   //注意下标处理,即5的阶乘结果存储在数组下标为[4]的位置
        return station_factorial[num];
    if(num==0)  // 递归终止条件
        return 1;
    result = num * factorial_3(num--);
    station_factorial[num]= result; //此时递归中的num已经“归”到了第一次传入的num值,
                                    //将阶乘结果存入数组对应位置,避免下次同num值调用的计算
    return result;  //最后返回阶乘值
}
Python:
#1、递归
def factorial_1(a):
    if(a==0): #递归终止条件
        return 1
    return a*factorial_1(a-1)



#2、迭代
def factorial_2(a):
    result =0
    for i in range(1,a+1):
        result *=i
    return result
    

#3、动态规划
station_list = [-1 for _ in range(101)]  #创建一个包含100个-1的列表,现在每个元素都是-1
def factorial_3(num):
    if(station_list[num]!=-1):  #若不是初值-1,则就是a的阶乘的结果,直接返回即可
        return station_list[num]
    if(num==0): #递归终止条件
        return 1
    temp =  num * factorial_3(num-1)
    station_list[num] = temp  #此时递归中的num已经“归”到了第一次传入的num值,
                              #将阶乘结果存入数组对应位置,避免下次同num值调用的计算
    return temp  //最后返回阶乘值
#这里的下标处理的不好,但看着更直观(hhh),station_list[0]的位置浪费了,使用时你可以自己灵活处理。
Java:
//递归
public static int factorial_1(int num){
    if(num==0){  //递归终止条件
        return 1;
    }
    return num * factorial_1(num--);
}

//迭代
public static int factorial_2(int num){
    int result=0;
    for(int i=1;i<=num;i++){
        result *=i;
    }
    return result;
}


//递归

//来两个全局变量
static int[] station_num = new int[100];  //全局“货架”,未存入数据默认元素值为0
static int temp=0;

public static int factorial_3(int num){
    if(station_num[num-1] !=0 ){   //数x 对应阶乘的结果存储在数组中的下标为 【x-1】 
        return station_num[num-1];
    }
    if(num==0){  //递归终止条件
        return 1;
    }
    temp = num * factorial_3(num-1);
    station_num[num-1] = temp;   // 数x 对应阶乘的结果存储在数组中的下标为 【x-1】 
    return temp;
}

7、最大公约(GCD)数&最小公倍数(LCM)

定义

最大公约数:指两个或多个数的最大的公共因数,换言之就是能同时将两个或多个数整除的最大因数。

最小公倍数:两个或多个数共有倍数中的最小数,可以通过 这些整数的乘积除以它们的最大公约数(两数间关系) 求得它们的最小公倍数。

处理思路

根据两数间关系,只要知道最大公约数GCD即可求得最小公倍数LCM,故以下只讨论最大公约数求法。

1、辗转相除法(较大数gengyou)

常见处理方法是辗转相除法得到最大公约数,然后根据 乘积=最大公约数*最小公倍数 关系,计算最小公倍数。具体辗转相除法步骤如下:

a. 输入两个数 a 和 b
b. 如果 b 等于0,返回 a 作为最大公约数。
c. 否则,计算 a % b,并将 a 更新为 b,将 b 更新为 a % b
d. 重复步骤 c,直到某次调用中的余数为0。
e. 返回上一层的调用结果,即为最大公约数。

*若求的是两个以上的数,可以先任意选两个数,求出最大公约数,再将这个最大公约数与第三个数求出新的最大公约数,以此类推,最终求得所有数的最大公约数。

2、更相减损法

不断用较大数减去较小数,然后交换两个数,直到两个数相等。具体步骤如下:

  1. 第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。
  2. 第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。
  3. 则第一步中约掉的若干个2与第二步中等数的乘积就是所求的最大公约数。

等数:是指【a-b = c】中,当【b=c】时,bc就是等数

3、Stein算法

辗转相除法 vs 更相减损术:

(1)两者都是求最大公因数的方法,计算上辗转相除法以除法为主,更相减损术以减法为主,计算次数上辗转相除法计算次数相对较少,特别当两个数字大小区别较大时计算次数的区别较明显。

(2)从结果体现形式来看,辗转相除法体现结果是以相除余数为0则得到,而更相减损术则以减数与差相等而得到。

更相损减法在两数相差较大时,时间复杂度容易退化成O(N),而辗转相除法可以稳定在O(logN)。但辗转相除法需要试商,这就使得在某些情况下,使用更相损减法比使用辗转相除法更加简单。而stein算法便由此出现。

Stein算法具体如下:

1、设置An=|A|、Bn=|B|、Cn=1和n=1

2、如果An=Bn,那么An(或Bn)*Cn是最大公约数,算法结束

3、如果An=0,Bn是最大公约数,算法结束

4、如果Bn=0,An是最大公约数,算法结束

5、如果An和Bn都是偶数,则An+1=An/2,Bn+1=Bn/2,Cn+1=Cn*2(注意,乘2只要把整数左移一位即可,除2只要把整数右移一位即可)

6、如果An是偶数,Bn不是偶数,则An+1=An/2,Bn+1=Bn,Cn+1=Cn(很显然啦,2不是奇数的约数)

7、如果Bn是偶数,An不是偶数,则Bn+1=Bn/2,An+1=An,Cn+1=Cn(很显然啦,2不是奇数的约数)

8、如果An和Bn都不是偶数,则An+1=|An-Bn|/2,Bn+1=min(An,Bn),Cn+1=Cn

9、n=n+1,转2

代码实现

C语言:

//辗转相除法求最大公约数
int gcd_1(int a,int b){
    if(b==0)
        return a;
    else
        return gcd_1(b,a%b);
}
//利用性质求【最小公倍数】
int lcm(int a,int b){
    int gcd = gcd_1(a,b); // 先求最大公约数
    return a*b/gcd; //两数乘积/最大公约数=最小公倍数
}

//更相减损术求最大公约数
int gcd_2(int a,int b){
    int temp=0,result=1;
    if(a<b){ //保证a为较大数
        temp=a;
        a=b;
        b=temp;
    }
    else if(a==b){ //补充逻辑完整
        return a;
    }
    //以上是预处理,现在开始判断
    if(a%2==0&&b%2==0){ //若两个数同时为偶数
        while(a%2==0&&b%2==0){
            result*=2;
            a/=2;
            b/=2;
        }
        return result;
    }
    else{
        while((a-b)!=b){
            //下一轮两个数,一个是较小数b,另一个是差值a-b
            temp=b;
            b=a-b;
            a=b;
            //下一轮开始之间别忘了更新较大数和较小数
            if(a<b){
                temp=a;
                a=b;
                b=temp;
            }
        }//当 a-b ==较小数b时,b即为最大公约数
        return b;
    }
}


//3、Stein算法(辗转相除法改进)
#include<math.h>
int gcd_3(int a,int b){
    int result=1;
    while(1){
        if(a==b)    
            return a*result; 
        else if(a==0)
            return b;
        else if(b==0)
            return a;
        if(a%2==0 && b%2==0){  //两数同时为偶数
            a/=2;
            b/=2;
            result*=2;
        }else if(a%2==0) //仅a为偶数
            a/=2;
        else if(b%2==0)  //仅b为偶数
            b/=2;
        else{            //两个数都不是偶数
            a=abs(a-b); //两个函数都属于 math标准库,记得加头文件
            b=min(a,b);
        }
    }
}

Python:

#1、辗转相除法求最大公约数
def gcd_1(a,b): #可在传入参数前将a置为最大数,b置为最小数便于更快找到最大公约数
    if(b==0):
        return a
    else:
        return gcd_1(b,a%b)

#利用性质求最小公倍数
def lcm_1(a, b):
    gcd = gcd_1(a, b) #先求最大公约数
    lcm = (a * b) // gcd #两数乘积/最大公约数=最小公倍数
    return lcm

#3、更相减损术求最大公约数(不如前者)
def gcd_2(a,b):
    if(a<b): #保证a为较大数
        a,b=b,a 
    elif(a==b): #较为多余的一步,补充逻辑完整
        return a
    result=1
    temp=0
    if(a%2==0 and b%2==0): #若都是偶数,则用2除
        while(a%2==0 and b%2==0):
            a//=2
            b//=2
            result*=2
        return result
    else:                  #若两个不同时为偶数
        while((a-b)!=b):
            #更新下一轮两个新数的值
            #注意不要写成 a,b = b,a-b!
            temp=b
            b=a-b
            a=temp  
            #下一轮开始之前保持a为较大数
            if(a<b): 
                a,b=b,a 
        return b

#3、#Stein算法(改进辗转相除法)
def gcd_3(a,b):
    result=1
    while(1):
        if(a==b):
            return a*result
        elif(a==0):
            return b
        elif(b==0):
            return a
        if(a%2==0 and b%2==0): #两个数都是偶数时
            a//=2
            b//=2
            result*=2
        elif(a%2==0):  #仅a==0
            a//=2
        elif(b%2==0):  #仅b==0
            b//=2
        else:  #两个都不是偶数
            a=abs(a-b)
            b=min(a,b)

Java:

//辗转相除法求最大公约数
    public static int gcd_1(int a,int b){
        if(b==0)
            return a;
        else
            return gcd_1(b,a%b);
    }

 //利用性质求最小公倍数
    public static int lcm_1(int a,int b){
        int gcd = gcd_1(a,b); //先求最大公约数
        return a*b/gcd;       //两数乘积/最大公约数=最小公倍数
    }

 //2、更相减损术
    public static int gcd_2(int a,int b){
        int temp=0,result=1; //temp用于变量交换,result用于累乘得出结果,所以赋初值为1
        if(a<b) {//保证a为较大值
            temp=a;
            a=b;
            b=temp;
        }
        else if(a==b) //补充逻辑完整
            return a;
        
        if(a%2==0 && b%2==0){//若两个数都为偶数
            while(a%2==0&&b%2==0){
            a/=2;
            b/=2;
            }
            return result;
        }
        else { //不同时为偶数时
            while((a-b)!=b){
                //下一轮的两个数一个是较小数b,一个是差值 a-b.将它们赋值给下一轮a,b
                temp=b;
                b=a-b;
                a=temp;
                //保证a始终为较大值
                if(a<b) {
                    temp=a;
                    a=b;
                    b=temp;
                }
            }
            //当较小数b等于差值a-b时,b为最大公约数
            return b;
    }
}


//3、    //Stein算法(辗转相除法改进版)
    public static int gcd_3(int a,int b){
        int result=1;
        while(true){
            
            if(a==b)
                return a*result;
            else if(a==0){
                return b;
            }
            else if(b==0){
                return a;
            }
            if(a%2==0 && b%2==0){  //两个数都为偶数
                a/=2;
                b/=2;
                result*=2;
            }
            else if(a%2==0)     //仅a为偶数
                a/=2;
            else if(b%2==0)     //仅b为偶数
                b/=2;
            else{               //两个数都不是偶数
                a=Math.abs(a-1);
                b=Math.min(a, b);
            }    
        }
    }

9、黑洞数、卡普雷卡尔数(Kaprekar数)

定义

Kaprekar 数:Kaprekar数是黑洞数的一个特例。更广泛地说,黑洞数是指通过一系列操作最终收敛到一个固定的常数值的数。Kaprekar数是这种现象的一种具体体现。

它的操作步骤是将四位数的数字重新排列,形成最大的数和最小的数,然后将最大数减去最小数,得到一个新的四位数,并将这个过程重复。经过一系列这样的操作后,所有的四位数都会收敛到6174,这个数据称为“四位数的黑洞数”。

这个黑洞数在整理的时候发现在互联网上说法不一,此处只提编程中常提的四位数黑洞数。想了解更多黑洞数相关的信息,此处留位,后续补上

处理思路

正如其定义中所说,我们将要处理的对象是一个四位数,然后重复进行:位切分 - 排序 - 获取两个新数 - 两数相减得到下一个数,直到出现6174为止。所以编程实现过程也很简单

代码实现 

C语言:
#include<stdio.h>
#include<stdlib.h>


//数组排序时我想到了冒泡排序,但经调试才想起冒泡排序弊端
//就是当纯乱序数组出现时,冒泡排序有可能失效!
//故针对本题我用了下面的选择排序
void bubble_sort(int* arr) { //冒泡排序
    int length = sizeof(arr) / sizeof(int);
    int i = 0, j = 0;
    for (; i < length - 1; i++) {
        int temp = 0;
        for (; i < length - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                // if(arr[j]==arr[j+1])
                //     continue;
            }
        }
    }
}


void select_sort(int* arr, int length) {  //选择排序(由小到大升序)
    printf("%d", length);
    int temp = 0;
    for (int flagMin = 0; flagMin < length; flagMin++) {
        for (int flag_move = flagMin; flag_move < length; flag_move++) {
            if (arr[flagMin] > arr[flag_move]) {
                temp = arr[flagMin];
                arr[flagMin] = arr[flag_move];
                arr[flag_move] = temp;
            }
        }
    }
}

int kaprekar(int num) {  //求四位数黑洞数
    int obj = num; //用于和下一个数比较,看是否重复
    int arr0[4]; //存储各位上的数
    //位拆分
    arr0[0] = num % 10;
    arr0[1] = (num / 10) % 10;
    arr0[2] = (num / 100) % 10;
    arr0[3] = num / 1000;

    select_sort(arr0, 4);//选择排序将各位上的数字排序(升序)

    int minNum = arr0[0] * 1000 + arr0[1] * 100 + arr0[2] * 10 + arr0[3];
    int maxNum = arr0[3] * 1000 + arr0[2] * 100 + arr0[1] * 10 + arr0[0];
    //printf("%d,\t%d\n", maxNum, minNum);  //调试

    //递归
    if (maxNum - minNum == obj)//若重复则返回这个数(将返回6174)
        return obj;
    else
        return kaprekar(maxNum - minNum);
}

void main() {
    int arr1[] = { 1,2,3,4 };
    int blackhole_num = kaprekar(5496);
    printf("最终黑洞数:%d", blackhole_num);
}
Python:
def kaprekar(num):
    
    obj = num #用于和next_num比较,若相等则返回
    temp_list_1 =[]#列表存储各位上的数
    while(num>0): #位拆分
        temp_list_1.append(num%10)
        num //=10
    temp_list_2 = sorted(temp_list_1) #将切分位上的数默认升序排序
    #temp_list_2=[1,2,3,4]
    #得到最大最小数
    max_num = temp_list_2[0]+temp_list_2[1]*10+temp_list_2[2]*100+temp_list_2[3]*1000
    min_num = temp_list_2[0]*1000+temp_list_2[1]*100+temp_list_2[2]*10+temp_list_2[3]

#递归去找重复出现的数(6174)
    if((max_num-min_num)==obj): #重复出现则返回 
        return obj
    else:
        return kaprekar(max_num - min_num)
        
#deBug:
print(kaprekar(1234))  #输出6174


Java:
package 平台代码.CSDN.算法中的各种数;
import java.util.Arrays;
public class 黑洞数 {
    
    public static int kaprekar(int num){
        int obj = num; //用于和下一个数比较,看是否重复
        int[] arr1 = new int[4]; //数组存储各位上的数
        //开始位拆分
        arr1[0]=num%10;    
        arr1[1]=(num/10)%10;
        arr1[2]=(num/100)%10;
        arr1[3]=num/1000;

        Arrays.sort(arr1); //默认正序排列数组
        int maxNum = arr1[0] +arr1[1]*10 +arr1[2]*100 +arr1[3]*1000;
        int minNum = arr1[3] +arr1[2]*10 +arr1[1]*100 +arr1[0]*1000;

        if(maxNum-minNum==obj) //重复则返回这个数(将返回6174)
            return obj;
        else
            return kaprekar(maxNum-minNum);
    }
    
public static void main(String[] args){
    System.out.println(kaprekar(7654)); //输出结果:6174
}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值