You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python+Matplotlib正确绘制O(log n)时间复杂度曲线?

问题描述

我正在开展一个项目,使用Python和matplotlib绘制不同算法的运行时间随输入规模变化的曲线,但在绘制O(log n)时间复杂度算法的曲线时遇到困难。

第一版代码

import time
import numpy as np
import matplotlib.pyplot as plt

def run_algo(n, func):
    start_time = time.time()  # current time
    func(n)
    end_time = time.time()
    return end_time - start_time

def linear(n):
    for i in range(n):
        i

def quadratic(n):
    for i in range(n):
        for j in range(n):
            j

def quadratic2(n):
    for i in range(n): 
        for j in range(i): 
            j

def cubic(n):
    for i in range(n): 
        for j in range(n): 
            for k in range(n): 
                k

def log_n(n):
    low = 1
    high = n
    while low <= high:
        mid = (low + high) // 2
        if mid * mid == n:
            return mid
        elif mid * mid < n:
            low = mid + 1
        else:
            high = mid - 1
    
    return -1

numberOfPoints = [60, 60, 60, 60, 1000]
min_value = [500, 50, 50, 10, 10**50]
max_value = [100000, 1000, 1000, 100, 10**101]
list_of_functions_to_run = [linear, quadratic, quadratic2, cubic, log_n]
titles = ["O(n)", "O(n^2)", "O(n^2)", "O(n^3)", "O(log n)"]
degrees = [1, 2, 2, 3, 1]

for i, func in enumerate(list_of_functions_to_run):
    time_complexity = []
    n_values = np.linspace(min_value[i], max_value[i], numberOfPoints[i])
    for n in n_values:
        time_complexity.append(run_algo(int(n), func))
    # Smooth out the time complexity curve using a moving average
    window_size = 4
    moving_average = np.convolve(time_complexity, np.ones(window_size)/window_size, mode='valid')
    # Plot the time complexity curve and the smoothed curve
    plt.figure()
    plt.plot(n_values[len(n_values)-len(moving_average):], moving_average, label='Smoothed')
    # Find a polynomial fit 
    x_values_for_fit = n_values[len(n_values)-len(moving_average):]
    label='Polynomial Fit'
    if titles[i] == "O(log n)":
        x_values_for_fit = np.log(x_values_for_fit)
        label='Logarithmic Fit'
    polyfit = np.polyfit(x_values_for_fit, moving_average, deg=degrees[i])
    polyfit_fn = np.poly1d(polyfit)
    plt.plot(n_values, polyfit_fn(n_values), label=label,color='tab:blue', linestyle='--')
    # Set the title and axis labels
    plt.title(f"Time Complexity: {titles[i]}")
    plt.xlabel("Input Size (n)")
    plt.ylabel("Time Complexity")
    plt.legend()

# Show the plot
plt.show()

代码运行无报错,但O(log n)的曲线不符合预期,呈现为一条水平线,其余复杂度的曲线均正常。多次调整n值数量、最大/最小n值,问题未解决。

更新版代码(改用scipy.curve_fit)

import time
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit

def run_algo(n, func):
    start_time = time.time()  # current time
    func(n)
    end_time = time.time()
    return end_time - start_time

def linear(n):
    for i in range(n):
        i

def quadratic(n):
    for i in range(n):
        for j in range(n):
            j

def quadratic2(n):
    for i in range(n): 
        for j in range(i): 
            j

def cubic(n):
    for i in range(n): 
        for j in range(n): 
            for k in range(n): 
                k

def log_n(n):
    low = 1
    high = n
    while low <= high:
        mid = (low + high) // 2
        if mid * mid == n:
            return mid
        elif mid * mid < n:
            low = mid + 1
        else:
            high = mid - 1
    
    return -1
    
def log_function(x, a, b):
    return a * np.log(x) + b

def polynom_function(x, a, b, n):
    return a *x**n + b

def linear_func(x, a, b):
    return polynom_function(x, a, b,1)

def quadratic_func(x, a, b):
    return polynom_function(x, a, b,2)

def cubic_func(x, a, b):
    return polynom_function(x, a, b,3)


numberOfPoints = [60, 60, 60, 60, 6000]
min_value = [500, 50, 50, 10, 2]
max_value = [100000, 1000, 1000, 100, 100]
list_of_functions_to_run = [linear, quadratic, quadratic2, cubic, log_n]
list_of_functions_to_fit = [linear_func, quadratic_func, quadratic_func, cubic_func, log_function]
titles = ["O(n)", "O(n^2)", "O(n^2)", "O(n^3)", "O(log n)"]

for i, func in enumerate(list_of_functions_to_run):
    time_complexity = []
    n_values = np.linspace(min_value[i], max_value[i], numberOfPoints[i])
    for n in n_values:
        time_complexity.append(run_algo(int(n), func))
    # Smooth out the time complexity curve using a moving average
    window_size = 4
    moving_average = np.convolve(time_complexity, np.ones(window_size)/window_size, mode='valid')
    # Plot the time complexity curve and the smoothed curve
    plt.figure()
    plt.plot(n_values[len(n_values)-len(moving_average):], moving_average, label='Smoothed')
    # Find a curve fit 
    x_values_for_fit = n_values[len(n_values)-len(moving_average):]
    y_values_for_fit = moving_average
    func_to_fit = list_of_functions_to_fit[i]
    popt, pcov = curve_fit(func_to_fit, x_values_for_fit, y_values_for_fit)
    y_fit = func_to_fit(x_values_for_fit, *popt)
    plt.plot(x_values_for_fit, y_fit, label='fit: a=%f, b=%f' % tuple(popt), color='tab:blue', linestyle='--')
    # Set the title and axis labels
    plt.title(f"Time Complexity: {titles[i]}")
    plt.xlabel("Input Size (n)")
    plt.ylabel("Time Complexity")
    plt.legend()

# Show the plot
plt.show()

但曲线仍出现异常尖峰。

请问如何解决该问题?如何在Python中使用matplotlib正确绘制O(log n)时间复杂度曲线?


解决方案

1. 核心问题分析

O(log n)算法的运行时间变化极小,远低于time.time()的毫秒级精度,导致测量结果噪声极大,要么是0要么是随机小值,最终曲线呈现水平线或尖峰。同时原测试的输入规模范围太小,log n的变化幅度被进一步压缩;二分查找函数的提前返回也会导致运行时间不严格对应log n复杂度。

2. 针对性改进措施

(1) 提升计时精度并增加重复测试

改用精度更高的time.perf_counter(),同时对同一n值重复运行多次算法取平均,大幅降低噪声:

def run_algo(n, func, repeat=1000):
    total_time = 0
    for _ in range(repeat):
        start = time.perf_counter()
        func(n)
        end = time.perf_counter()
        total_time += end - start
    return total_time / repeat

可根据n的大小动态调整repeat值:n小时重复10^5次,n大时重复100次,平衡测试精度和耗时。

(2) 扩展输入规模并使用对数间隔采样

log n增长极慢,需要覆盖足够大的n范围才能看出变化。用对数间隔生成n值,让小n和大n都有足够采样点:

# 生成从10到10^7的对数间隔n值,共100个点
n_values = np.logspace(1, 7, 100, dtype=int)

(3) 修正二分查找函数的提前返回问题

强制完成所有二分迭代,确保运行时间严格对应log n复杂度:

def log_n(n):
    low = 1
    high = n
    # 不提前返回,完成所有二分步骤
    while low <= high:
        mid = (low + high) // 2
        if mid * mid < n:
            low = mid + 1
        else:
            high = mid - 1
    return -1

(4) 优化平滑与拟合逻辑

  • 增大移动平均窗口尺寸(比如设为10)进一步平滑噪声;
  • 拟合时使用底数为2的对数(二分查找实际是log₂n),让拟合更准确:
def log2_function(x, a, b):
    return a * np.log2(x) + b

3. 完整修正代码

import time
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit

def run_algo(n, func, repeat=1000):
    total_time = 0
    for _ in range(repeat):
        start = time.perf_counter()
        func(n)
        end = time.perf_counter()
        total_time += end - start
    return total_time / repeat

def linear(n):
    for i in range(n):
        pass

def quadratic(n):
    for i in range(n):
        for j in range(n):
            pass

def quadratic2(n):
    for i in range(n): 
        for j in range(i): 
            pass

def cubic(n):
    for i in range(n): 
        for j in range(n): 
            for k in range(n): 
                pass

def log_n(n):
    low = 1
    high = n
    while low <= high:
        mid = (low + high) // 2
        if mid * mid < n:
            low = mid + 1
        else:
            high = mid - 1
    return -1
    
def log2_function(x, a, b):
    return a * np.log2(x) + b

def linear_func(x, a, b):
    return a * x + b

def quadratic_func(x, a, b):
    return a * x**2 + b

def cubic_func(x, a, b):
    return a * x**3 + b

# 各算法配置参数
algo_configs = [
    {"func": linear, "fit_func": linear_func, "title": "O(n)", "n_gen": lambda: np.linspace(100, 10000, 50, dtype=int), "repeat": 100},
    {"func": quadratic, "fit_func": quadratic_func, "title": "O(n^2)", "n_gen": lambda: np.linspace(10, 200, 50, dtype=int), "repeat": 10},
    {"func": quadratic2, "fit_func": quadratic_func, "title": "O(n^2) (triangular)", "n_gen": lambda: np.linspace(10, 200, 50, dtype=int), "repeat": 10},
    {"func": cubic, "fit_func": cubic_func, "title": "O(n^3)", "n_gen": lambda: np.linspace(5, 50, 50, dtype=int), "repeat": 2},
    {"func": log_n, "fit_func": log2_function, "title": "O(log n)", "n_gen": lambda: np.logspace(1, 7, 100, dtype=int), "repeat": 10000}
]

for config in algo_configs:
    func = config["func"]
    fit_func = config["fit_func"]
    title = config["title"]
    n_values = config["n_gen"]()
    repeat = config["repeat"]
    
    time_complexity = []
    for n in n_values:
        time_complexity.append(run_algo(n, func, repeat))
    
    # 平滑处理
    window_size = 5
    moving_average = np.convolve(time_complexity, np.ones(window_size)/window_size, mode='valid')
    x_smooth = n_values[window_size//2 : -window_size//2 +1]
    
    plt.figure(figsize=(8,5))
    plt.plot(x_smooth, moving_average, label='Smoothed')
    
    # 曲线拟合
    popt, pcov = curve_fit(fit_func, x_smooth, moving_average)
    fit_label = f'Fit: {popt[0]:.2e}*'
    if title == "O(log n)":
        fit_label += 'log2(n)'
    else:
        fit_label += f'n^{title.split("^")[-1][:-1]}'
    fit_label += f' + {popt[1]:.2e}'
    
    plt.plot(x_smooth, fit_func(x_smooth, *popt), label=fit_label, color='tab:blue', linestyle='--')
    
    plt.title(f"Time Complexity: {title}")
    plt.xlabel("Input Size (n)")
    plt.ylabel("Average Time (seconds)")
    plt.legend()
    # log n图设置x轴为对数刻度,更直观展示趋势
    if title == "O(log n)":
        plt.xscale("log")

plt.show()

4. 效果说明

  • 高重复次数+高精度计时有效降低了log n算法的时间测量噪声;
  • 对数间隔的n值让曲线变化更明显;
  • 强制完成二分迭代确保运行时间与log n严格对应;
  • 对数x轴可清晰展示log n的缓慢增长趋势。

内容的提问来源于stack exchange,提问作者DannyBoy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 02:30:04