发现了一个有趣的算法可视化工具
1301
2022.08.02
2022.08.02
发布于 未知归属地

最近在leetcode上学习时,看到了很多coder使用算法动图来讲题解,非常清晰明了,着实是羡慕。于是在网上查了下是否有什么可视化工具,能以动画的形式展现算法的执行过程。于是发现了这个东西:algorithm-visualize。这对于我这种特别不会画图、不会图像化记录解题思路的人,是个小小的惊喜。

我们可以去来体验一下:体验地址。最左边是算法目录,包括排序、二叉树、图、动规等很多经典的算法。中间是算法动画演示区域,右边是代码coding区域。我们如果对哪个算法感兴趣,可以直接点击【运行Play】按钮就可以了。

但是啊,这些算法都是已经结合可视化程序写好的,我想把它换成自己想运行的算法,就得了解它可视化的部分如何实现,但是在网上却几乎搜索不到对应的教程,就捣鼓学习了一下。

自己来实现算法动画

冒泡排序可视化

冒泡排序原代码如下:

function bubbleSort(arr) {
  let len = arr.length;
  let done = true; // 我们加一个标志位,如果某次循环完后,没有任何两数进行交换,就将标志位 设置为 true,表示排序完成,这样我们就可以减少不必要的排序,提高性能

  // 如果当前循环已经不需要排序了,则说明此时已经是排好序的数组了,不需要进入到下一轮循环
  while (done) {
    done = false;

    // 以下为一轮完整的冒泡,可以完成第(len-i-1)个数的排序
    for (let i = 1; i < len; i++) {
      // 如果当前值大于后一个值,就进行交换
      if (arr[i - 1] > arr[i]) {
        const temp = arr[i - 1];
        arr[i - 1] = arr[i];
        arr[i] = temp;
        done = true; // 存在交换,就置为false
      }
    }
    len--;
  }

  return arr;
}

加入可视化程序如下:

冒泡.png

详细代码如下:

// 引入algorithm-visualizer包
const { Tracer, Array1DTracer, ChartTracer, LogTracer, Randomize, Layout, VerticalLayout } = require('algorithm-visualizer');

const chart = new ChartTracer(); // 实例化ChartTracer
const tracer = new Array1DTracer(); // 实例化Array1DTracer
const logger = new LogTracer(); // 实例化LogTracer
Layout.setRoot(new VerticalLayout([chart, tracer, logger])); // 以垂直方式排列统计图、一维数组、输出日志

const arr = Randomize.Array1D({ N: 10 }); // 随机初始化长度为15的一维数组
tracer.set(arr); // 初始化数组
tracer.chart(chart); // 以该数组生成柱状图
Tracer.delay(); // 暂停一下

// 打印信息,在这里打印的是原始数组
logger.println(`原始数组 = [${arr.join(', ')}]`);

function bubbleSort(arr) {
  let len = arr.length;
  let done = true; // 我们加一个标志位,如果某次循环完后,没有任何两数进行交换,就将标志位 设置为 true,表示排序完成,这样我们就可以减少不必要的排序,提高性能

  // 如果当前循环已经不需要排序了,则说明此时已经是排好序的数组了,不需要进入到下一轮循环
  while (done) {
    done = false;
    tracer.select(len - 1); // 选择最后一个数字
    Tracer.delay(); // 暂停一下

    // 以下为一轮完整的冒泡,可以完成第(len-i-1)个数的排序
    for (let i = 1; i < len; i++) {
      tracer.select(i); // 选择当前i对应的值
      Tracer.delay(); // 暂停一下

      // 如果当前值大于后一个值,就进行交换
      if (arr[i - 1] > arr[i]) {
        const temp = arr[i - 1];
        arr[i - 1] = arr[i];
        arr[i] = temp;
        done = true; // 存在交换,就置为false

        tracer.patch(i - 1, arr[i - 1]); // 显示i-1位置的值已经变化为了arr[i-1]值
        tracer.patch(i, arr[i]); // 显示i位置的值已经变化为了arr[i]值
        Tracer.delay(); // 暂停一下
        tracer.depatch(i - 1); // 取消显示i-1位置值变化
        tracer.depatch(i); // 取消显示i位置值变化
      }

      tracer.deselect(i); // 取消选择i位置的值
    }
    len--;
  }
  
  tracer.deselect(len - 1); // 取消选择最后一个数字
  // 打印信息,在这里打印的是排好序后的数组
  logger.println(`排序完数组 = [${arr.join(', ')}]`);
  
  return arr;
}

bubbleSort(arr);

以上代码的可视化运行效果如下:

冒泡动图.gif

有序数组二分查找可视化

二分查找原代码如下:

// 在数组arr找出terget的位置
function BinarySearch(arr, target) {
  let start = 0;
  let end = arr.length - 1;
  let midNum;

  while (start <= end) {
    const midIdx = Math.floor((start + end) / 2);
    midNum = arr[midIdx];

    if (midNum < target) {
      start = midIdx + 1;
    } else if (midNum > target) {
      end = midIdx - 1;
    } else {
      return midIdx;
    }
  }

  return -1;
}

加入可视化程序如下:

二分.png

详细代码如下:

const { Tracer, Array1DTracer, ChartTracer, LogTracer, Randomize, Layout, VerticalLayout } = require('algorithm-visualizer');

const chart = new ChartTracer();
const tracer = new Array1DTracer();
const logger = new LogTracer();

Layout.setRoot(new VerticalLayout([chart, tracer, logger]));
 // 生成随机数组,这里的arr是由0-50之间的数字组成的有序数组
const arr = Randomize.Array1D({ N: 15, value: () => Randomize.Integer({ min: 0, max: 50 }), sorted: true });
 // 随机生成一个值
const target = arr[Randomize.Integer({ min: 0, max: arr.length - 1 })];
tracer.set(arr);
tracer.chart(chart);
Tracer.delay();

// 在数组arr找出terget的位置
function BinarySearch(arr, target) { 
  let start = 0;
  let end = arr.length - 1;
  let midNum;

  while (start <= end) {
    const midIdx = Math.floor((start + end) / 2);
    midNum = arr[midIdx];

    tracer.select(start, end); // 选择区间start-end
    Tracer.delay(); // 暂停一下
    tracer.patch(midIdx); // 高亮中间位置的值
    Tracer.delay(); // 暂停一下
    tracer.depatch(midIdx); // 取消高亮中间位置的值
    tracer.deselect(start, end); // 取消选择区间start-end

    if (midNum < target) {
      start = midIdx + 1;
    } else if (midNum > target) {
      end = midIdx - 1;
    } else {
      logger.println(`${target}找到在位置${midIdx}!`);
      tracer.select(midIdx); // 选择中间位置的值
      return midIdx;
    }
  }

  logger.println(`${target}没有找到!`);
  return -1;
}

BinarySearch(arr, target);

二分查找的可视化运行效果如下:

二分查找动图.gif

可视化代码解释

Array1DTracer:将一维数组写入table中。

const tracer = new Array1DTracer(); // 实例化Array1DTracer
const arr = Randomize.Array1D({ len: 15 }); // 随机初始化长度为15的一维数组

// 1. 初始化数组 
tracer.set(arr);
// 2. 重置数组
tracer.reset();
// 3. 暂停显示变化
tracer.delay();
// 4. 可视化表示x位置的值已经变为了v
tracer.patch(x, v);
// 5. 停止可视化表示x位置的值变化了
tracer.depatch(x);
// 6. 选择x位置(某个位置)、选择from到to位置(某个区间)
tracer.select(x);
tracer.select(from, to);
// 7. 取消选择
tracer.deselect(x);
tracer.deselect(from, to);
// 8. 同步数组到统计图
tracer.chart();

ChartTracer:以柱状图的形式展示一维数组

const chart = new ChartTracer(); // 实例化ChartTracer
// 所有的用户都和上述Array1DTracer的一致

// 1. 初始化数组 
chart.set(arr);
// 2. 重置数组
chart.reset();
// 3. 暂停显示变化
chart.delay();
// 4. 可视化表示x位置的值已经变为了v
// ......

LogTracer:打印日志

const logger = new LogTracer(); // 实例化日志

// 1. 打印内容
logger.print('I am message.'); // 一行打印
logger.println('I am message.'); // 换行打印

随机初始化方法:

// 1. 随机创建一维数组,N为数组长度,sorted为是否排好序
const arr = Randomize.Array1D({ N: 15, sorted: false });

// 2. 随机创建二维数组,N为第一维长度,M为第二维长度,sorted为是否排好序
const arr = Randomize.Array2D({ N: 3, M: 4, sorted: false });

// 3. 随机创建浮点数
const doubleNum = Randomize.Double(min: 1, max: 10);

// 4. 随机创建小数
const intNum = Randomize.Integer(min: 1, max: 10);

// 5. 随机创建字符串,length为长度,letters为组成字符串可选择的字母
const randomString = Randomize.String(length: 10, letters: 'abcdefg');

这里列举的是js语言的用法,当然这个平台也支持Java、python语言,大家也可以动手试试,在自己的程序中加入可视化代码,在平台中可看到运行的动画效果。这样其实可以帮助到我们排查错误、做笔记、记录题解,毕竟可视化是最直接的让人理解算法思想的好方式。

评论 (1)