快速排序的另一个静态显示,密度较小但可能更容易读,将每个元素表示为彩色线,并显示每个顺序交换。(这种形式是受到Aldo Cortesi的排序可视化的启发。)更小的值颜色更轻,更大的值颜色更深。 现在已经看到了同一算法的三种不同的视觉展示:动画、稠密静态展示和稀疏静态展示。每种形式都有各自的优缺点。动画看起来有趣,但静态的可视化允许仔细检查,而不用着急。稀疏展示可能更容易理解,但密集展示除了显示细节之外,还显示算法行为的“宏观”视图。 在我们继续下去之前,让我们将快速排序与另一个众所周知的排序算法——归并排序进行对比。 ▼下列为归并排序的代码—— function mergesort(array) { varn = array.length, a0 = array, a1 = new Array(n); for(var m = 1; m < n; m <<= 1) { for (var i = 0; i < n; i += m << 1) { var left = i, right = Math.min(i + m, n), end = Math.min(i + (m << 1), n); merge(a0, a1, left, right, end); } i= a0, a0 = a1, a1 = i; } if(array === a1) { for (var i = 0; i < n; ++i) { array[i] = a0[i]; } } } function merge(a0, a1, left, right, end) { for(var i0 = left, i1 = right; left < end; ++left) { if (i0 < right && (i1 >= end || a0[i0] <= a0[i1])) { a1[left] = a0[i0++]; }else { a1[left] = a0[i1++]; } } } 下面是相关的动画展示: 正如你可能从代码或动画中推测的,归并排序采用了一种与快速排序非常不同的排序方法。快速排序通过执行交换就地运行,与快速排序不同,归并排序需要额外的数组副本。这个额外的空间用于归并排序的子数组,把来自子数组的每对元素组合在一起,同时保持顺序。由于归并排序运行副本而不是交换,因此我们必须相应地修改动画(或有误导读者的风险)。 归并排序自下而上进行。最初,它合并大小为1的子数组,因为它们经过了排序。每个相邻的子数组:首先,只是一对元素,使用额外的数组合并为大小为2的排序子数组。然后,将大小为2的每个相邻排序子数组合并成大小为4的排序子数组。每次遍历整个数组后,归并排序将排序子数组的大小加倍:8,16,等等。最终,这个加倍合并了整个数组,算法终止。 因为归并排序在数组上执行重复遍历而不是像快速排序那样递归,并且因为每次遍历使排序的子数组的大小加倍,而不考虑输入,所以更容易设计成静态展示。我们只需在每次合并后显示数组的状态。 让我们再花一点时间来想想我们所看到的。这里的目标是研究算法的行为而不是特定的数据集。但仍然有数据,这是必然的,因为数据是从算法的执行而导出的。这意味着我们可以使用派生数据的类型来将算法可视化分类。 ▼第0级/黑盒 最简单的类,只显示输出。不解释算法的操作,但它仍然可以验证正确性。通过将算法视为黑盒,可以更容易地比较不同算法的输出。黑盒可视化还可以与更深入的输出分析结合,例如上面显示的随机偏移矩阵图。 ▼第1级/灰盒 许多算法(虽然不是全部)增量地构建输出。随着它的进程,通过可视化过程中间的输出,开始看到算法是如何工作的。这解释了更多而不必引入新的抽象概念,因为过程中间和最终输出共享相同的结构。然而,这种类型的可视化会产生比它可以回答的更多的问题,因为它没有解释为什么算法做它要做的事。 ▼第2级/白盒 为了回答“为什么”这个问题,白盒可视化暴露算法的内部状态以及其中间过程输出。这种类型有最大的潜力来解释,但也对读者是最大的负担,因为内部状态的意义和目的必须清楚地描述。这里有一个风险,j2直播,额外的复杂性会压垮读者;分层信息可以使图形更容易获得。最后,由于内部状态高度依赖于特定算法,这种类型的可视化通常不适合于比较算法。 (责任编辑:本港台直播) |