调皮的宏

《编程珠玑》第九章一道题目:
n是数组最大尺寸的正整数,下面的递归C函数返回数组x[0...n-1]中的最大值:
float arrmax(int n){
    if(n==1){
        return x[0];
    }else{
        return max(x[n-1],arrmax(n-1));
    }
}
 

max是一个宏:
#define max(a,b) ((a)>(b)?(a):(b))
分析这个函数的时间复杂度。

初看到这个这个题目时我觉得这个时间复杂度很简单,就是O(n)。但答案提示输入x是降序排列时,时间是O(2**n)。我实际运行了这个函数。
n=10,s=0.469s
n=15,s=6.266s
n=20,s=172.219s
很夸张的时间数据,绝对不是线性增长方式。我觉得这个递归函数的调用方式不就是:
arrmax(n)
arrmax(n-1)
...
arrmax(0)
很明显是线性的。我甚至怀疑我是不是对递归函数理解不对啊。我就另外写了一个简单递归函数:
int cumulate(n){
    printf("n=%d\n",n);
    if(n==0) return 2;
    return cumulate(n-1)+2;
}
测试后,确实是线性调用。在比较那个函数,我决定把arrmax提出来:
float arrmax(int n){
    if(n==1){
        return x[0];
    }else{
        int tmp = arrmax(n-1);
        return max(x[n-1],tmp);
    }
}
突然醒悟了,宏要展开啊!!!
float arrmax(int n){
    if(n==1){
        return x[0];
    }else{
 
        return x[n-1]>arrmax(n-1) ? x[n-1]:arrmax(n-1);
    }
}
如果x是降序,时间复杂度有O(n)=2*O(n-1),所以时间复杂度是O(2**n)。折磨你的宏!如果不使用宏下面的写法就是线性的。
float arrmax(int n){
    if(n==1){
        return x[0];
    }else{
        int tmp = arrmax(n-1);
        return max(x[n-1],tmp);
    }
}

虽然我不是很聪明,拿到问题就能解决。但是我想方法、花时间也搞懂了这个问题,也不错。一点感受:遇到问题怎么也想不通时,不要死想,拼命想不能解决问题(这个想法是我上学的想法),遇到问题要想办法、想途径、多动手,先放一放、换个思路的好处:一是让自己大脑休息一下;二是在尝试别的方法、途径过程中会得到启发。像这个题,如果我还是拼命想,我可能短时间还不会注意到宏,很容易跟自己较劲:这应该就是线性的,怎么会是幂增长。情绪上就会着急,影响思路的展开。我就写了个简单的递归函数,体会体会,也让自己平静,于是醒悟了,弄懂了题目。感觉问题不用怕,解决问题的过程可能会曲折,但整个过程还是有趣的。
最近跟老板交流,说不管你做什么,除了完成日常工作,都要去理解事情背后的methodology。每件小事要认真做,同时要跳出来看看这些事件背后的一些共用的方法。这样会在职业发展上走的更远些。这让我思考最近工作的一件小事:系统的一个feature有很多配置文件,不同的系统使用不同的配置文件,需要找出一个系统使用的配置文件。这是个小问题,但也至少包含了两个方法:一是从源代码处查找;二是从这个系统上这个feature的信息查找。如果把源代码理解为产品的一端,那第一个方法是从源头查找;把安装的系统理解为另一端,那第二个方法是从尽头查找。从两个相反的方法去寻找答案。想起看过的堆算法,在初始化堆时,就有两种方法:一是从第一个元素开始构建;另一个是从最后一个元素开始构建,后一个也是我们现在常用的。找使用的配置文件是小事,改进算法是大事,但是不论大小,两件事中都包含了相同的思考方法。


另外最近解决一个C语言的小bug。

最近部门把产品的代码merge到另一个平台上。merge之后测试过程中发现一个进程执行一个操作就死机。首先检查日志系统,没有发现特别的信息。然后用gdb设置断点来检查,最后定位到一个函数,进程崩溃时,backtrace显示参数指针为null。设置断点后,打印参数,是正确的,但进程崩溃时却是null,有点纳闷。进程每次都在同一个地方发生崩溃。这个函数是一个老的函数,我就从崩溃点往前阅读。发现了一段代码类似下面的初始化代码:
 char a[100]
 memset(a,0,strlen(a));

 用strlen(a)计算数组长度显然不对,strlen会从数组开始一直计算到'\0'为止,strlen(a)的值不确定的,可能大于100,可能小于100,也许碰巧是100。 我特意设断点,果然strlen(a)=176,memset初始化后面76个字节时,把参数所在的字节也写为'\0',所以参数就变为了null。应该是用sizeof(a)/sizeof(char)来计算数组长度。
### Spring Boot 整合 PageHelper 的配置及常见问题解决 #### 1. 导入分页插件依赖 为了实现分页功能,需要在项目的 `pom.xml` 文件中引入 PageHelper 的 Maven 依赖。以下是具体的依赖配置: ```xml <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.6</version> </dependency> ``` 上述依赖适用于大多数场景下的分页需求[^1]。 --- #### 2. 配置参数 在 `application.yml` 或 `application.properties` 中完成 PageHelper 插件的相关配置。以下是一些常用的配置项及其含义: ```yaml pagehelper: helperDialect: mysql # 数据库方言,需根据实际使用的数据库调整 reasonable: true # 是否合理化分页,默认 false supportMethodsArguments: true # 支持通过 Mapper 接口方法参数指定分页查询参数 params: count=countSql # 设置 SQL 参数 ``` 这些配置能够满足大部分项目中的分页需求。 --- #### 3. 常见问题及解决方案 ##### (1) **版本不兼容** 如果使用的是较新的 Spring Boot 和 MyBatis 版本,则需要注意 PageHelper 的版本与其兼容性。例如,在某些情况下,低版本的 PageHelper 可能无法正常工作于高版本的 MyBatis 上。建议按照官方文档推荐的组合进行选择[^4]。 **解决办法**: 使用经过验证的稳定版本组合,如下所示: - Spring Boot: 2.x.x - MyBatis: 3.x.x - PageHelper: 5.x.x ##### (2) **线程安全问题** PageHelper 默认不是完全线程安全的,尤其是在多线程环境下可能会引发数据混乱等问题。可以通过设置全局变量或者每次调用前重新初始化的方式规避此风险[^3]。 **代码示例**: ```java // 局部开启分页(推荐) PageHelper.startPage(1, 10); List<User> users = userMapper.selectAll(); ``` ##### (3) **SQL 查询性能优化不足** 当分页涉及大量数据时,可能会影响查询效率。此时应考虑对底层 SQL 进行优化,比如索引设计、减少不必要的字段返回等操作。 **优化策略**: - 添加合适的索引来加速排序和过滤条件。 - 尽量只获取当前页面所需的数据列而非全部字段。 ##### (4) **跨服务调用导致分页失效** 在分布式架构下,若分页逻辑被封装到远程接口内部,则外部调用方可能看不到分页效果。这是因为 PageHelper 是基于本地线程绑定机制工作的。 **应对措施**: 将分页逻辑移至具体的服务层实现,并确保其作用范围仅限于单次请求周期内。 --- #### 4. 完整代码示例 下面展示了一个简单的分页查询案例,假设我们有一个 User 表并希望按每页显示 10 条记录的形式读取数据。 ```java @Service public class UserService { @Autowired private UserMapper userMapper; public PageInfo<User> getUsers(int pageNum, int pageSize) { // 开启分页 PageHelper.startPage(pageNum, pageSize); // 执行查询 List<User> userList = userMapper.selectAll(); // 返回分页结果对象 return new PageInfo<>(userList); } } ``` 以上代码片段展示了如何利用 PageHelper 结合 MyBatis 实现基本的分页功能[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值