《算法修行_引章:三问悟道》

228 阅读6分钟

一问:什么是算法?

计算机算法指的是一系列解决问题或完成特定任务的规则和步骤。它是计算机程序中最基本、最关键的组成部分之一。

算法描述了如何将输入转换为期望的输出,包括了处理数据和执行操作的具体步骤。通过使用算法,计算机可以根据给定的输入数据自动执行特定的任务,并生成相应的输出结果。

好的算法应该具备以下几个特点:

  1. 正确性:算法应该能够产生正确的结果。
  2. 效率:算法应该在合理的时间内完成任务,避免不必要的资源浪费。
  3. 简洁性:算法应该简单明了,易于理解和实现。
  4. 健壮性:算法应该能够处理各种输入情况,对于非预期的输入具有较好的容错性。

二问:什么是数据结构?

数据结构指的是组织和存储数据的方式,它涉及到在计算机内存中如何组织和管理数据的方法和技术。数据结构可以帮助我们高效地存储、访问和操作数据。

常见的数据结构有以下几种:

  1. 数组(Array):将相同类型的数据元素按照一定的顺序存储在连续的内存空间中,通过索引来访问元素。

  2. 链表(Linked List):由一系列节点组成,每个节点包含数据和指向下一个节点的指针,可以按照特定规则连接起来。

  3. 栈(Stack):遵循先进后出(LIFO)原则的数据结构,只能在栈顶进行插入和删除操作。

  4. 队列(Queue):遵循先进先出(FIFO)原则的数据结构,只能在队尾插入元素,在队头删除元素。

  5. 树(Tree):由节点和边组成的层级结构,每个节点可以有零个或多个子节点。

  6. 图(Graph):由节点和边组成的非线性数据结构,节点之间可以存在多种关系。

数据结构的选择取决于数据的特性和应用场景。不同的数据结构对于操作和查询的效率有所差异,因此,在设计算法时选择合适的数据结构非常重要。熟悉并正确应用数据结构可以提高程序的性能和效率,并简化问题的解决过程。

三问:利用算法解决的问题?

算法主要用于解决各种问题,其中包括但不限于以下几个方面:

  1. 搜索和查找:在给定的数据集中查找目标元素,如在一个有序数组中查找特定元素或在图中搜索最短路径。
  2. 排序:将一组数据按照特定的顺序进行排列,如升序或降序排列。
  3. 图算法:处理与图相关的问题,如最短路径、最小生成树、网络流等。
  4. 字符串处理:对字符串进行匹配、替换、压缩等操作,如字符串匹配、正则表达式匹配等。
  5. 最优化问题:寻找最优解或接近最优解的算法,如线性规划、动态规划等。

空间复杂度(Space Complexity)和时间复杂度(Time Complexity)是评估算法性能的两个重要指标。

悟道:空间复杂度/时间复杂度

空间复杂度

空间复杂度是用来衡量算法所需要的额外空间或内存资源的量度。它表示算法在执行过程中所占用的额外内存单元数量,通常以字节为单位。

在计算空间复杂度时,我们主要考虑算法执行过程中所使用的额外内存,而不考虑输入数据本身所占用的空间。因此,空间复杂度不包括输入参数的空间占用。

空间复杂度用大O符号(O())表示。常见的空间复杂度有以下几种:

  1. 常数空间复杂度(O(1)):算法所需的额外空间是常量级别的,与输入规模无关。
#include <iostream> 
using namespace std;
void constantSpaceComplexity(int n) { 
    int a = 5; // 常量级别的额外空间,与输入规模无关 
    cout << "常数空间复杂度示例:" << a << endl; 
} 
int main() { 
    int n = 10; 
    constantSpaceComplexity(n); 
    return 0;
    //在这个示例中,无论输入规模是多少,额外的空间使用量都是恒定的,即常量级别的。
}
  1. 线性空间复杂度(O(n)):算法所需的额外空间随着输入规模线性增长。
#include <iostream>
#include <vector>
using namespace std;
void linearSpaceComplexity(int n) {
    vector<int> nums(n); // 空间大小与输入规模 n 线性增长
    for (int i = 0; i < n; i++) {
        nums[i] = i;//新增空间,当输入多少新增多少空间
    }
    cout << "线性空间复杂度示例:";
    for (int num : nums) {
        cout << num << " ";
    }
    std::cout << std::endl;
}

int main() {
    int n = 5;
    linearSpaceComplexity(n);
    return 0;
}
//在函数`linearSpaceComplexity`中,创建了一个名为`nums`的`std::vector<int>`容器,
//其大小与输入规模`n`成线性增长的关系。
//每个`int`元素占用固定的内存空间,因此向量的总内存消耗正比于`n`。因此,`nums`的空间复杂度为 O(n)。
  1. 对数空间复杂度(O(log n)):算法所需的额外空间随着输入规模的对数增长。
#include <iostream>

void logarithmicSpaceComplexity(int n) {
    int i = 1;
    while (i < n) {
        std::cout << i << " ";
        i = i * 3; // 空间大小随着输入规模 n 的对数增长
    }
    std::cout << std::endl;
}

int main() {
    int n = 10;
    logarithmicSpaceComplexity(n);
    return 0;
}
//根据代码逻辑分析,`i` 的取值序列将是 1、3、9、27、81... 
//直到第一个大于等于 `n` 的值。换句话说,循环将执行 log₃(n) 次。也就是说,
//循环迭代的次数随着输入规模 `n` 的对数增长。
//由于在每次迭代中只使用了常数空间,没有额外的数组或数据结构,代码的空间复杂度是 O(log₃(n)),即
//对数级别的空间复杂度。这意味着随着输入规模 `n` 的增大,所需的存储空间将按对数比例增长。
  1. 平方空间复杂度(O(n^2)):算法所需的额外空间随着输入规模的平方增长。
#include <iostream>
#include <vector>

void squareSpaceComplexity(int n) {
    std::vector<std::vector<int>> matrix(n, std::vector<int>(n)); // 空间大小与输入规模 n 的平方增长
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            matrix[i][j] = i * j;
        }
    }
    std::cout << "平方空间复杂度示例:" << std::endl;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            std::cout << matrix[i][j] << " ";
        }
        std::cout << std::endl;
    }
}

int main() {
    int n = 3;
    squareSpaceComplexity(n);
    return 0;
}
//在这个示例中,创建了一个大小为 n × n 的二维整型数组,因此额外的空间使用量与输入规模 n 的平方增长。

其中,常数空间复杂度是空间利用效率最高的情况,而高阶空间复杂度表示空间利用效率较低,可能需要较多的额外内存。 注意:O(n^2)与O(n)中虽然是两个渲染但是都只可以看为常数类似与3+3与循环嵌套3*3的区别简单理解+不会影响复杂度判断

时间复杂度

时间复杂度是算法运行时间与输入规模之间的关系。它用来衡量算法执行所需的时间资源。通常使用大O符号(O)来表示时间复杂度。时间复杂度描述了算法在最坏情况下的运行时间增长速度。

时间复杂度并不是表示具体的执行时间,而是表示算法运行时间随着输入规模增加时的增长趋势。通过分析时间复杂度,可以比较不同算法的效率,并选择最优算法。

常见的时间复杂度有:

  1. 常数时间复杂度(O(1)):
#include <iostream>

using namespace std;

void constantTimeComplexity(int n) {
    int a = 5; // 执行时间与输入规模无关,恒定为常数
    cout << "常数时间复杂度示例:" << a << endl;
}

int main() {
    int n = 100;
    constantTimeComplexity(n);
    return 0;
}
//这里不管输入多少参数只会输出一句
  1. 线性时间复杂度(O(n)):
#include <iostream>

using namespace std;

void linearTimeComplexity(int n) {
    for (int i = 0; i < n; i++) {
        cout << i << " "; // 执行时间与输入规模 n 成正比
    }
    cout << endl;
}

int main() {
    int n = 10;
    linearTimeComplexity(n);
    return 0;
}
//这里的循环次数是提供我提供的n来决定的
  1. 对数时间复杂度(O(log n)):
#include <iostream>

using namespace std;

void logarithmicTimeComplexity(int n) {
    int i = 1;
    while (i < n) {
        cout << i << " ";
        i = i * 2; // 每次乘以2,执行时间与输入规模 n 的对数成正比
    }
    cout << endl;
}

int main() {
    int n = 16;
    logarithmicTimeComplexity(n);
    return 0;
}
//这里循环的次数是通过我们输入n的数量来决定循环的次数
  1. 线性对数时间复杂度(O(n log n)):
#include <iostream>

using namespace std;

void linearLogarithmicTimeComplexity(int n) {
    for (int i = 1; i <= n; i++) {
        int j = 1;
        while (j < n) {
            cout << i * j << " ";
            j = j * 2; // 每次乘以2,执行时间与输入规模 n 的对数成正比
        }
        cout << endl;
    }
}

int main() {
    int n = 4;
    linearLogarithmicTimeComplexity(n);
    return 0;
}
//提供的n与循环次数做对比这里就是循环n/2次循环
  1. 平方时间复杂度(O(n^2)):
#include <iostream>

using namespace std;

void quadraticTimeComplexity(int n) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cout << i * j << " "; // 执行时间与输入规模 n 的平方成正比
        }
        cout << endl;
    }
}

int main() {
    int n = 3;
    quadraticTimeComplexity(n);
    return 0;
}
//循环次数就是我们n的倍数
  1. 指数时间复杂度(O(2^n)):
#include <iostream>

using namespace std;

int exponentialTimeComplexity(int n) {
    if (n <= 0) {
        return 1;
    }
    else {
        return exponentialTimeComplexity(n - 1) + exponentialTimeComplexity(n - 2); // 执行时间呈指数级增长
    }
}

int main() {
    int n = 4;
    int result = exponentialTimeComplexity(n);
    cout << "指数时间复杂度示例:" << result << endl;
    return 0;
}
//利用递归循环次数为2个地方发生递归的次数的倍数2*n

需要注意的是,时间复杂度只关注算法的增长趋势,忽略了系数、低阶项和常数因子。因此,时间复杂度相同的算法并不一定意味着它们具有相同的实际执行时间。

结语:通过要求选择合适的算法

  1. 算法效率评估:时间复杂度和空间复杂度可以帮助我们评估算法的效率。通过分析算法的时间复杂度,我们可以了解到算法的执行时间随着输入规模的增大而如何变化。通过分析算法的空间复杂度,我们可以了解到算法所需的额外空间随着输入规模的增大而如何变化。这些评估指标可以帮助我们选择更高效的算法,从而提高算法的执行速度和资源利用率。

  2. 算法设计优化:时间复杂度和空间复杂度的分析可以帮助我们发现算法中的性能瓶颈,并指导我们进行算法的改进和优化。通过对时间复杂度的分析,我们可以找到算法中的耗时操作或者冗余计算,并尝试优化它们,以减少算法的执行时间。通过对空间复杂度的分析,我们可以找到算法中占用较多内存的数据结构或临时变量,并尝试优化它们的使用方式,以减少算法的内存消耗。

  3. 算法选择与权衡:在学习算法时,我们通常会遇到多种解决同一个问题的算法。时间复杂度和空间复杂度可以帮助我们比较和选择不同算法之间的优劣。有时候,某个算法可能在时间上更加高效,而另一个算法可能在空间上更加节省。根据具体的应用场景和需求,我们可以根据时间复杂度和空间复杂度来进行权衡和选择合适的算法。