• C++
  • C++算法复杂度的估算(含多项式、指数、对数复杂度)教程 通俗易懂 详细 0基础

  • @ 2025-3-13 22:07:43

一、算法复杂度概述

在编写程序时,我们常常需要考虑算法的效率。算法复杂度就是衡量算法效率的一个重要指标,它主要分为时间复杂度和空间复杂度。本教程着重讲解时间复杂度,即算法执行所需要的计算工作量,它反映了算法执行时间随输入规模增长的变化趋势。为了描述算法复杂度,我们通常使用大O表示法。

二、大O表示法

大O表示法用于描述算法复杂度的上限,也就是在最坏情况下算法的复杂度。以下是几种常见的大O表示法及其含义:

  1. O(1)O(1):常数时间复杂度,意味着算法的执行时间不随输入规模的增加而增加。
  2. O(logn)O(\log n):对数时间复杂度,算法的执行时间随输入规模的对数增长。
  3. O(n)O(n):线性时间复杂度,算法的执行时间随输入规模的增加而线性增加。
  4. O(nk)O(n^k)kk 为常数):多项式时间复杂度,常见的如 O(n2)O(n^2)(平方时间复杂度)、O(n3)O(n^3)(立方时间复杂度)等。
  5. O(2n)O(2^n):指数时间复杂度,算法的执行时间随输入规模的指数增加。

三、不同复杂度的算法示例及估算

1. 常数时间复杂度 O(1)O(1)

#include <iostream>

// 交换两个整数的值
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 5, y = 10;
    swap(x, y);
    std::cout << "x: " << x << ", y: " << y << std::endl;
    return 0;
}

复杂度分析:在 swap 函数中,无论输入的 ab 是什么值,函数内部只执行了固定的几次赋值操作,执行时间是固定的,不随输入规模的变化而变化,所以时间复杂度为 O(1)O(1)

2. 对数时间复杂度 O(logn)O(\log n)

#include <iostream>

// 二分查找函数
int binarySearch(int arr[], int left, int right, int target) {
    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[] = {1, 3, 5, 7, 9};
    int n = sizeof(arr) / sizeof(arr[0]);
    int target = 5;
    int result = binarySearch(arr, 0, n - 1, target);
    if (result != -1) {
        std::cout << "元素 " << target << " 在数组中的索引是: " << result << std::endl;
    } else {
        std::cout << "元素 " << target << " 不在数组中。" << std::endl;
    }
    return 0;
}

复杂度分析:二分查找每次将搜索范围缩小一半。假设数组长度为 n,第一次查找后范围变为 n/2,第二次变为 n/4,以此类推,直到找到目标元素或搜索范围为空。设查找次数为 k,则有 n/2k=1n/2^k = 1,解得 k=log2nk = \log_2 n。所以二分查找的时间复杂度为 O(logn)O(\log n)

3. 线性时间复杂度 O(n)O(n)

#include <iostream>

// 计算数组中所有元素的和
int sumArray(int arr[], int n) {
    int sum = 0;
    for (int i = 0; i < n; ++i) {
        sum += arr[i];
    }
    return sum;
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    int result = sumArray(arr, n);
    std::cout << "数组元素的和是: " << result << std::endl;
    return 0;
}

复杂度分析:在 sumArray 函数中,有一个 for 循环,循环的次数等于数组的长度 n。也就是说,随着数组长度的增加,循环执行的次数也会线性增加,所以时间复杂度为 O(n)O(n)

4. 多项式时间复杂度(以 O(n2)O(n^2) 为例)

#include <iostream>

// 冒泡排序函数
void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; ++i) {
        for (int j = 0; j < n - i - 1; ++j) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main() {
    int arr[] = {5, 4, 3, 2, 1};
    int n = sizeof(arr) / sizeof(arr[0]);
    bubbleSort(arr, n);
    std::cout << "排序后的数组: ";
    for (int i = 0; i < n; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
    return 0;
}

复杂度分析:冒泡排序使用了两层嵌套的 for 循环。外层循环执行 n - 1 次,内层循环对于外层循环的每一次执行,执行次数逐渐减少。总的比较次数约为 (n1)+(n2)++1=n(n1)/2(n - 1) + (n - 2) + \cdots + 1 = n(n - 1)/2。当 n 很大时,忽略低阶项和常数系数,时间复杂度为 O(n2)O(n^2)

5. 指数时间复杂度 O(2n)O(2^n)

#include <iostream>

// 递归计算斐波那契数列的第 n 项
int fibonacci(int n) {
    if (n <= 1) {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    int n = 5;
    int result = fibonacci(n);
    std::cout << "斐波那契数列的第 " << n << " 项是: " << result << std::endl;
    return 0;
}

复杂度分析:在 fibonacci 函数中,每次调用都会递归地调用自身两次。可以通过递归树来分析,递归树的节点数近似为 2n2^n。也就是说,随着输入规模 n 的增加,递归调用的次数会呈指数级增长,所以时间复杂度为 O(2n)O(2^n)

四、复杂度估算的注意事项

  1. 只关注最高阶项:在估算复杂度时,我们只关注最高阶项,忽略低阶项和常数项。例如,O(3n2+5n+7)O(3n^2 + 5n + 7) 可以简化为 O(n2)O(n^2)
  2. 最坏情况分析:大O表示法描述的是最坏情况下的复杂度。在实际应用中,算法的实际执行时间可能会比最坏情况要好。

五、总结

通过以上示例,你应该对不同复杂度的算法有了基本的了解。在编写代码时,我们应该尽量选择复杂度较低的算法,以提高程序的执行效率。同时,要注意不同复杂度的算法在处理大规模输入时的性能差异,例如指数复杂度的算法在输入规模较大时可能会变得非常慢。

4 条评论

  • @ 2025-3-13 22:11:48

    主定理(Master Theorem)是用于分析分治算法时间复杂度的一种重要工具。分治算法通常将一个规模为 nn 的问题分解为 aa 个规模为 n/bn/b 的子问题,然后通过合并这些子问题的解来得到原问题的解。主定理能够帮助我们快速确定这类分治算法的时间复杂度。

    1. 主定理的形式

    对于一个满足递归式 T(n)=aT(n/b)+f(n)T(n)=aT(n/b)+f(n) 的分治算法,其中:

    • nn 是问题的规模。
    • a1a\geq1 是子问题的个数,即原问题被分解成的子问题数量。
    • b>1b > 1 是子问题规模的缩小因子,也就是每个子问题的规模是原问题规模的 1/b1/b
    • f(n)f(n) 是将原问题分解为子问题以及合并子问题的解所需要的时间。

    主定理根据 f(n)f(n)nlogban^{\log_b a} 的渐近关系,给出了三种情况来确定 T(n)T(n) 的渐近复杂度:

    情况 1:f(n)=O(nlogbaϵ)f(n)=O(n^{\log_b a - \epsilon}),其中 ϵ>0\epsilon>0

    若存在一个正常数 ϵ\epsilon,使得 f(n)f(n) 的增长速度比 nlogban^{\log_b a} 慢,慢的程度达到 nlogbaϵn^{\log_b a - \epsilon} 这个量级,那么 T(n)=Θ(nlogba)T(n)=\Theta(n^{\log_b a})

    解释:在这种情况下,分解和合并子问题的代价 f(n)f(n) 相对较小,算法的时间复杂度主要由子问题的求解时间决定。

    情况 2:f(n)=Θ(nlogba)f(n)=\Theta(n^{\log_b a})

    如果 f(n)f(n)nlogban^{\log_b a} 具有相同的渐近增长速度,那么 T(n)=Θ(nlogbalogn)T(n)=\Theta(n^{\log_b a}\log n)

    解释:此时分解、合并子问题的代价和子问题的求解代价处于同一量级,递归树每层的代价大致相同,总代价是每层代价乘以递归树的深度 logn\log n

    情况 3:f(n)=Ω(nlogba+ϵ)f(n)=\Omega(n^{\log_b a + \epsilon}),其中 ϵ>0\epsilon>0,并且存在常数 c<1c < 1 和足够大的 nn,使得 af(n/b)cf(n)af(n/b)\leq cf(n)

    f(n)f(n) 的增长速度比 nlogban^{\log_b a} 快,快的程度达到 nlogba+ϵn^{\log_b a + \epsilon} 这个量级,并且满足正则条件 af(n/b)cf(n)af(n/b)\leq cf(n),那么 T(n)=Θ(f(n))T(n)=\Theta(f(n))

    解释:在这种情况下,分解和合并子问题的代价 f(n)f(n) 占据主导地位,算法的时间复杂度主要由分解和合并操作决定。

    2. 主定理应用示例

    示例 1:二分查找

    二分查找将一个规模为 nn 的问题分解为 1 个规模为 n/2n/2 的子问题,分解和比较操作的时间复杂度为 O(1)O(1)。其递归式为 T(n)=T(n/2)+O(1)T(n)=T(n/2)+O(1)

    • 这里 a=1a = 1b=2b = 2,则 nlogba=nlog21=n0=1n^{\log_b a}=n^{\log_2 1}=n^0 = 1
    • f(n)=O(1)f(n)=O(1),满足情况 2,因为 f(n)=Θ(nlogba)f(n)=\Theta(n^{\log_b a})(两者都是常数阶)。
    • 根据主定理,T(n)=Θ(logn)T(n)=\Theta(\log n)

    示例 2:归并排序

    归并排序将一个规模为 nn 的问题分解为 2 个规模为 n/2n/2 的子问题,合并子问题的时间复杂度为 O(n)O(n)。其递归式为 T(n)=2T(n/2)+O(n)T(n)=2T(n/2)+O(n)

    • 这里 a=2a = 2b=2b = 2,则 nlogba=nlog22=nn^{\log_b a}=n^{\log_2 2}=n
    • f(n)=O(n)f(n)=O(n),满足情况 2,因为 f(n)=Θ(nlogba)f(n)=\Theta(n^{\log_b a})
    • 根据主定理,T(n)=Θ(nlogn)T(n)=\Theta(n\log n)

    示例 3:一种特殊的分治算法

    考虑递归式 T(n)=3T(n/2)+n2T(n)=3T(n/2)+n^2

    • 这里 a=3a = 3b=2b = 2,则 nlogba=nlog23n1.58n^{\log_b a}=n^{\log_2 3}\approx n^{1.58}
    • f(n)=n2f(n)=n^2,因为 f(n)=Ω(nlog23+ϵ)f(n)=\Omega(n^{\log_2 3+\epsilon})(例如取 ϵ=0.4\epsilon = 0.4),并且可以验证正则条件(对于足够大的 nn3f(n/2)=3×(n/2)2=34n2cf(n)3f(n/2)=3\times(n/2)^2=\frac{3}{4}n^2\leq cf(n),取 c=0.8c = 0.8 即可),满足情况 3。
    • 根据主定理,T(n)=Θ(n2)T(n)=\Theta(n^2)

    3. 主定理的局限性

    主定理并非适用于所有的分治递归式,它有一定的局限性:

    • f(n)f(n) 的增长速度与 nlogban^{\log_b a} 的关系不满足上述三种情况时,主定理无法直接应用。例如,若 f(n)=nlognf(n)=n\log n,就不能简单地使用主定理来确定复杂度。
    • 对于一些不满足分治算法标准形式的递归式,主定理也不适用。

    总之,主定理是分析分治算法时间复杂度的一个强大工具,但在使用时需要注意其适用条件和局限性。

    • @ 2025-3-13 22:10:27

      在多项式复杂度的算法里,时间复杂度通常用 O(nk)O(n^k) 来表示,其中 nn 是输入规模,kk 是一个大于等于 0 的常数。kk 的取值对算法效率有着重大影响,下面为你详细分析:

      1. 不同 kk 值对应的复杂度情况

      • k=0k = 0:此时复杂度为 O(n0)=O(1)O(n^0)=O(1),属于常数时间复杂度。这意味着算法的执行时间不会随着输入规模 nn 的增加而改变,无论输入数据量有多大,算法都能在固定的时间内完成操作,效率极高。例如,访问数组中某个固定索引位置的元素,无论数组有多长,访问操作的时间都是固定的。
      • k=1k = 1:复杂度为 O(n)O(n),是线性时间复杂度。算法的执行时间与输入规模 nn 呈线性关系,即输入规模增大几倍,算法执行时间也大致增大几倍。比如遍历一个数组并对每个元素进行简单操作,像计算数组元素的总和,随着数组长度的增加,操作时间会线性增长。
      • k=2k = 2:复杂度为 O(n2)O(n^2),是平方时间复杂度。常见于嵌套循环的算法,如冒泡排序、选择排序等。当输入规模 nn 增大时,算法执行时间会以平方的速度增长,效率相对较低。例如,对于一个长度为 nn 的数组进行冒泡排序,其比较和交换操作的次数大致为 n(n1)/2n(n - 1)/2,当 nn 翻倍时,执行时间大约会变为原来的 4 倍。
      • k>2k > 2:随着 kk 值的增大,复杂度会急剧上升。例如 k=3k = 3 时是立方时间复杂度 O(n3)O(n^3),在处理大规模输入时,算法的执行时间会变得非常长,效率会显著降低。像矩阵乘法的朴素算法复杂度就是 O(n3)O(n^3),当矩阵规模变大时,计算所需的时间会迅速增加。

      2. kk 值对算法效率影响的具体体现

      2.1 小规模输入

      当输入规模 nn 较小时,不同 kk 值的多项式复杂度算法在效率上的差异可能并不明显。这是因为在小规模数据下,常数项和低阶项对算法执行时间的影响相对较大,而 nkn^k 的增长趋势还未充分显现出来。例如,对于一个长度为 10 的数组,O(n)O(n)O(n2)O(n^2) 复杂度的算法执行时间可能相差无几。

      2.2 大规模输入

      随着输入规模 nn 的不断增大,kk 值对算法效率的影响会愈发显著。kk 值越大,算法执行时间的增长速度就越快,效率也就越低。以下是不同 kk 值的复杂度函数在 nn 增大时的增长情况对比:

      复杂度 n=10n = 10 n=100n = 100 n=1000n = 1000
      O(n)O(n) 10 100 1000
      O(n2)O(n^2) 100 10000 1000000
      O(n3)O(n^3) 1000 1000000 1000000000

      从这个表格可以看出,当 nn 从 10 增长到 100 再到 1000 时,O(n)O(n) 复杂度的算法执行时间线性增长,而 O(n2)O(n^2)O(n3)O(n^3) 复杂度的算法执行时间增长速度明显加快,尤其是 O(n3)O(n^3) 复杂度的算法,执行时间的增长幅度非常大。

      3. 实际应用中的考虑

      在实际应用中,我们通常希望选择 kk 值较小的多项式复杂度算法,以提高算法的效率。当面对大规模数据处理时,kk 值的微小差异可能会导致算法执行时间的巨大差距。例如,在处理海量数据的排序任务时,应优先选择复杂度为 O(nlogn)O(n \log n) 的快速排序、归并排序等算法,而不是复杂度为 O(n2)O(n^2) 的冒泡排序、选择排序。

      综上所述,kk 的取值直接影响着多项式复杂度算法的效率,在设计和选择算法时,需要充分考虑输入规模和 kk 值对算法性能的影响。

      • @ 2025-3-13 22:09:35

        算法复杂度分析是衡量算法效率的重要手段,主要分为时间复杂度和空间复杂度,这里着重讲解时间复杂度的估算,包含多项式、指数、对数复杂度。下面将详细介绍如何估算这些复杂度,并给出相应的 C++ 代码示例。

        1. 基本概念与大 O 表示法

        时间复杂度描述了算法执行时间随输入规模增长的变化趋势,使用大 O 表示法来表示。大 O 表示法关注的是算法复杂度的上限,即最坏情况下的复杂度,并且忽略常数系数和低阶项。常见的复杂度有:

        • 常数复杂度O(1)O(1)
        • 对数复杂度O(logn)O(\log n)
        • 线性复杂度O(n)O(n)
        • 多项式复杂度O(nk)O(n^k)kk 为大于 1 的整数)
        • 指数复杂度O(an)O(a^n)a>1a > 1

        2. 不同复杂度的估算与示例

        2.1 常数复杂度 O(1)O(1)

        如果算法的执行时间不随输入规模的增加而变化,就是常数复杂度。

        #include <iostream>
        
        // 交换两个整数的值
        void swap(int& a, int& b) {
            int temp = a;
            a = b;
            b = temp;
        }
        
        int main() {
            int x = 10, y = 20;
            swap(x, y);
            std::cout << "x: " << x << ", y: " << y << std::endl;
            return 0;
        }
        

        复杂度分析swap 函数中的操作(赋值、交换)数量是固定的,不随输入规模变化,所以时间复杂度为 O(1)O(1)

        2.2 对数复杂度 O(logn)O(\log n)

        当算法每次迭代将问题规模缩小一个常数因子时,通常具有对数复杂度,常见于二分查找。

        #include <iostream>
        #include <vector>
        
        // 二分查找函数
        int binarySearch(const std::vector<int>& arr, int target) {
            int left = 0, right = arr.size() - 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() {
            std::vector<int> arr = {1, 3, 5, 7, 9, 11, 13};
            int target = 7;
            int result = binarySearch(arr, target);
            if (result != -1) {
                std::cout << "元素 " << target << " 在数组中的索引是: " << result << std::endl;
            } else {
                std::cout << "元素 " << target << " 不在数组中。" << std::endl;
            }
            return 0;
        }
        

        复杂度分析:二分查找每次迭代将搜索范围缩小一半,设输入规模为 nn,经过 kk 次迭代后问题规模变为 1,则有 n/2k=1n/2^k = 1,解得 k=log2nk = \log_2 n,所以时间复杂度为 O(logn)O(\log n)

        2.3 线性复杂度 O(n)O(n)

        如果算法的执行时间与输入规模 nn 成正比,则为线性复杂度。

        #include <iostream>
        #include <vector>
        
        // 计算数组元素的和
        int sum(const std::vector<int>& arr) {
            int total = 0;
            for (int num : arr) {
                total += num;
            }
            return total;
        }
        
        int main() {
            std::vector<int> arr = {1, 2, 3, 4, 5};
            int result = sum(arr);
            std::cout << "数组元素的和是: " << result << std::endl;
            return 0;
        }
        

        复杂度分析sum 函数中的 for 循环会遍历数组中的每个元素一次,执行次数与数组长度 nn 成正比,所以时间复杂度为 O(n)O(n)

        2.4 多项式复杂度 O(nk)O(n^k)

        以平方复杂度 O(n2)O(n^2) 为例,当算法中有嵌套循环,且每个循环的迭代次数都与输入规模 nn 相关时,可能具有平方复杂度。

        #include <iostream>
        #include <vector>
        
        // 冒泡排序函数
        void bubbleSort(std::vector<int>& arr) {
            int n = arr.size();
            for (int i = 0; i < n - 1; ++i) {
                for (int j = 0; j < n - i - 1; ++j) {
                    if (arr[j] > arr[j + 1]) {
                        int temp = arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = temp;
                    }
                }
            }
        }
        
        int main() {
            std::vector<int> arr = {5, 4, 3, 2, 1};
            bubbleSort(arr);
            std::cout << "排序后的数组: ";
            for (int num : arr) {
                std::cout << num << " ";
            }
            std::cout << std::endl;
            return 0;
        }
        

        复杂度分析:冒泡排序有两层嵌套循环,外层循环执行 n1n - 1 次,内层循环对于外层的每次迭代,执行次数逐渐减少,总的比较次数约为 n(n1)2\frac{n(n - 1)}{2},忽略低阶项和常数系数,时间复杂度为 O(n2)O(n^2)

        2.5 指数复杂度 O(an)O(a^n)

        O(2n)O(2^n) 为例,递归计算斐波那契数列是典型的指数复杂度算法。

        #include <iostream>
        
        // 递归计算斐波那契数列
        int fibonacci(int n) {
            if (n <= 1) {
                return n;
            }
            return fibonacci(n - 1) + fibonacci(n - 2);
        }
        
        int main() {
            int n = 6;
            std::cout << "斐波那契数列的第 " << n << " 项是: " << fibonacci(n) << std::endl;
            return 0;
        }
        

        复杂度分析:在计算斐波那契数列时,每次递归调用会产生两个新的递归调用,递归树的节点数随 nn 的增加呈指数级增长,时间复杂度为 O(2n)O(2^n)

        3. 复杂度估算的注意事项

        • 只关注最高阶项:在估算复杂度时,只保留最高阶项,忽略低阶项和常数系数。例如,O(3n2+5n+7)O(3n^2 + 5n + 7) 简化为 O(n2)O(n^2)
        • 最坏情况分析:大 O 表示法描述的是最坏情况下的复杂度,实际执行时间可能优于最坏情况。
        • @ 2025-3-13 22:08:30

          指数复杂度算法的时间复杂度通常表示为 O(2n)O(2^n)O(3n)O(3^n) 等,随着输入规模 nn 的增加,算法的执行时间会呈指数级增长,在处理大规模输入时效率会变得非常低。以下是几个C++中指数复杂度算法的例子。

          1. 递归计算斐波那契数列

          斐波那契数列是指这样一个数列:00111122335588131321213434、…… ,在数学上,斐波那契数列以如下递推的方法定义:F(0)=0F(0)=0F(1)=1F(1)=1, F(n)=F(n1)+F(n2)F(n)=F(n - 1)+F(n - 2)n2n \geq 2nNn \in N*)。

          #include <iostream>
          
          // 递归计算斐波那契数列的第 n 项
          int fibonacci(int n) {
              if (n <= 1) {
                  return n;
              }
              return fibonacci(n - 1) + fibonacci(n - 2);
          }
          
          int main() {
              int n = 6;
              std::cout << "斐波那契数列的第 " << n << " 项是: " << fibonacci(n) << std::endl;
              return 0;
          }
          

          复杂度分析: 该算法的时间复杂度为 O(2n)O(2^n)。可以通过递归树来分析,每次递归调用会产生两个新的递归调用,递归树的节点数随着 nn 的增加呈指数级增长。例如,当计算 F(n)F(n) 时,需要计算 F(n1)F(n - 1)F(n2)F(n - 2),而计算 F(n1)F(n - 1) 又需要计算 F(n2)F(n - 2)F(n3)F(n - 3),以此类推,会产生大量的重复计算。

          2. 子集生成问题

          给定一个包含 nn 个元素的集合,生成该集合的所有子集。

          #include <iostream>
          #include <vector>
          
          // 生成集合的所有子集
          void generateSubsets(const std::vector<int>& set, int index, std::vector<int>& current, std::vector<std::vector<int>>& subsets) {
              if (index == set.size()) {
                  subsets.push_back(current);
                  return;
              }
          
              // 不选择当前元素
              generateSubsets(set, index + 1, current, subsets);
          
              // 选择当前元素
              current.push_back(set[index]);
              generateSubsets(set, index + 1, current, subsets);
              current.pop_back();
          }
          
          int main() {
              std::vector<int> set = {1, 2, 3};
              std::vector<std::vector<int>> subsets;
              std::vector<int> current;
          
              generateSubsets(set, 0, current, subsets);
          
              // 输出所有子集
              for (const auto& subset : subsets) {
                  std::cout << "{ ";
                  for (int element : subset) {
                      std::cout << element << " ";
                  }
                  std::cout << "}" << std::endl;
              }
          
              return 0;
          }
          

          复杂度分析: 对于一个包含 nn 个元素的集合,每个元素都有两种选择:要么在子集中,要么不在子集中。因此,总的子集数量为 2n2^n。该算法通过递归的方式生成所有子集,时间复杂度为 O(2n)O(2^n)

          3. 旅行商问题(TSP)的暴力解法

          旅行商问题是指给定一系列城市和每对城市之间的距离,求解访问每一座城市一次并回到起始城市的最短回路。暴力解法是枚举所有可能的城市排列,计算每种排列的总距离,然后找出最短的距离。

          #include <iostream>
          #include <vector>
          #include <algorithm>
          #include <climits>
          
          // 计算路径的总距离
          int calculateDistance(const std::vector<std::vector<int>>& graph, const std::vector<int>& path) {
              int distance = 0;
              int n = path.size();
              for (int i = 0; i < n - 1; ++i) {
                  distance += graph[path[i]][path[i + 1]];
              }
              distance += graph[path[n - 1]][path[0]]; // 回到起始城市
              return distance;
          }
          
          // 暴力求解旅行商问题
          int tsp(const std::vector<std::vector<int>>& graph) {
              int n = graph.size();
              std::vector<int> path;
              for (int i = 0; i < n; ++i) {
                  path.push_back(i);
              }
          
              int minDistance = INT_MAX;
              do {
                  int distance = calculateDistance(graph, path);
                  minDistance = std::min(minDistance, distance);
              } while (std::next_permutation(path.begin() + 1, path.end()));
          
              return minDistance;
          }
          
          int main() {
              std::vector<std::vector<int>> graph = {
                  {0, 10, 15, 20},
                  {10, 0, 35, 25},
                  {15, 35, 0, 30},
                  {20, 25, 30, 0}
              };
          
              int minDistance = tsp(graph);
              std::cout << "最短路径的距离是: " << minDistance << std::endl;
          
              return 0;
          }
          

          复杂度分析nn 个城市的全排列数量为 n!n!,在暴力求解旅行商问题时,需要枚举所有可能的排列并计算每种排列的总距离,因此时间复杂度为 O(n!)O(n!),这是一种指数复杂度,因为 n!n! 的增长速度比 2n2^n 还要快。

          这些指数复杂度的算法在小规模输入时可能表现良好,但随着输入规模的增加,算法的执行时间会急剧增长,因此在实际应用中,通常需要寻找更高效的算法来解决这些问题。

          • 1