【左程云算法03】对数器算法和数据结构大致分类
目录
对数器的实现
代码实现与解析
1. 随机样本生成器 (randomArray)
2. 核心驱动逻辑 (main 方法)
3. 辅助函数 (copyArray 和 sameArray)
对数器的威力
算法和数据结构简介编辑
1. 硬计算类算法 (Hard Computing)
2. 软计算类算法 (Soft Computing)
核心观点
一个宏观的数据结构分类
1. 连续结构 (Continuous Structure)
2. 跳转结构 (Jumping Structure)
视频链接
算法讲解005【入门】对数器-验证的重要手段
算法讲解008【入门】算法和数据结构简介
对数器的实现
对数器的本质,就是通过大规模的随机样本测试,来对比我们实现的“精巧算法”和一个“绝对正确”的暴力方法,从而验证我们算法的正确性。
构建一个对数器的完整流程分为以下六步:
-
你要有一个想测的方法 a(最优解)
这是我们自己实现的、希望验证其正确性的算法。 -
实现一个复杂度不好但是容易实现的方法 b(暴力解)
这个方法是我们的“真理标杆”。我们不要求它性能好,但必须保证它的逻辑是绝对正确的。通常,我们会用最直观、最暴力的方式来实现它。 -
实现一个随机样本产生器
我们需要一个函数,能够生成各种随机情况的测试数据(例如,长度随机、值也随机的数组)。 -
把方法 a 和方法 b 跑相同的输入样本,看看得到的结果是否一样
这是对数器的核心对比环节。对于同一个随机样本,分别用方法 a 和方法 b 去处理。 -
如果有某个随机样本使得比对结果不一致,打印出这个出错的样本进行人工干预,改对方法 a 和方法 b
一旦发现不一致,就意味着我们的方法 a 存在 bug。对数器会自动捕获这个导致错误的“反例”,我们就可以用这个具体的样本去轻松地进行 debug。 -
当样本数量很多时比对依然正确,可以确定方法 a(最优解)已经正确。
经过成千上万,甚至几十万次随机样本的“轰炸”都未曾出错,我们就可以非常有信心地认为,我们的算法 a 是正确的。
代码实现与解析
下面,我们结合截图中的 Java 代码,来一步步拆解对数器的具体实现。这里的例子是用来同时验证我们写的多个排序算法是否正确。
1. 随机样本生成器 (randomArray)
负责源源不断地提供测试数据。
// 得到一个随机数组,长度是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 方法)
这里是对数器的“发动机”,它组织了整个测试流程。
// 为了验证
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:
。如果其中任何两个的结果不一致,就说明至少有一个算法出了问题。
// 为了验证 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树等)
-
可持久化线段树
-
树链剖分
-
后缀数组