最长考拉兹序列

news2024/11/25 10:42:11
  • 题目: 

考虑如下定义在正整数集上的迭代规则: 

n  \rightarrow  n/2 (若n为偶数)

n  \rightarrow  3n+1 (若n为奇数)

从13开始,可以迭代生成如下的序列:

        13 \rightarrow 40 \rightarrow 20 \rightarrow 10 \rightarrow 5 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1

可以看出这个序列(从13开始到1结束)共有10项。 尽管还未被证明,但普遍认为,从任何数开始最终都能抵达1并结束, 这被称为 “考拉兹序列”。

在小于100万的数中,从哪个数开始迭代生成的序列最长?

注:在迭代过程中允许出现超过一百万的项(例如,题目中的序列,第二项40就比第一项13大)。

  • 题目分析:

根据题目描述,我们可以知道这是一道枚举类型的题,

  • 代码实现
//代码段1
#include <stdio.h>

#define MAX_N 1000000


int cal_len(int n){
    if(n == 1) return 1;
    if(n & 1) return cal_len(3*n +1)+1;
    return cal_len(n>>1)+1;    
}

int main(){
    int ans = 0, len = 0;
    for(int i = 1;i <= MAX_N;i++){
        int temp_len = cal_len(i);
        if(temp_len <= len) continue;
        len = temp_len;
        ans = i;
    }
    printf("ans = %d, len = %d\n",ans, len);
    return 0;
}

小技巧:

在函数 int cal_len(long long n) 中,判断当前n值是否为偶数,没有使用模运算 if(n % 2 ) 而是使用了 位运算 if(n & 1 ) ,  这是因为位运算的运算效率更快 ,(n& 1) 为真值,说明n是奇数。 同样在,n/2 运算也写成了位运算 n >> 1.

  • 运行结果

  • 运行结果分析

程序报错崩溃了,这个报错结果表示“访问了非法内存”,造成这个报错的原因可以有以下几种情况:

1)访问未被分配或已释放的内存区域;

2)使用空指针或野指针进行读写操作;

3)数组越界访问,例如通过错误的下标访问数组元素;

4)非法指针操作,如未经授权的指针转换或解引用非合法的内存地址;

5)堆栈溢出,通常由于局部变量过大或递归调用过深导致;

6)多线程编程中使用了线程不安全的函数或未加锁保护共享资源;

7)对常量或只读内存区域的非法写入操作。

如何避免出现这个报错?

1)正确的使用指针;

确保所有指针在使用前都进行了初始化,并在不再需要时及时释放;避免使用野指针,特别是不要随意假设指针指向的内存是可用的。

2)数组的访问

不要出现数组的越界访问,例如通过错误的下标访问数组元素;

3)动态内存管理

在程序员自己进行内存分配时,确保内存分配后要有对应的释放操作,避免出现内存泄漏;

4)多线程环境

如果使用了多线程编程,要合理使用互斥锁或其他同步机制来保护共享资源。

经过上述分析,我们进一步分析代码段1出现崩溃的原因可能是什么,代码中使用了递归调用,会不会是递归调用的层数太深,栈中装不下了呢?但是在cal_len 函数中,即使递归层数比较深,每一次进行函数调用所需要的空间仅是一个整型值再加上一个函数指针的大小,所以,我们猜测他不是因为空间上存不下了,那会不会是函数本身陷入了死循环?这个函数如果想结束,需要满足条件 n==1, 如果这个条件不会出现呢?那就进入死循环。n在变化的过程中,超出了int 所能表示的范围,成为了负数,就会出现这个结果。我们来验证一下, n是否出现了负数。

//代码段2
#include <stdio.h>

#define MAX_N 1000000


int cal_len(int n){
    if(n == 1) return 1;
    if(n < 0) printf("n < 0 ,error");
    if(n & 1) return cal_len(3*n +1)+1;
    return cal_len(n>>1)+1;    
}

int main(){
    int ans = 0, len = 0;
    for(int i = 1;i <= MAX_N;i++){
        int temp_len = cal_len(i);
        if(temp_len <= len) continue;
        len = temp_len;
        ans = i;
    }
    printf("ans = %d, len = %d\n",ans, len);
    return 0;
}

运行结果:

运行结果显示,果然出现了负数,说明数据溢出了,即当前的int类型存不下实际数据的长度,应该改为long long 类型。

//代码段3
#include <stdio.h>

#define MAX_N 1000000


int cal_len(long long n){
    if(n == 1) return 1;
    if(n & 1) return cal_len(3*n +1)+1;
    return cal_len(n>>1)+1;    
}

int main(){
    int ans = 0, len = 0;
    for(int i = 1;i <= MAX_N;i++){
        int temp_len = cal_len(i);
        if(temp_len <= len) continue;
        len = temp_len;
        ans = i;
    }
    printf("ans = %d, len = %d\n",ans, len);
    return 0;
}

运行结果:

  • 代码优化:

代码段3运行时间:

现在将数据规模由100万增加到1000万:

结果显示,运行时间也增加了10倍多,可见这段代码的耗时是较长的,提高运行时间就是我们优化的目标,那么,这段这段代码为什么会如此耗时?

分析递归调用的过程:

如果当前遍历到 i = 10, 那么我们得到的迭代序列是:

10 \rightarrow 5 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1, len = 7;

如果当前遍历到 i = 13, 得到的迭代序列是:

13 \rightarrow 40 \rightarrow 20 \rightarrow 10 \rightarrow 5 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1, len = 10;

这两段迭代过程是有重复的,当i= 13 时,序列10 之后的迭代过程在 i = 10 时已经计算过了,所以如果i = 13时, len = 1 + 1+ 1 + 7 就可以得出结果,所需要的前期操作只是把 i=10 时的len值记录保存下来。

这个将重复计算过程值保存下来的思路,被称为记忆化

总结:

1)将中间的计算结果保存下来,减少后续计算中的重复计算;

2)这种技巧常常被用在搜索算法中,称为“记忆化搜索”。

例如: 

先前计算:10 \rightarrow 5 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1

记忆化:f(10) = 7, f(5) = 6, f(16) = 5, ... f(1) = 1;

之后计算:13 \rightarrow 40 \rightarrow 20 \rightarrow 10 \rightarrow 5 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1

直接得到:f(20) = 1+f(10) = 1+7 = 8; f(40) = 1+1+f(10) = 9, f(13) = 1+1+1+f(10) = 10, 少做了6次计算。

  • 代码优化思路

1. 定义一个数据结构 int keep[M] , 用于存储数据范围M之内的序列长度, 即keep[i]  =  i 的序列长度,(i<M);

2.  当遍历值 n < M 时, len(n) =  keep[n];

3.  当遍历值 n > M 时,len(n) = 1+1+ ... + len(i) ; i<M;

  •  代码优化实现:
//代码段4
#include <stdio.h>

#define MAX_N 1000000
#define MAX_M 10000

int keep[MAX_M+5] = {0};

int cal_len(long long n){
    if(n == 1) return 1;
    if(n <= MAX_M && keep[n]) return keep[n];
    int ret = ((n & 1) ? cal_len(3*n+1):cal_len(n>>1))+1;
    if(n <= MAX_M) return keep[n];
    return ret;
}

int main(){
    int ans = 0, len = 0;
    for(int i = 1;i< MAX_N;i++){
        int temp_len = cal_len(i);
        if(temp_len <= len) continue;
        ans = i;
        len = temp_len;
    }
    printf("ans = %d, len = %d\n",ans, len);
    return 0;
}

  运行结果:

通过计算结果,运行时间果然是缩短了, 目前代码中的keep数组大小是10000,现在将数据规模增加到1000万,keep数据的大小不变:

 可以看到运行时间原来的约13秒提高到约5秒。

  • 优化结果分析

那么keep数组的大小设计为多大合适,是不是越大越好呢?

设: MAX_N = 100 0000

测试1: MAX_M = 10 0000

运行时间由 0.367s 提高到 0.164 s

测试2:MAX_M = 100 0000

 运行时间由0.164s 提高到0.032s

测试3:MAX _M = 1000 0000

运行时间由0.032s 增加到 0.082s

结论:

增加keep数组的大小是可以提高程序的运行时间的,但keep数组不是越大越好,因为keep是用于搜索查询的,而数据在计算机中是按页存储的,,如果keep数组太大,那么将会开辟多个页用于存储数据,在搜素查询所需数据时,就会将时间用在页面切换上,反而增加了时间的开销。

  • 题目总结:

1. 在题目中出现迭代,我们可以考虑递归思想的应用;

2. "segmentation fault" 报错的原因分析。

3. 当出现大量重复计算的时候,考虑“记忆化” 这个优化技巧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1852594.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实时语音翻译软件哪个好?多语言实时沟通无负担

众所周知&#xff0c;英语是国际交流的共同语言。无论是跨国商务洽谈还是学术研讨&#xff0c;英语的流畅使用至关重要。 然而&#xff0c;语言的障碍依然存在&#xff0c;这时一款高效的英语语音翻译软件就显得尤为关键。它们能够即时转换语言&#xff0c;让我们的对话无国界…

跨境电商系列02:跨境电商的本质,是一场现代文明的“侵x入”吗

作者|夏虫&#xff08;题图&#xff1a;中国龙&#xff09; 公号&#xff0c;数据虫巢(ID: blogchong) “ 不知道有没有人想过一个问题&#xff0c;海出电商市场是增量的没问题&#xff0c;那为何是跨境电商的模式呢&#xff1f;所谓跨境电商&#xff0c;一定是存在商品流动&a…

pytest测试框架pytest-rerunfailures插件重试失败用例

Pytest提供了丰富的插件来扩展其功能&#xff0c;介绍下插件pytest-rerunfailures &#xff0c;用于在测试用例失败时自动重新运行这些测试用例。 pytest-rerunfailures官方显示的python和pytest版本限制&#xff1a; Python 3.8pytest 7.2 或更新版本 此插件可以通过以下可…

《计算机英语》Unit2 Operating System and Computer Architecture 操作系统和计算机构造

SectionA Operating System操作系统 不同操作系统 批处理操作系统(Batch Processing Operating System) 分时操作系统(Time Sharing Operating System) 实时操作系统(Real Time Operating System) 个人操作系统(Personal Operating System) 网络操作系统(NOS, Network Operati…

【Vue3组件】分享一下自己写的简约风格评论区组件

代码比较简单&#xff0c;方便大家二次开发&#xff0c;旨在快速提供基础的样式模板&#xff0c;自行迭代定制 预览 简介 通用评论组件 组件功能 此组件旨在创建一个具备嵌套回复能力的通用评论区域&#xff0c;适用于构建动态、互动性强的用户讨论场景。 接收数据结构 组件通…

运营管理和服务支撑阶段

我前面的所有设备都部署好了&#xff0c;现在就需要运营管理和服务支撑 遇到问题了迅速解决&#xff0c;避免风险扩大 我们也可以给客户提供上面的服务&#xff0c;提高客户的预警能力&#xff0c;安全风险处理能力 我们不仅提供设备&#xff0c;还提供服务 我们公司成立了安…

如何选择服务器?快解析能搭建私人服务器吗?

随着网络的发展&#xff0c;搭建私人服务器逐渐成为网络达人们的热门选择&#xff0c;比如建立私人性质的博客、论坛、FTP、个人网站、服务器集群等。通过源搭建私人服务器&#xff0c;就可以将很多资源分享到网络上进行信息共享。随之而来的是服务器市场不断扩大&#xff0c;在…

马尔可夫聚类算法

马尔可夫聚类算法&#xff08;Markov Clustering Algorithm&#xff0c;MCL&#xff09;是一种用于图聚类的算法&#xff0c;广泛应用于生物信息学、社交网络分析、推荐系统等领域。 其核心思想是模拟随机游走过程&#xff0c;通过迭代地扩散和收缩图上的概率分布来识别图中的…

Java基础的重点知识-01-基础

文章目录 开发前言Java语言开发环境入门程序说明常量变量和数据类型数据类型转换运算符方法解析 开发前言 常用DOS命令 Java语言的初学者&#xff0c;学习一些DOS命令&#xff0c;会非常有帮助。DOS是一个早期的操作系统&#xff0c;现在已经被Windows系统取代&#xff0c;对于…

【mysql】建库

通过命令建库&#xff1a; CREATE DATABASE database_name; 如果是用Workbench&#xff1a;

React+TS前台项目实战(十六)-- 全局常用组件Pagination封装

文章目录 前言Pagination组件1. 功能分析2. 代码详细注释3. 使用方式4. 效果展示 [PC端&手机端] 总结 前言 在上篇文章中&#xff0c;我们封装了表格组件Table&#xff0c;本文则继续封装配套使用的分页器组件。想看Table表格组件的&#xff0c;可自行查看全局常用组件Tab…

【pytorch04】创建Tensor

numpy中的数据创建tensor 数据已经在numpy中了&#xff0c;将numpy中的数据转到tensor中来&#xff0c;因为我们将使用tensor在GPU上进行加速运算 从NUMPY导入的FLOAT其实是DOUBLE类型 list中的数据创建tensor FloatTensor()和大写的Tensor()接收的是shape&#xff08;即数据的…

敏捷开发笔记(第7章节)--什么是敏捷设计

目录 1&#xff1a;PDF上传链接 7.1: 软件出了什么错 7.2: 设计的臭味--腐化软件的气味 7.2.1: 什么激化了软件的腐化 7.2.2: 敏捷团体不允许软件腐化 7.3: “copy”程序 1: 初始设计 2: 需求在变化 3: 得寸进尺 4: 期望变化 7.3.1: “copy”程序的敏捷设计 7.3.2:…

【职场人】职场故事:与邀功精的共舞

在我的职业生涯中&#xff0c;我遇到过一位特别引人注目的同事&#xff0c;我们都叫他李经理。他的工作能力并不差&#xff0c;但他有一个习惯&#xff0c;那就是喜欢邀功。他的这种习惯&#xff0c;不仅让我印象深刻&#xff0c;也让我在合作中学会了不少东西。 恶心的四件事 …

MySQL学习笔记-进阶篇-视图和存储过程

四、视图和存储过程 视图 存储过程 基本语法 创建 CREATE PROCEDURE ([参数列表]) BEGIN --SQL END; 调用 CALL 存储过程名&#xff08;[参数列表]&#xff09; 查看 --查看指定数据库的存储过程及状态信息 SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SHCEMA…

AI-智能体

什么是 AI 智能体&#xff1f; 「AI 智能体」这个术语并没有真正被定义&#xff0c;对智能体究竟是什么也存在很多的争议。 AI 智能体可以定义为「一个被赋予行动能力的 LLM&#xff08;通常在 RAG 环境中进行函数调用&#xff09;&#xff0c;以便在环境中对如何执行任务做出…

第一次接触Swing

学习java版的HslCommunication发现使用的是Swing&#xff0c;所以了解了一下~ 了解&#xff1a; Swing是Java的标准库&#xff08;Java Foundation Classes, JFC&#xff09;的一部分&#xff0c;用于构建桌面应用程序的图形用户界面&#xff08;GUI&#xff09;。它是Java AWT…

华为某员工爆料:三年前985本科起薪30万,现在硕士起薪还是30w,感慨互联网行情变化

“曾经的30万年薪&#xff0c;是985本科学历的‘标配’&#xff0c;如今硕士也只值这个价&#xff1f;” 一位华为员工的爆料&#xff0c;揭开了互联网行业薪资变化的冰山一角&#xff0c;也引发了不少人的焦虑&#xff1a;互联网人才“通货膨胀”的时代&#xff0c;真的结束了…

Java-异常:不恰当的异常转换、不充分的日志记录、过度或不当的异常捕获

Java-异常&#xff1a;不恰当的异常转换、不充分的日志记录、过度或不当的异常捕获 Java-异常&#xff1a;不恰当的异常转换、不充分的日志记录、过度或不当的异常捕获一、前期准备二、案例分析1、不恰当的异常转换2、不充分日志记录3、过度或不当的异常捕获 三、正确处理方式1…

2024年6月计算机视觉论文推荐:扩散模型、视觉语言模型、视频生成等

6月还有一周就要结束了&#xff0c;我们今天来总结2024年6月上半月发表的最重要的论文&#xff0c;重点介绍了计算机视觉领域的最新研究和进展。 Diffusion Models 1、Autoregressive Model Beats Diffusion: Llama for Scalable Image Generation LlamaGen&#xff0c;是一个…