#
- 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 = (
) 其中p和(2^p-1)均为素数,也就是说,只要找到符合条件的素数(p为素数同时,(
)也是素数),即可得到一个完全数:
(
)
复杂度分析:
-
因子迭代法:
- 时间复杂度:O(√n)
- 空间复杂度:O(1)
-
欧拉公式法:
- 时间复杂度: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、更相减损法
不断用较大数减去较小数,然后交换两个数,直到两个数相等。具体步骤如下:
- 第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。
- 第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。
- 则第一步中约掉的若干个2与第二步中等数的乘积就是所求的最大公约数。
等数:是指【a-b = c】中,当【b=c】时,b和c就是等数
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
}
}