找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4648

积分

0

好友

644

主题
发表于 2 小时前 | 查看: 1| 回复: 0

学习算法时,你是否常常觉得概念抽象,难以在脑中构建出数据流动的具象画面?

  • 看了几遍插入排序的代码,还是不理解为什么从后往前比较?
  • 脑子里想象不出元素移动的过程,只能死记硬背?
  • 网上找的动画演示太快或太慢,无法反复琢磨?
  • 想深入理解算法,但纸上画图效率太低?

今天,我们将采用一种“动手实现”的进阶学习法——自己动手,使用 Remotion 这个基于 React 的视频创作框架,来制作一个算法可视化视频。通过编写代码来“生成”理解,这才是真正的深度学习。当我自己实现这个视频时,才恍然大悟:原来插入排序的“空位”和“元素后移”是这样的!

🌩️ 什么是插入排序?

在动手制作视频之前,我们先来快速回顾一下插入排序的原理。

📖 生活化理解

想象你在玩扑克牌,手里已经有一排排好序的牌:

[3♣] [5♦] [7♥] [9♠]  ← 已排序的牌

这时候你摸到一张 4♠,要把它插到正确的位置:

第1步:和9♠比,4 < 9,把9往后移 → [3] [5] [7] [空] [9]
第2步:和7♥比,4 < 7,把7往后移 → [3] [5] [空] [7] [9]
第3步:和5♦比,4 < 5,把5往后移 → [3] [空] [5] [7] [9]
第4步:和3♣比,4 > 3,找到了!插在3后面 → [3] [4] [5] [7] [9]

这就是插入排序的核心思想:

  1. 将数组分为已排序未排序两部分
  2. 每次从未排序部分取出第一个元素
  3. 在已排序部分中找到合适的位置插入
  4. 重复直到所有元素排序完成

💡 重点理解

  • 为什么要“从后往前”比较?因为我们边比较边移位
  • “空位”是如何产生的?元素后移腾出的空间
  • 如何找到插入位置?遇到第一个 ≤ key 的元素

📂 第一步:设计数据结构

准备环境

npm init remotion
npm install

技术栈:React + TypeScript + Remotion

要展示插入排序的每一步,我们需要记录:

  • 当前数组状态
  • 正在比较的元素
  • 已排序的部分
  • 待插入的元素及其位置
  • 空位标记
interface SortStep {
  array: number[];           // 当前数组状态
  comparing: number[];       // 正在比较的元素索引
  sorted: number[];          // 已排序元素的索引
  stepType: 'comparing' | 'moving' | 'inserting'; // 步骤类型
  keyValue?: number;         // 待插入元素的值
  keyIndex?: number;         // 待插入元素的原位置
  emptySlot?: number;        // 移动后空出来的位置
  subtitle: string;          // 字幕说明
}

💻 第二步:生成排序步骤

这是最核心的部分,需要记录每一步操作:

export const generateInsertionSortSteps = (arr: number[]): SortStep[] => {
  const steps: SortStep[] = [];
  const array = [...arr];
  const n = array.length;
  const sorted: number[] = [0];  // 第一个元素默认已排序

  for (let i = 1; i < n; i++) {
    const key = array[i]; // 取出待插入元素
    let j = i - 1;
    let currentEmptySlot = i; // 初始空位就是key的位置

    // 在已排序部分从后向前查找插入位置
    while (j >= 0 && array[j] > key) {
      // 记录比较步骤
      steps.push({
        array: [...array],
        comparing: [j],
        stepType: 'comparing',
        keyValue: key,
        emptySlot: currentEmptySlot,
        subtitle: `比较 ${array[j]} 和 ${key}`
      });

      // 移动元素,更新空位
      array[j + 1] = array[j];
      currentEmptySlot = j;

      // 记录移动步骤
      steps.push({
        array: [...array],
        comparing: [j, j + 1],
        stepType: 'moving',
        keyValue: key,
        emptySlot: currentEmptySlot,
        subtitle: `${array[j]} 后移一位`
      });

      j--;
    }

    // 插入元素
    array[j + 1] = key;
    steps.push({
      array: [...array],
      stepType: 'inserting',
      keyValue: key,
      subtitle: `插入 ${key} 到位置 ${j + 1}`
    });
  }

  return steps;
};

🔑 第三步:可视化渲染

Remotion 的核心是React 组件,每一帧都是一个 React 渲染结果:

export const InsertionSort: React.FC = () => {
  const frame = useCurrentFrame(); // 获取当前帧
  const steps = generateInsertionSortSteps([64, 34, 25, 12, 22, 11, 90]);

  // 计算当前步骤
  const stepIndex = Math.floor(frame / FRAMES_PER_STEP);
  const currentStep = steps[stepIndex];

  return (
    <AbsoluteFill style={{ backgroundColor: '#1a1a2e' }}>
      {/* 标题 */}
      <div>插入排序 Insertion Sort</div>

      {/* 已排序/未排序区域标识 */}
      <div>✓ 已排序: 前 {boundaryIndex + 1} 个</div>
      <div>未排序: 后 {n - boundaryIndex - 1} 个</div>

      {/* 待插入元素卡片 */}
      <div>待插入元素: {keyValue}</div>

      {/* 数组条形图 */}
      {currentStep.array.map((value, index) => {
        // 空位特殊处理
        if (isEmptySlot) {
          return (
            <div>
              <div style={{ border: '2px dashed #8b949e' }}>
                空
              </div>
              {/* 悬空的待插入元素 */}
              <div style={{ color: '#ffd700' }}>
                {keyValue} ↓
              </div>
            </div>
          );
        }

        return <div style={{ backgroundColor: getColor(index) }} />;
      })}

      {/* 伪代码面板 */}
      <div>
        {pseudocode.map((line, i) => (
          <div style={{
            backgroundColor: i === codeLine ? '#2d4a22' : 'transparent',
            borderLeft: i === codeLine ? '3px solid #4ecca3' : 'none'
          }}>
            {line}
          </div>
        ))}
      </div>
    </AbsoluteFill>
  );
};

🎨 第四步:颜色编码系统

为了让演示更直观,我们设计了清晰的视觉系统:

颜色 状态 说明
🟣 紫色 未排序 尚未处理的元素
🟢 绿色 已排序 已插入到正确位置
🟡 金色 待插入 从数组中取出的key元素
🔵 蓝色 比较中 正在比较的元素
🔴 红色 移动中 正在向后移动的元素

空位特殊显示

  • 虚线边框 + 半透明
  • 标注“空”字
  • 上方显示金色的待插入元素

⚡ 第五步:导出视频

预览视频

npm start

访问 http://localhost:3000,实时预览效果

导出 MP4

npm run build

输出高清 MP4 视频:

  • 分辨率:1920×1080
  • 帧率:30fps
  • 每步停留:30帧(约1秒)

💡 核心技术亮点

1. 空位可视化

传统教学无法展示“空位”,学生很难理解元素移动过程。我们的方案:

移动前:[34] [64] [25] [12] [22]
             ↑
           待插入

移动过程:
[34] [空] [64] [12] [22]
      ↑
    [25] 金色悬空

[空] [34] [64] [12] [22]
 ↑
[25] 金色悬空

学生能清晰看到:

  • 哪个位置空出来了
  • 待插入元素在哪里悬着
  • 元素如何向后移动

2. 已排序/未排序分界线

在排序过程中,用金色竖线标记分界:

[25] [34] [64] | [12] [22]
 已排序部分    ↑  未排序部分
           分界线

3. 伪代码实时高亮

右侧代码面板同步显示:

3  | key = array[i]       ← 当前执行(绿色高亮)
4  | j = i - 1
5  | while j >= 0 and array[j] > key:

学生能直观看到:代码在执行哪一行,数组在发生什么变化

4. 布局稳定性

所有信息区域固定高度,避免内容闪烁:

  • 已排序/未排序标识:固定50px
  • 待插入元素卡片:固定70px
  • 插入位置提示:固定50px

📊 传统教学 vs 可视化教学

维度 传统教学 Remotion 可视化
直观性 代码+口述 动态条形图+颜色编码
参与感 被动接收 主动观察每一步
理解难度 抽象难懂 具象易懂
修改成本 改PPT需半天 改代码几分钟
可复用性 高(代码复用)
制作成本 低(但效果差) 低(代码即视频)

✅ 效果展示

最终生成的视频具有以下特点:

视觉层面

  • ✨ 条形图高度对应数值大小
  • 🎨 颜色清晰区分不同状态
  • 📍 空位虚线框 + 悬空元素
  • 🎯 分界线标记已排序部分

教学层面

  • 📝 字幕实时解释操作
  • 💻 伪代码同步高亮
  • 🔍 步骤可暂停、可回放
  • 📊 复杂度对比清晰

🚀 进阶玩法推荐

1. 多种排序算法对比

已经实现:

可以继续添加:

  • 选择排序
  • 快速排序
  • 归并排序

让学生对比不同算法的执行过程。

2. 性能对比可视化

添加计时器和交换次数统计:

冒泡排序:交换 45 次,耗时 2.3s
插入排序:交换 12 次,耗时 1.1s

3. 自定义数据

允许学生输入自己的数组:

const initialArray = [/* 学生自定义 */];

4. 速度调节

调整 FRAMES_PER_STEP 控制演示速度:

export const FRAMES_PER_STEP = 30; // 30帧≈1秒
export const FRAMES_PER_STEP = 15; // 15帧≈0.5秒(加速)

💪 我的实践心得

在实现这个视频的过程中,我遇到了几个关键问题,解决后才真正理解了插入排序:

问题1:如何展示“空位”?

最初的错误想法:直接显示移动后的数组
问题:看不出来元素是怎么移动的,也看不出“空位”

最终方案

// 标记空位
emptySlot: currentEmptySlot

// 渲染时特殊处理
if (isEmptySlot) {
  // 只显示虚线框 + “空”字
  // 上方悬空显示待插入元素
}

收获:理解了为什么插入排序是“从后往前比较”——这样可以边比较边腾出空间!

问题2:如何追踪待插入元素的位置?

困惑:元素不断移动,key 应该显示在哪?

理解过程

  • 初始:key 在位置 i
  • 每移动一次:空位向前移动一位
  • 最终:插入到正确位置

收获:这才理解了算法中 j 变量的作用——追踪空位位置!

问题3:布局为什么会跳动?

问题:信息框时有时无,下面的柱状图上下跳动

解决方案:所有区域固定高度

// 已排序/未排序标识:固定50px高度
<div style={{ height: 50 }}>
  {显示内容 或 占位符}
</div>

收获:学会了 UI 稳定性设计,也加深了对 React 渲染的理解。

🎯 学完后的收获

通过自己动手实现这个视频,我获得了远超看教程的理解深度:

1. 算法理解更深刻

以前:知道插入排序是“取元素插入到已排序部分”
现在:理解了每一步的细节:

  • 空位是如何产生的
  • 为什么从后往前比较
  • 边界条件如何处理(j < 0)

2. 编程能力提升

TypeScript 类型设计

interface SortStep {
  array: number[];
  stepType: 'comparing' | 'moving' | 'inserting';
  emptySlot?: number;  // 这个字段让我理解了“空位”的概念
}

React 状态管理:理解了如何用 React 管理复杂的可视化状态

3. 技术视野拓展

Remotion 应用场景

  • 算法可视化教学视频
  • 产品演示视频
  • 数据可视化动画
  • 技术分享 PPT 的动态版本

核心价值:代码即视频,修改方便,可复用!

📦 项目结构

insertion-sort-video/
├── src/
│   ├── index.tsx              # 入口,注册组件
│   ├── BubbleSort.tsx         # 冒泡排序
│   └── InsertionSort.tsx      # 插入排序 ⭐
├── out/
│   └── insertion-sort.mp4     # 导出视频
├── package.json
└── README.md

🎯 总结

用 Remotion 制作算法可视化,我最大的感悟是:

“看懂了是别人的,做出来才是自己的”

  • 动手实现:比看10遍教程都深刻
  • 可视化思维:强迫你思考每一步的细节
  • 代码即视频:修改方便,还能复用到其他算法
  • 技术栈延伸:顺便学会了 React + TypeScript + Remotion

最关键的是:自己做出的视频,可以反复看,可以分享给其他人,成就感爆棚!

赶紧去试试吧!这种将理解转化为可运行、可观看的开源项目的过程,本身就是一种极佳的深度学习路径。如果你在实现过程中遇到问题,或者有更好的想法,欢迎在 云栈社区 的对应板块交流探讨。

你还想看哪种算法的可视化?快排?归并排序?堆排序?不妨在评论区告诉我们。




上一篇:TypeScript排版库Pretext发布:纯JS实现文字环绕与动态布局,前端性能提升数百倍
下一篇:面试复盘:十五分钟拿Offer与一道Lisp语法解析题
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-31 08:21 , Processed in 0.790705 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表