算法效率的度量(上):时间复杂度中的对数思维

一、大O表示法:只看趋势

大O表示法描述的是算法执行时间随数据规模增长的变化趋势。推导时只需记住:保留最高阶项,去掉所有系数

例如:2N + 3logN + 10 最终记为 O(N)


二、O(log N):折半就是对数

对数复杂度的核心特征只有一个:每次循环或递归,问题的规模都缩小一半(或按固定比例缩小)

2.1 循环折半

复制代码
#include <stdio.h>
/*
 * 功能:演示对数复杂度 O(log N)
 * 关键特征:循环变量 i 每次乘以 2(翻倍)
 * 
 * 推导:设循环执行 x 次后结束
 * 第1次:i = 2
 * 第2次:i = 4
 * 第3次:i = 8
 * ...
 * 第x次:i = 2^x
 * 
 * 结束条件:2^x >= N  =>  x >= log₂N
 * 因此时间复杂度为 O(log N)
 */
void demoLogN(int N) {
    int count = 0;
    int i = 1;
while (i < N) {
    i = i * 2;      // 关键:每次翻倍
    count++;
}
printf("N=%d, 循环次数=%d\n", N, count);
}
int main() {
demoLogN(16);       // N=16, 循环4次 (log₂16 = 4)
demoLogN(1024);     // N=1024, 循环10次
return 0;
}

快速判断 :看到循环变量 i *= 2i /= 2,立刻想到 O(log N)。底数是 2 还是 3 不影响大O表示。

2.2 二分查找

复制代码
#include <stdio.h>
/*
 * 功能:二分查找,在有序数组中查找目标值
 * 时间复杂度:O(log N)
 * 
 * 核心思想:每次比较后,搜索范围缩小一半
 * 无论目标在哪,最多只需 log₂N 次比较
 */
int binarySearch(int arr[], int n, int target) {
    int left = 0;
    int right = n - 1;
while (left <= right) {
    int mid = left + (right - left) / 2;  // 防止溢出的写法
if (arr[mid] == target) {
    return mid;         // 找到目标
}
else if (arr[mid] < target) {
    left = mid + 1;     // 目标在右半部分,范围减半
}
else {
    right = mid - 1;    // 目标在左半部分,范围减半
}
}
return -1;  // 未找到
}
int main() {
int arr[] = {3, 8, 12, 25, 31, 47, 58, 69, 81, 95};
int n = 10;
int result = binarySearch(arr, n, 47);
printf("查找结果下标:%d\n", result);  // 输出 5
return 0;
}

2.3 递归折半

复制代码
#include <stdio.h>
/*
 * 功能:递归方式演示对数复杂度
 * 时间复杂度:O(log N)
 * 
 * 分析:递归深度决定了执行次数
 * 每次递归 N 变为 N/2,深度为 log₂N
 * 每层只执行常数操作(打印、判断)
 */
void recursiveHalve(int N) {
    if (N <= 1) {
        return;             // 递归终止条件
    }
printf("当前 N = %d\n", N);
recursiveHalve(N / 2);  // 关键:每次规模减半
}
int main() {
recursiveHalve(16);     // 会打印 16, 8, 4, 2,共 log₂16 = 4 层
return 0;
}

三、O(N log N):线性遍历套折半操作

O(N log N) 是高效排序算法(如归并排序、堆排序)的典型复杂度。它的结构很固定:外层是 N 次的线性遍历,内层是 O(log N) 的折半操作

复制代码
#include <stdio.h>
/*
 * 功能:演示线性对数复杂度 O(N log N)
 * 
 * 外层循环:i 从 1 到 N,执行 N 次
 * 内层循环:j 每次翻倍,执行 log N 次
 * 总操作次数 = N * log N
 */
void demoNLogN(int N) {
    int total = 0;
for (int i = 1; i <= N; i++) {      // 外层:线性 N 次
    int j = 1;
while (j < N) {                 // 内层:对数 log N 次
    j = j * 2;
    total++;
}
}
printf("N=%d, 总操作次数=%d\n", N, total);
}
int main() {
demoNLogN(8);   // 8 * 3 = 24
demoNLogN(16);  // 16 * 4 = 64
return 0;
}

实际场景 :对 N 个元素分别进行一次二分查找,整体就是 O(N log N)


四、快速判断口诀

表格

代码特征 时间复杂度 判断要点
循环变量每次乘2或除2 O(log N) 折半、翻倍就是对数
外层 N 次 + 内层折半 O(N log N) 线性套对数
递归每次规模减半 O(log N) 递归深度为 log N
递归先遍历 N 再折半递归(两次) O(N log N) 类似归并排序的递归树

五、练习题

题目1

复制代码
void funcA(int N) {
    int i = N;
    while (i > 1) {
        i = i / 3;      // 每次变为原来的 1/3
    }
}

时间复杂度是多少?

题目2

复制代码
void funcB(int N) {
    for (int i = 0; i < N; i++) {
        int j = 1;
        while (j < N) {
            j = j * 2;
        }
    }
}

时间复杂度是多少?

题目3(递归)

复制代码
void funcC(int N) {
    if (N <= 1) return;
    funcC(N / 2);
}

时间复杂度是多少?

题目4(递归)

复制代码
void funcD(int N) {
    if (N <= 1) return;
    for (int i = 0; i < N; i++) {
        printf("%d ", i);
    }
    printf("\n");
    funcD(N / 2);
    funcD(N / 2);
}

时间复杂度是多少?

题目5(双参数)

复制代码
void funcE(int N, int M) {
    for (int i = 0; i < N; i++) printf("1");
    for (int j = 0; j < M; j++) printf("2");
}

时间复杂度是多少?(提示:O(M+N)O(max(M,N)) 在渐进意义下等价,因为 max(M,N) ≤ M+N ≤ 2×max(M,N),两者只相差常数倍。通常写 O(M+N) 更直观。)

答案与解析

表格

题号 时间复杂度 解析
1 O(log N) 循环变量每次除以3,底数不影响大O
2 O(N log N) 外层N次 × 内层log N次
3 O(log N) 递归深度为 log N,每层常数操作
4 O(N log N) 递归树深度为 log N,每层总工作量约为 N(N + N/2 + N/2 = 2N),总复杂度为 O(N log N)
5 O(M+N) 两个独立循环,复杂度相加。也可写作 O(max(M,N))
相关推荐
算AI2 小时前
基于LLM的无人机仿真测试:新方法竞赛夺佳绩
人工智能·深度学习·算法·机器学习·ai
单片机仿真设计3 小时前
【proteus仿真】基于 STM32 的智能垃圾桶设计(仿真图+程序)
c语言·stm32·stm32单片机·proteus·毕设·仿真
kaixin_啊啊3 小时前
专用优化算法LKH
算法
一木 之林3 小时前
四、STL容器与数据结构
开发语言·数据结构·c++
_Narcissus_4 小时前
二分算法笔记及例题
数据结构·c++·笔记·算法·蓝桥杯·查找·二分算法
tachibana24 小时前
RAGAS 指标解读
数据库·人工智能·算法·机器学习·架构·大模型·llm
qq_419563095 小时前
ToT 的 BFS/DFS 有个致命缺口:蒙特卡洛树搜索(MCTS)用「随机试错+统计」让大模型想得更深,小模型 + 它竟超过 GPT-4
算法·深度优先·宽度优先
万法若空6 小时前
CSP-J/S 排序算法完整专题训练题单
数据结构·算法·排序算法
凉茶钱6 小时前
【数据结构】排序(快排,选择,直接插入,希尔)
数据结构·算法·排序算法