最长考拉兹序列

  • 题目: 

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

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. 当出现大量重复计算的时候,考虑“记忆化” 这个优化技巧。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pytest测试框架pytest-xdist插件并发执行测试用例

Pytest提供了丰富的插件来扩展其功能&#xff0c;本章介绍下插件pytest-xdist&#xff0c;主要是提供并行测试、分布式测试、循环测试等功能&#xff0c;可以加快测试速度。 pytest-xdist官方显示没有严格的python和pytest版本限制。 pytest-xdist安装 使用pip命令安装: pip…

高中数学:数列-等差数列、等比数列的和与通项公式的关系

一、等差数列 1、通项公式与求和公式 2、性质 性质1 求和公式比上n&#xff0c;依然是一个等差数列。 性质2 等差数列中&#xff0c;每相邻m项和&#xff0c;构成的数列&#xff0c;依然是等差数列&#xff0c;公差&#xff1a;m2d 二、等比数列 1、通项公式与求和公式 a…

「GPT源码探索」:从ChatPaper到学术论文GPT的二次开发实践

前言 本文的前两个部分最早是属于此旧文的《学术论文GPT的源码解读与微调&#xff1a;从ChatPaper到七月论文审稿GPT第1版》&#xff0c;但为了每一篇文章各自的内容更好的呈现&#xff0c;于是我今天做了以下三个改动 原来属于mamba第五部分的「Mamba近似工作之线性Transfor…

数据库原理与安全复习笔记

1 概念 产生与发展&#xff1a;人工管理阶段 → \to → 文件系统阶段 → \to → 数据库系统阶段。 数据库系统特点&#xff1a;数据的管理者&#xff08;DBMS&#xff09;&#xff1b;数据结构化&#xff1b;数据共享性高&#xff0c;冗余度低&#xff0c;易于扩充&#xff…

自动控制原理出射角计算

背景&#xff1a;突然发现自己出射角不会算 被减数是零点到极点的角度&#xff0c;减数是极点到极点的角度

毕业设计——可视化实验仿真平台

该程序用于毕业设计&#xff0c;架构为前后端分离技术&#xff0c;涉及技术包括vue3&#xff0c;SpringBoot&#xff0c;spring-secrity&#xff0c;Redis&#xff0c;需要者进群769119544进行相关咨询。 程序分为三个角色&#xff1a;学生、老师、管理员。使用了spring-secrit…

Vision Pro的3D跟踪能力:B端应用的工作流、使用教程和经验总结

Vision Pro的最新3D跟踪能力为工业、文博、营销等多个B端领域带来了革命性的交互体验。本文将详细介绍这一功能的工作流、使用教程,并结合实际经验进行总结。 第一部分:工作流详解 一、对象扫描 使用Reality Composer iPhone应用程序对目标对象进行3D扫描,如吉他或雕塑,…

从关键新闻和最新技术看AI行业发展(2024.6.3-6.16第二十五期) |【WeThinkIn老实人报】

写在前面 【WeThinkIn老实人报】旨在整理&挖掘AI行业的关键新闻和最新技术&#xff0c;同时Rocky会对这些关键信息进行解读&#xff0c;力求让读者们能从容跟随AI科技潮流。也欢迎大家提出宝贵的优化建议&#xff0c;一起交流学习&#x1f4aa; 欢迎大家关注Rocky的公众号&…

一键智能整理TXT文档,高效删除连续行,轻松提升工作效率与数据管理效能

信息爆炸的时代&#xff0c;TXT文档作为我们日常工作中不可或缺的一部分&#xff0c;承载着大量的数据和信息。然而&#xff0c;随着文档内容的不断增加&#xff0c;连续重复的行数也逐渐增多&#xff0c;这不仅影响了文档的整洁度&#xff0c;还大大降低了我们处理数据的效率。…

idea添加文档注释

一、easy javadoc插件 在settings的plugins中下载easy javadoc插件。 安装完成后重启idea&#xff0c;再次打开settings界面。会出现easyDoc相关配置。 二、设置模版以及使用 类描述模版参考设置&#xff1a; /** * 类描述 -> * * Author: ywz * Date: $Date$ */ 方法描述…

Vue80-全局路由守卫:前置、后置

一、路由守卫的定义 二、需求 在第三步&#xff0c;做校验&#xff01; 三、代码实现 3-1、前置路由守卫 注意&#xff0c;此时就不能将router一开始就暴露出去了&#xff01; to和from是路由组件的信息。 写法一&#xff1a; 写法二&#xff1a; 缺点&#xff1a;若是路由…

马尔可夫聚类算法

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

Python17 多进程multiprocessing

1.多进程与多线程的区别 在Python中&#xff0c;多线程&#xff08;multithreading&#xff09;和多进程&#xff08;multiprocessing&#xff09;是两种并行执行任务的方式&#xff0c;它们有一些关键的区别&#xff1a; 进程和线程的基本区别&#xff1a; 进程&#xff1a;进…

【ajax基础】回调函数地狱

一&#xff1a;什么是回调函数地狱 在一个回调函数中嵌套另一个回调函数&#xff08;甚至一直嵌套下去&#xff09;&#xff0c;形成回调函数地狱 回调函数地狱存在问题&#xff1a; 可读性差异常捕获严重耦合性严重 // 1. 获取默认第一个省份的名字axios({url: http://hmaj…

第一次接触Swing

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

Java程序之百鸡百钱问题

题目&#xff1a; 百钱买百鸡的问题算是一套非常经典的不定方程的问题&#xff0c;题目很简单&#xff1a;公鸡5文钱一只&#xff0c;母鸡3文钱一只&#xff0c;小鸡3只一文钱&#xff0c;用100文钱买一百只鸡,其中公鸡&#xff0c;母鸡&#xff0c;小鸡都必须要有&#xff0c;…

JWT介绍及其基本使用

JWT介绍及其基本使用 官网&#xff1a;https://jwt.io/ 什么是JWT 全称&#xff1a;JSON Web Token&#xff08;JSON Web令牌&#xff09; 一个开放标准(RFC 7519) &#xff0c;它定义了一种紧凑和自包含的方式&#xff0c; 用于作为 JSON 对象在各方之间安全地传输信息。此信…

Day 30:100344. 使二进制数组全部等于1的最小操作次数Ⅰ

Leetcode 100344. 使二进制数组全部等于1的最小操作次数Ⅰ 给你一个二进制数组 nums 。 你可以对数组执行以下操作 任意 次&#xff08;也可以 0 次&#xff09;&#xff1a; 选择数组中 任意连续 3 个元素&#xff0c;并将它们 全部反转 。 反转 一个元素指的是将它的值从 0 变…

云资源管理系统-项目部署

云资源管理系统-项目部署 大家好&#xff0c;我是秋意零。 今天分享个人项目同时也是个人毕设项目&#xff0c;云平台资源管理系统。该系统具备对OpenStack最基本资源的生命周期管理&#xff0c;如&#xff1a;云主机、云盘、镜像、网络。 该篇主要介绍&#xff0c;项目在Li…

idea2022激活

下载激活脚本 解压后&#xff0c;打开文件夹如下&#xff1a;ja-netfilter.jar 为激活补丁&#xff1a; 复制补丁所在的整个文件夹到硬盘某个位置 将 ja-netfilter补丁所在的整个文件夹移动到电脑上某个位置&#xff0c;我是放到了 D 盘下&#xff1a; &#xff08;路径中不…