【左程云算法03】对数器&算法和数据结构大致分类

目录

对数器的实现

代码实现与解析

[1. 随机样本生成器 (randomArray)](#1. 随机样本生成器 (randomArray))

[2. 核心驱动逻辑 (main 方法)](#2. 核心驱动逻辑 (main 方法))

[3. 辅助函数 (copyArray 和 sameArray)](#3. 辅助函数 (copyArray 和 sameArray))

对数器的威力

算法和数据结构简介​编辑

[1. 硬计算类算法 (Hard Computing)](#1. 硬计算类算法 (Hard Computing))

[2. 软计算类算法 (Soft Computing)](#2. 软计算类算法 (Soft Computing))

核心观点

一个宏观的数据结构分类

[1. 连续结构 (Continuous Structure)](#1. 连续结构 (Continuous Structure))

[2. 跳转结构 (Jumping Structure)](#2. 跳转结构 (Jumping Structure))


视频链接

算法讲解005【入门】对数器-验证的重要手段

算法讲解008【入门】算法和数据结构简介

对数器的实现

对数器的本质,就是通过大规模的随机样本测试,来对比我们实现的"精巧算法"和一个"绝对正确"的暴力方法,从而验证我们算法的正确性。

构建一个对数器的完整流程分为以下六步:

  1. 你要有一个想测的方法 a(最优解)

    这是我们自己实现的、希望验证其正确性的算法。

  2. 实现一个复杂度不好但是容易实现的方法 b(暴力解)

    这个方法是我们的"真理标杆"。我们不要求它性能好,但必须保证它的逻辑是绝对正确的。通常,我们会用最直观、最暴力的方式来实现它。

  3. 实现一个随机样本产生器

    我们需要一个函数,能够生成各种随机情况的测试数据(例如,长度随机、值也随机的数组)。

  4. 把方法 a 和方法 b 跑相同的输入样本,看看得到的结果是否一样

    这是对数器的核心对比环节。对于同一个随机样本,分别用方法 a 和方法 b 去处理。

  5. 如果有某个随机样本使得比对结果不一致,打印出这个出错的样本进行人工干预,改对方法 a 和方法 b

    一旦发现不一致,就意味着我们的方法 a 存在 bug。对数器会自动捕获这个导致错误的"反例",我们就可以用这个具体的样本去轻松地进行 debug。

  6. 当样本数量很多时比对依然正确,可以确定方法 a(最优解)已经正确。

    经过成千上万,甚至几十万次随机样本的"轰炸"都未曾出错,我们就可以非常有信心地认为,我们的算法 a 是正确的。

代码实现与解析

下面,我们结合截图中的 Java 代码,来一步步拆解对数器的具体实现。这里的例子是用来同时验证我们写的多个排序算法是否正确。

1. 随机样本生成器 (randomArray)

负责源源不断地提供测试数据。

java 复制代码
// 得到一个随机数组,长度是n
// 数组中每个数,都在1~v之间,随机得到
public static int[] randomArray(int n, int v) {
    int[] arr = new int[n];
    for (int i = 0; i < n; i++) {
        // Math.random() -> double -> [0,1)的一个小数
        // Math.random() * v -> double -> [0,v)的一个小数
        // (int)(Math.random() * v) -> int -> 0 1 2 3 ... v-1,等概率的!
        // (int)(Math.random() * v) + 1 -> int -> 1 2 3 .... v,等概率的!
        arr[i] = (int) (Math.random() * v) + 1;
    }
    return arr;
}

这个函数接收最大长度 n 和最大值 v,生成一个长度和值都在指定范围内的随机数组。注释清晰地解释了 Math.random() 如何通过一系列变换,生成我们需要的 [1, v] 范围内的随机整数。

2. 核心驱动逻辑 (main 方法)

这里是对数器的"发动机",它组织了整个测试流程。

java 复制代码
// 为了验证
public static void main(String[] args) {
    // 随机数组最大长度
    int N = 100;
    // 随机数组每个值,在1~V之间随机
    int V = 1000;
    // 测试次数
    int testTimes = 50000;
    System.out.println("测试开始");
    for (int i = 0; i < testTimes; i++) {
        // 随机得到一个长度,长度在[0~N-1]
        int n = (int) (Math.random() * N);
        // 得到随机数组
        int[] arr = randomArray(n, V);
        // 拷贝数组,确保每个排序算法处理的是相同的原始数据
        int[] arr1 = copyArray(arr);
        int[] arr2 = copyArray(arr);
        int[] arr3 = copyArray(arr);
        
        // 分别用不同方法排序
        selectionSort(arr1);
        bubbleSort(arr2);
        insertionSort(arr3);
        
        // 对比排序结果
        if (!sameArray(arr1, arr2) || !sameArray(arr1, arr3)) {
            System.out.println("出错了!");
            // 当出错时,可以把原始样本arr打印出来,
            // 方便把这个例子带入到每个方法去debug!
        }
    }
    System.out.println("测试结束");
}

关键点解析

在后续的很多题目中,对数器都会是我们验证复杂算法(如动态规划、贪心等)的得力助手。

算法和数据结构简介

  • 循环测试:通过 testTimes 控制测试次数,量级越大,覆盖的样本情况就越全面,结果越可靠。

  • 拷贝数组 :copyArray(arr) 这一步至关重要 !因为排序是原地操作,会修改原数组。如果不拷贝,那么第一个排序(selectionSort)完成后,arr2 和 arr3 拿到的将是一个有序数组,测试就失去了意义。必须保证每个待测算法都工作在完全相同的、未经修改的原始样本上。

  • 结果比对:if (!sameArray(arr1, arr2) || !sameArray(arr1, arr3)) 负责检查。这里我们让多个排序算法互相验证

    3. 辅助函数 (copyArray 和 sameArray)

    这两个是保证对数器能正确运行的工具函数。

    copyArray:

    。如果其中任何两个的结果不一致,就说明至少有一个算法出了问题。

    java 复制代码
    // 为了验证
    public static int[] copyArray(int[] arr) {
        int n = arr.length;
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = arr[i];
        }
        return ans;
    }```
    功能很简单:创建一个和原数组一模一样的新数组,避免引用传递导致的问题。
    
    **`sameArray`:**
    ```java
    // 为了验证
    public static boolean sameArray(int[] arr1, int[] arr2) {
        // 这里可以先补充长度是否相等的判断
        int n = arr1.length;
        for (int i = 0; i < n; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    对数器的威力

    对数器的门槛其实是比较高的,因为它往往需要你用两种不同的思路去实现相同功能的方法。但它的价值是巨大的:

  • 信心:它是你验证自己算法正确性的最强后盾。

  • 能力:它锻炼你从不同角度思考同一个问题的能力(暴力解 vs. 最优解)。

  • 效率:它能自动找到你手动很难想到的边界"反例",让你的 debug 过程从大海捞针变成按图索骥。

一个有趣、有用的算法分类

首先,左老师提出了一种非官方但极具现实意义的算法分类方法,将算法分为两大类。

注意:这两个名词都不是计算机科学或算法中的标准术语。

1. 硬计算类算法 (Hard Computing)

定义 :追求精确解、最优解的算法。

特点:为了找到那个唯一的、精确的答案,可能会导致算法的复杂度较高。

应用场景:这是目前绝大多数程序员面试、笔试、ACM/ICPC 类竞赛考察的核心。无论是大厂还是初创公司,面试中考察的算法基本都属于硬计算的范畴。

重要性硬计算类算法是所有程序员岗位都必须会考、任何写代码的工作都会用到的基础能力。无论是前端、后端、架构,算法的所有岗位都需要用到。

2. 软计算类算法 (Soft Computing)
核心观点

任何数据结构都一定是这两个结构拼出来的!没有例外!

这是一个非常重要的论断。我们将来要学习的各种纷繁复杂的数据结构,例如:

等等,无论它们看起来多么复杂,其最底层的实现原理,都离不开"连续的内存"和"跳转的指针"这两种基本构造单元的组合与运用。

在后续的课程中,我们将会深入学习这些具体的数据结构,届时可以回头再来体会这个宏观分类的精妙之处。

一个宏观的数据结构分类

接着,左老师提出了一个同样宏观的、用于理解所有数据结构的底层逻辑分类。他认为,无论多么复杂的数据结构,其底层都是由两种最基本的结构"拼"出来的。

1. 连续结构 (Continuous Structure)

可以理解为像数组 这样的结构。数据在内存中是连续存放的,一个挨着一个,可以通过索引直接计算出内存地址,实现快速的随机访问。

2. 跳转结构 (Jumping Structure)

可以理解为像链表 这样的结构。数据在内存中是离散存放的,每个节点通过一个指针或引用,"跳转"到下一个节点的位置。

  • 定义 :更注重逼近一个可接受的解决方案,而不是强求精确的最优解。

  • 特点:计算时间可控,允许在一定程度上牺牲精度来换取效率。

  • 典型例子:模糊逻辑、神经网络、进化计算、概率论、混沌理论、支持向量机、群体智能等。这些通常是机器学习、人工智能领域的核心算法。

  • 重要性 :对于普通的开发岗位,软计算不是必须掌握的。但对于算法工程师,除了必须精通硬计算类算法之外,还需要掌握软计算类算法。

  • 链表

  • 队列、栈

  • 树(二叉树、平衡树、B树等)

  • 可持久化线段树

  • 树链剖分

  • 后缀数组

相关推荐
轮到我狗叫了4 小时前
牛客.小红的子串牛客.kotori和抽卡牛客.循环汉诺塔牛客.ruby和薯条
java·开发语言·算法
高山有多高4 小时前
详解文件操作
c语言·开发语言·数据库·c++·算法
乌萨奇也要立志学C++4 小时前
【洛谷】队列相关经典算法题详解:模板队列、机器翻译、海港
算法
YuTaoShao5 小时前
【LeetCode 热题 100】49. 字母异位词分组
算法·leetcode·哈希算法
aliedudu6 小时前
决策树概念与原理
算法·决策树·机器学习
赴3357 小时前
残差网络 迁移学习对食物分类案例的改进
人工智能·分类·迁移学习·resnet18
程序员Xu7 小时前
【LeetCode热题100道笔记】腐烂的橘子
笔记·算法·leetcode
天选之女wow7 小时前
【代码随想录算法训练营——Day4】链表——24.两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题02.07.链表相交、142.环形链表II
数据结构·算法·leetcode·链表
胡萝卜3.07 小时前
数据结构初阶:树的相关性质总结
数据结构·二叉树·性质·二叉树的性质