10个动画介绍递归(用Gemin3生成),Gemin3的确很利害!
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>递归算法动画教程</title>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style>
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
}
.animate-fadeIn { animation: fadeIn 0.3s ease-out; }
.animate-pulse-custom { animation: pulse 0.5s ease-in-out; }
.animate-slideIn { animation: slideIn 0.3s ease-out; }
.call-stack-item {
transition: all 0.3s ease;
}
.code-highlight {
background: linear-gradient(90deg, #fef08a 0%, #fef08a 100%);
border-radius: 2px;
}
.tree-node {
transition: all 0.3s ease;
}
.fractal-canvas {
background: linear-gradient(135deg, #1e1e2e 0%, #2d2d44 100%);
}
</style>
</head>
<body class="bg-gradient-to-br from-gray-900 via-purple-900 to-gray-900 min-h-screen text-white">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-12">
<h1 class="text-5xl font-bold bg-gradient-to-r from-cyan-400 via-purple-400 to-pink-400 bg-clip-text text-transparent mb-4">
🔄 递归算法动画教程
</h1>
<p class="text-xl text-gray-300">通过10个交互式动画,深入理解递归的魔力</p>
</header>
<!-- 导航 -->
<nav class="flex flex-wrap justify-center gap-2 mb-8 sticky top-2 z-50 bg-gray-900/80 backdrop-blur-sm p-4 rounded-xl">
<button onclick="showDemo(0)" class="nav-btn px-4 py-2 rounded-lg bg-gradient-to-r from-cyan-500 to-blue-500 hover:from-cyan-400 hover:to-blue-400 transition-all text-sm font-medium">1. 倒计时</button>
<button onclick="showDemo(1)" class="nav-btn px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-all text-sm font-medium">2. 阶乘</button>
<button onclick="showDemo(2)" class="nav-btn px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-all text-sm font-medium">3. 斐波那契</button>
<button onclick="showDemo(3)" class="nav-btn px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-all text-sm font-medium">4. 数组求和</button>
<button onclick="showDemo(4)" class="nav-btn px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-all text-sm font-medium">5. 字符串反转</button>
<button onclick="showDemo(5)" class="nav-btn px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-all text-sm font-medium">6. 二分查找</button>
<button onclick="showDemo(6)" class="nav-btn px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-all text-sm font-medium">7. 汉诺塔</button>
<button onclick="showDemo(7)" class="nav-btn px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-all text-sm font-medium">8. 树遍历</button>
<button onclick="showDemo(8)" class="nav-btn px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-all text-sm font-medium">9. 快速排序</button>
<button onclick="showDemo(9)" class="nav-btn px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-all text-sm font-medium">10. 分形图</button>
</nav>
<!-- 演示区域 -->
<div id="demo-container" class="max-w-6xl mx-auto">
<!-- 动态内容将在这里渲染 -->
</div>
</div>
<script>
let currentDemo = 0;
let animationRunning = false;
let animationId = null;
const demos = [
// 1. 倒计时
{
title: "1. 倒计时 - 最简单的递归",
difficulty: "⭐",
description: "递归的本质是函数调用自身。倒计时是最简单的例子:每次减1,直到达到0为止。",
code: `function countdown(n) {
if (n <= 0) { // 基准条件
console.log("发射!🚀");
return;
}
console.log(n); // 当前操作
countdown(n - 1); // 递归调用
}`,
init: initCountdown
},
// 2. 阶乘
{
title: "2. 阶乘计算 - 递归的经典应用",
difficulty: "⭐⭐",
description: "阶乘 n! = n × (n-1) × ... × 1。递归思想:n! = n × (n-1)!,基准条件是 0! = 1。",
code: `function factorial(n) {
if (n <= 1) { // 基准条件
return 1;
}
return n * factorial(n - 1); // n × (n-1)!
}`,
init: initFactorial
},
// 3. 斐波那契
{
title: "3. 斐波那契数列 - 双重递归",
difficulty: "⭐⭐⭐",
description: "斐波那契数列:每个数是前两个数之和。F(n) = F(n-1) + F(n-2),展示了递归的树形调用结构。",
code: `function fibonacci(n) {
if (n <= 1) { // 基准条件
return n;
}
return fibonacci(n-1) + fibonacci(n-2);
}`,
init: initFibonacci
},
// 4. 数组求和
{
title: "4. 数组求和 - 分解问题",
difficulty: "⭐⭐",
description: "将数组求和分解为:第一个元素 + 剩余元素之和。体现了递归的分治思想。",
code: `function sum(arr) {
if (arr.length === 0) { // 基准条件
return 0;
}
return arr[0] + sum(arr.slice(1));
}`,
init: initArraySum
},
// 5. 字符串反转
{
title: "5. 字符串反转 - 递归操作字符串",
difficulty: "⭐⭐",
description: "反转字符串:最后一个字符 + 反转(剩余字符串)。展示递归如何处理字符串。",
code: `function reverse(str) {
if (str.length <= 1) { // 基准条件
return str;
}
return str[str.length-1] + reverse(str.slice(0,-1));
}`,
init: initStringReverse
},
// 6. 二分查找
{
title: "6. 二分查找 - 高效搜索",
difficulty: "⭐⭐⭐",
description: "在有序数组中查找:每次将搜索范围减半。递归版本更直观地展示了分治策略。",
code: `function binarySearch(arr, target, low, high) {
if (low > high) return -1; // 未找到
let mid = Math.floor((low + high) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] > target) {
return binarySearch(arr, target, low, mid-1);
} else {
return binarySearch(arr, target, mid+1, high);
}
}`,
init: initBinarySearch
},
// 7. 汉诺塔
{
title: "7. 汉诺塔 - 经典递归问题",
difficulty: "⭐⭐⭐⭐",
description: "将n个盘子从A移到C:先把n-1个移到B,再把最大的移到C,最后把n-1个从B移到C。",
code: `function hanoi(n, from, to, aux) {
if (n === 1) {
move(from, to); // 基准:移动一个盘子
return;
}
hanoi(n-1, from, aux, to); // 移动n-1个到辅助柱
move(from, to); // 移动最大的
hanoi(n-1, aux, to, from); // 移动n-1个到目标柱
}`,
init: initHanoi
},
// 8. 树遍历
{
title: "8. 二叉树遍历 - 递归与数据结构",
difficulty: "⭐⭐⭐⭐",
description: "前序遍历:根→左→右。递归天然适合树形结构的处理。",
code: `function preorder(node) {
if (node === null) return; // 基准条件
visit(node); // 访问当前节点
preorder(node.left); // 递归左子树
preorder(node.right); // 递归右子树
}`,
init: initTreeTraversal
},
// 9. 快速排序
{
title: "9. 快速排序 - 分治算法",
difficulty: "⭐⭐⭐⭐⭐",
description: "选择基准值,将数组分为小于和大于基准的两部分,递归排序后合并。",
code: `function quickSort(arr) {
if (arr.length <= 1) return arr; // 基准条件
let pivot = arr[0];
let left = arr.filter(x => x < pivot);
let right = arr.filter(x => x > pivot);
return [...quickSort(left), pivot, ...quickSort(right)];
}`,
init: initQuickSort
},
// 10. 分形图
{
title: "10. 谢尔宾斯基三角形 - 递归艺术",
difficulty: "⭐⭐⭐⭐⭐",
description: "分形是递归的艺术表现:大三角形由三个小三角形组成,每个小三角形又由更小的三角形组成...",
code: `function sierpinski(x, y, size, depth) {
if (depth === 0) {
drawTriangle(x, y, size); // 基准:绘制三角形
return;
}
let half = size / 2;
sierpinski(x, y, half, depth-1); // 上
sierpinski(x-half/2, y+half*0.866, half, depth-1); // 左下
sierpinski(x+half/2, y+half*0.866, half, depth-1); // 右下
}`,
init: initFractal
}
];
function showDemo(index) {
currentDemo = index;
stopAnimation();
// 更新导航按钮样式
document.querySelectorAll('.nav-btn').forEach((btn, i) => {
if (i === index) {
btn.className = 'nav-btn px-4 py-2 rounded-lg bg-gradient-to-r from-cyan-500 to-blue-500 hover:from-cyan-400 hover:to-blue-400 transition-all text-sm font-medium';
} else {
btn.className = 'nav-btn px-4 py-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition-all text-sm font-medium';
}
});
const demo = demos[index];
const container = document.getElementById('demo-container');
container.innerHTML = `
<div class="animate-fadeIn">
<div class="bg-gray-800/50 backdrop-blur rounded-2xl p-6 mb-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-2xl font-bold text-cyan-400">${demo.title}</h2>
<span class="text-2xl">${demo.difficulty}</span>
</div>
<p class="text-gray-300 text-lg">${demo.description}</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- 代码区域 -->
<div class="bg-gray-800/50 backdrop-blur rounded-2xl p-6">
<h3 class="text-lg font-semibold text-purple-400 mb-4">📝 代码</h3>
<pre class="bg-gray-900 rounded-xl p-4 overflow-x-auto text-sm"><code id="code-display" class="text-green-400">${escapeHtml(demo.code)}</code></pre>
<div class="mt-4 flex gap-3">
<button onclick="startAnimation()" class="px-6 py-2 bg-gradient-to-r from-green-500 to-emerald-500 rounded-lg font-medium hover:from-green-400 hover:to-emerald-400 transition-all">
▶ 开始动画
</button>
<button onclick="stopAnimation()" class="px-6 py-2 bg-gradient-to-r from-red-500 to-pink-500 rounded-lg font-medium hover:from-red-400 hover:to-pink-400 transition-all">
⏹ 停止
</button>
</div>
</div>
<!-- 可视化区域 -->
<div class="bg-gray-800/50 backdrop-blur rounded-2xl p-6">
<h3 class="text-lg font-semibold text-pink-400 mb-4">🎬 动画演示</h3>
<div id="animation-area" class="min-h-[300px] bg-gray-900 rounded-xl p-4 relative overflow-hidden">
<!-- 动画内容 -->
</div>
</div>
</div>
<!-- 调用栈可视化 -->
<div class="mt-6 bg-gray-800/50 backdrop-blur rounded-2xl p-6">
<h3 class="text-lg font-semibold text-yellow-400 mb-4">📚 调用栈</h3>
<div id="call-stack" class="flex flex-wrap gap-2 min-h-[60px]">
<span class="text-gray-500">点击"开始动画"查看调用栈变化...</span>
</div>
</div>
</div>
`;
demo.init();
}
function escapeHtml(text) {
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
}
function stopAnimation() {
animationRunning = false;
if (animationId) {
clearTimeout(animationId);
animationId = null;
}
}
function startAnimation() {
stopAnimation();
animationRunning = true;
demos[currentDemo].init();
demos[currentDemo].start?.();
}
function updateCallStack(stack) {
const container = document.getElementById('call-stack');
container.innerHTML = stack.map((item, i) => `
<div class="call-stack-item animate-slideIn bg-gradient-to-r from-indigo-600 to-purple-600 px-4 py-2 rounded-lg text-sm font-mono" style="animation-delay: ${i * 50}ms">
${item}
</div>
`).join('');
}
// 1. 倒计时动画
function initCountdown() {
const area = document.getElementById('animation-area');
area.innerHTML = `
<div class="flex flex-col items-center justify-center h-full">
<div id="countdown-display" class="text-9xl font-bold text-cyan-400">5</div>
<div id="countdown-status" class="text-2xl mt-4 text-gray-400"></div>
</div>
`;
demos[0].start = async () => {
const display = document.getElementById('countdown-display');
const status = document.getElementById('countdown-status');
async function countdown(n, stack = []) {
if (!animationRunning) return;
stack.push(`countdown(${n})`);
updateCallStack([...stack]);
if (n <= 0) {
display.textContent = '🚀';
display.className = 'text-9xl font-bold animate-pulse-custom';
status.textContent = '发射!';
stack.pop();
updateCallStack([...stack]);
return;
}
display.textContent = n;
display.className = 'text-9xl font-bold text-cyan-400 animate-pulse-custom';
status.textContent = `countdown(${n}) 调用中...`;
await sleep(1000);
await countdown(n - 1, stack);
stack.pop();
updateCallStack([...stack]);
}
await countdown(5);
};
}
// 2. 阶乘动画
function initFactorial() {
const area = document.getElementById('animation-area');
area.innerHTML = `
<div class="flex flex-col items-center justify-center h-full">
<div id="factorial-visual" class="flex flex-wrap justify-center gap-2 mb-4"></div>
<div id="factorial-result" class="text-4xl font-bold text-green-400 mt-4"></div>
</div>
`;
demos[1].start = async () => {
const visual = document.getElementById('factorial-visual');
const result = document.getElementById('factorial-result');
const n = 5;
let steps = [];
async function factorial(num, stack = []) {
if (!animationRunning) return 1;
stack.push(`factorial(${num})`);
updateCallStack([...stack]);
await sleep(600);
if (num <= 1) {
steps.push({ n: num, result: 1 });
renderFactorial(steps);
stack.pop();
updateCallStack([...stack]);
return 1;
}
const subResult = await factorial(num - 1, stack);
const thisResult = num * subResult;
steps.push({ n: num, result: thisResult });
renderFactorial(steps);
stack.pop();
updateCallStack([...stack]);
return thisResult;
}
function renderFactorial(steps) {
visual.innerHTML = steps.map((s, i) => `
<div class="animate-fadeIn bg-gradient-to-r from-purple-600 to-pink-600 px-4 py-3 rounded-xl text-center">
<div class="text-sm text-gray-200">${s.n}! =</div>
<div class="text-2xl font-bold">${s.result}</div>
</div>
`).join('');
if (steps.length > 0) {
result.textContent = `${n}! = ${steps[steps.length - 1].result}`;
}
}
await factorial(n);
};
}
// 3. 斐波那契动画
function initFibonacci() {
const area = document.getElementById('animation-area');
area.innerHTML = `
<div class="flex flex-col items-center h-full overflow-auto py-4">
<div id="fib-tree" class="text-center"></div>
<div id="fib-sequence" class="flex gap-2 mt-4 flex-wrap justify-center"></div>
</div>
`;
demos[2].start = async () => {
const tree = document.getElementById('fib-tree');
const sequence = document.getElementById('fib-sequence');
const n = 6;
let results = {};
let callOrder = [];
async function fib(num, depth = 0, stack = []) {
if (!animationRunning) return 0;
const callId = callOrder.length;
callOrder.push({ n: num, depth, status: 'calling' });
stack.push(`fib(${num})`);
updateCallStack([...stack]);
renderTree();
await sleep(400);
if (num <= 1) {
results[callId] = num;
callOrder[callId].status = 'done';
callOrder[callId].result = num;
renderTree();
stack.pop();
updateCallStack([...stack]);
return num;
}
const left = await fib(num - 1, depth + 1, stack);
const right = await fib(num - 2, depth + 1, stack);
const result = left + right;
results[callId] = result;
callOrder[callId].status = 'done';
callOrder[callId].result = result;
renderTree();
stack.pop();
updateCallStack([...stack]);
return result;
}
function renderTree() {
const levels = {};
callOrder.forEach((call, i) => {
if (!levels[call.depth]) levels[call.depth] = [];
levels[call.depth].push(call);
});
tree.innerHTML = Object.keys(levels).map(d => `
<div class="flex justify-center gap-2 mb-2">
${levels[d].map(c => `
<div class="px-3 py-1 rounded-lg text-sm ${
c.status === 'done'
? 'bg-green-600'
: 'bg-yellow-600 animate-pulse'
}">
F(${c.n})${c.result !== undefined ? ' = ' + c.result : ''}
</div>
`).join('')}
</div>
`).join('');
}
const result = await fib(n);
// 显示数列
sequence.innerHTML = '';
for (let i = 0; i <= n; i++) {
await sleep(200);
sequence.innerHTML += `
<div class="animate-fadeIn bg-cyan-600 w-10 h-10 rounded-full flex items-center justify-center font-bold">
${[0,1,1,2,3,5,8,13,21][i]}
</div>
`;
}
};
}
// 4. 数组求和动画
function initArraySum() {
const area = document.getElementById('animation-area');
area.innerHTML = `
<div class="flex flex-col items-center justify-center h-full">
<div id="array-display" class="flex gap-2 mb-6"></div>
<div id="sum-steps" class="space-y-2 text-lg font-mono"></div>
<div id="sum-result" class="text-3xl font-bold text-green-400 mt-4"></div>
</div>
`;
demos[3].start = async () => {
const arr = [3, 7, 2, 8, 5];
const display = document.getElementById('array-display');
const steps = document.getElementById('sum-steps');
const result = document.getElementById('sum-result');
let stepList = [];
// 渲染数组
function renderArray(highlightIndex = -1) {
display.innerHTML = arr.map((n, i) => `
<div class="w-12 h-12 rounded-lg flex items-center justify-center text-xl font-bold transition-all ${
i === highlightIndex ? 'bg-yellow-500 scale-110' :
i < highlightIndex ? 'bg-gray-600' : 'bg-blue-600'
}">
${n}
</div>
`).join('');
}
async function sum(array, index = 0, stack = []) {
if (!animationRunning) return 0;
stack.push(`sum([${array.slice(index).join(',')}])`);
updateCallStack([...stack]);
renderArray(index);
await sleep(700);
if (index >= array.length) {
stepList.push('= 0 (空数组)');
steps.innerHTML = stepList.map(s => `<div class="animate-fadeIn text-gray-300">${s}</div>`).join('');
stack.pop();
updateCallStack([...stack]);
return 0;
}
const rest = await sum(array, index + 1, stack);
const total = array[index] + rest;
stepList.push(`${array[index]} + ${rest} = ${total}`);
steps.innerHTML = stepList.map(s => `<div class="animate-fadeIn text-gray-300">${s}</div>`).join('');
stack.pop();
updateCallStack([...stack]);
result.textContent = `总和: ${total}`;
return total;
}
renderArray();
await sum(arr);
};
}
// 5. 字符串反转动画
function initStringReverse() {
const area = document.getElementById('animation-area');
area.innerHTML = `
<div class="flex flex-col items-center justify-center h-full">
<div class="text-gray-400 mb-2">原字符串</div>
<div id="original-str" class="flex gap-1 mb-8"></div>
<div class="text-gray-400 mb-2">反转过程</div>
<div id="reverse-process" class="flex gap-1 mb-4"></div>
<div id="reverse-result" class="text-2xl font-bold text-green-400 mt-4"></div>
</div>
`;
demos[4].start = async () => {
const str = "HELLO";
const original = document.getElementById('original-str');
const process = document.getElementById('reverse-process');
const result = document.getElementById('reverse-result');
// 显示原字符串
original.innerHTML = str.split('').map((c, i) => `
<div class="w-12 h-12 bg-blue-600 rounded-lg flex items-center justify-center text-xl font-bold">${c}</div>
`).join('');
let reversed = [];
async function reverse(s, stack = []) {
if (!animationRunning) return '';
stack.push(`reverse("${s}")`);
updateCallStack([...stack]);
await sleep(600);
if (s.length <= 1) {
if (s.length === 1) {
reversed.push(s);
renderProcess();
}
stack.pop();
updateCallStack([...stack]);
return s;
}
// 取最后一个字符
const lastChar = s[s.length - 1];
reversed.push(lastChar);
renderProcess();
const rest = await reverse(s.slice(0, -1), stack);
stack.pop();
updateCallStack([...stack]);
return lastChar + rest;
}
function renderProcess() {
process.innerHTML = reversed.map((c, i) => `
<div class="w-12 h-12 bg-gradient-to-r from-purple-600 to-pink-600 rounded-lg flex items-center justify-center text-xl font-bold animate-fadeIn">${c}</div>
`).join('');
}
const reversed_str = await reverse(str);
result.textContent = `结果: ${reversed_str}`;
};
}
// 6. 二分查找动画
function initBinarySearch() {
const area = document.getElementById('animation-area');
area.innerHTML = `
<div class="flex flex-col items-center justify-center h-full">
<div class="text-gray-400 mb-2">有序数组 (查找: <span class="text-yellow-400 font-bold">23</span>)</div>
<div id="bs-array" class="flex gap-1 mb-6"></div>
<div id="bs-status" class="text-xl text-center"></div>
<div id="bs-result" class="text-2xl font-bold text-green-400 mt-4"></div>
</div>
`;
demos[5].start = async () => {
const arr = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91];
const target = 23;
const arrayDiv = document.getElementById('bs-array');
const status = document.getElementById('bs-status');
const result = document.getElementById('bs-result');
function renderArray(low, high, mid = -1) {
arrayDiv.innerHTML = arr.map((n, i) => {
let classes = 'w-10 h-12 rounded-lg flex items-center justify-center font-bold transition-all ';
if (i === mid) {
classes += 'bg-yellow-500 scale-110';
} else if (i >= low && i <= high) {
classes += 'bg-blue-600';
} else {
classes += 'bg-gray-700 opacity-50';
}
return `<div class="${classes}">${n}</div>`;
}).join('');
}
async function binarySearch(low, high, stack = []) {
if (!animationRunning) return -1;
stack.push(`search(${low}, ${high})`);
updateCallStack([...stack]);
renderArray(low, high);
await sleep(800);
if (low > high) {
status.innerHTML = '<span class="text-red-400">未找到目标!</span>';
stack.pop();
updateCallStack([...stack]);
return -1;
}
const mid = Math.floor((low + high) / 2);
renderArray(low, high, mid);
status.innerHTML = `检查中间位置 [${mid}] = <span class="text-yellow-400">${arr[mid]}</span>`;
await sleep(1000);
if (arr[mid] === target) {
status.innerHTML = `<span class="text-green-400">找到了! arr[${mid}] = ${target}</span>`;
result.textContent = `✅ 找到目标,位置: ${mid}`;
stack.pop();
updateCallStack([...stack]);
return mid;
}
if (arr[mid] > target) {
status.innerHTML = `${arr[mid]} > ${target}, 向左搜索`;
await sleep(500);
const res = await binarySearch(low, mid - 1, stack);
stack.pop();
updateCallStack([...stack]);
return res;
} else {
status.innerHTML = `${arr[mid]} < ${target}, 向右搜索`;
await sleep(500);
const res = await binarySearch(mid + 1, high, stack);
stack.pop();
updateCallStack([...stack]);
return res;
}
}
renderArray(0, arr.length - 1);
await binarySearch(0, arr.length - 1);
};
}
// 7. 汉诺塔动画
function initHanoi() {
const area = document.getElementById('animation-area');
area.innerHTML = `
<div class="flex flex-col h-full">
<div id="hanoi-towers" class="flex justify-around items-end flex-1 pb-4">
<div class="tower" id="tower-A"></div>
<div class="tower" id="tower-B"></div>
<div class="tower" id="tower-C"></div>
</div>
<div class="flex justify-around text-center">
<div class="w-24 text-lg font-bold text-cyan-400">A</div>
<div class="w-24 text-lg font-bold text-cyan-400">B</div>
<div class="w-24 text-lg font-bold text-cyan-400">C</div>
</div>
<div id="hanoi-status" class="text-center mt-4 text-lg"></div>
</div>
`;
demos[6].start = async () => {
const towers = { A: [3, 2, 1], B: [], C: [] };
const colors = ['#ef4444', '#22c55e', '#3b82f6'];
const status = document.getElementById('hanoi-status');
function renderTowers() {
['A', 'B', 'C'].forEach(name => {
const tower = document.getElementById(`tower-${name}`);
tower.innerHTML = `
<div class="flex flex-col-reverse items-center">
<div class="w-24 h-2 bg-gray-600 rounded"></div>
${towers[name].map(disk => `
<div class="h-6 rounded transition-all" style="width: ${disk * 25 + 20}px; background: ${colors[disk-1]}"></div>
`).join('')}
</div>
`;
});
}
async function hanoi(n, from, to, aux, stack = []) {
if (!animationRunning) return;
stack.push(`hanoi(${n}, ${from}, ${to})`);
updateCallStack([...stack]);
if (n === 1) {
const disk = towers[from].pop();
towers[to].push(disk);
status.innerHTML = `移动盘子 <span class="text-yellow-400">${disk}</span> 从 <span class="text-cyan-400">${from}</span> 到 <span class="text-green-400">${to}</span>`;
renderTowers();
await sleep(800);
stack.pop();
updateCallStack([...stack]);
return;
}
await hanoi(n - 1, from, aux, to, stack);
const disk = towers[from].pop();
towers[to].push(disk);
status.innerHTML = `移动盘子 <span class="text-yellow-400">${disk}</span> 从 <span class="text-cyan-400">${from}</span> 到 <span class="text-green-400">${to}</span>`;
renderTowers();
await sleep(800);
await hanoi(n - 1, aux, to, from, stack);
stack.pop();
updateCallStack([...stack]);
}
renderTowers();
await hanoi(3, 'A', 'C', 'B');
status.innerHTML = '<span class="text-green-400">✅ 完成!</span>';
};
}
// 8. 树遍历动画
function initTreeTraversal() {
const area = document.getElementById('animation-area');
area.innerHTML = `
<div class="flex flex-col items-center h-full">
<svg id="tree-svg" width="100%" height="200" class="mb-4"></svg>
<div class="text-gray-400 mb-2">前序遍历结果:</div>
<div id="traversal-result" class="flex gap-2 flex-wrap justify-center"></div>
</div>
`;
demos[7].start = async () => {
const tree = {
val: 1,
left: {
val: 2,
left: { val: 4, left: null, right: null },
right: { val: 5, left: null, right: null }
},
right: {
val: 3,
left: { val: 6, left: null, right: null },
right: { val: 7, left: null, right: null }
}
};
const positions = {
1: { x: 50, y: 20 },
2: { x: 25, y: 50 },
3: { x: 75, y: 50 },
4: { x: 12, y: 80 },
5: { x: 38, y: 80 },
6: { x: 62, y: 80 },
7: { x: 88, y: 80 }
};
const svg = document.getElementById('tree-svg');
const result = document.getElementById('traversal-result');
let visited = [];
let nodeStates = {};
function renderTree() {
const width = svg.clientWidth || 400;
const height = 200;
// 绘制连线
let lines = `
<line x1="${positions[1].x}%" y1="${positions[1].y}%" x2="${positions[2].x}%" y2="${positions[2].y}%" stroke="#4B5563" stroke-width="2"/>
<line x1="${positions[1].x}%" y1="${positions[1].y}%" x2="${positions[3].x}%" y2="${positions[3].y}%" stroke="#4B5563" stroke-width="2"/>
<line x1="${positions[2].x}%" y1="${positions[2].y}%" x2="${positions[4].x}%" y2="${positions[4].y}%" stroke="#4B5563" stroke-width="2"/>
<line x1="${positions[2].x}%" y1="${positions[2].y}%" x2="${positions[5].x}%" y2="${positions[5].y}%" stroke="#4B5563" stroke-width="2"/>
<line x1="${positions[3].x}%" y1="${positions[3].y}%" x2="${positions[6].x}%" y2="${positions[6].y}%" stroke="#4B5563" stroke-width="2"/>
<line x1="${positions[3].x}%" y1="${positions[3].y}%" x2="${positions[7].x}%" y2="${positions[7].y}%" stroke="#4B5563" stroke-width="2"/>
`;
// 绘制节点
let nodes = '';
for (let i = 1; i <= 7; i++) {
const state = nodeStates[i] || 'default';
const color = state === 'visiting' ? '#EAB308' : state === 'visited' ? '#22C55E' : '#3B82F6';
nodes += `
<circle cx="${positions[i].x}%" cy="${positions[i].y}%" r="20" fill="${color}" class="transition-all"/>
<text x="${positions[i].x}%" y="${positions[i].y}%" text-anchor="middle" dy="6" fill="white" font-weight="bold" font-size="16">${i}</text>
`;
}
svg.innerHTML = lines + nodes;
}
async function preorder(node, stack = []) {
if (!node || !animationRunning) return;
stack.push(`visit(${node.val})`);
updateCallStack([...stack]);
// 访问当前节点
nodeStates[node.val] = 'visiting';
renderTree();
await sleep(600);
nodeStates[node.val] = 'visited';
visited.push(node.val);
result.innerHTML = visited.map(v => `
<div class="w-10 h-10 bg-green-600 rounded-full flex items-center justify-center font-bold animate-fadeIn">${v}</div>
`).join('');
renderTree();
await sleep(400);
// 递归左子树
await preorder(node.left, stack);
// 递归右子树
await preorder(node.right, stack);
stack.pop();
updateCallStack([...stack]);
}
renderTree();
await preorder(tree);
};
}
// 9. 快速排序动画
function initQuickSort() {
const area = document.getElementById('animation-area');
area.innerHTML = `
<div class="flex flex-col items-center justify-center h-full">
<div id="qs-array" class="flex gap-1 items-end mb-6 h-40"></div>
<div id="qs-status" class="text-lg text-center"></div>
</div>
`;
demos[8].start = async () => {
let arr = [64, 34, 25, 12, 22, 11, 90, 45];
const arrayDiv = document.getElementById('qs-array');
const status = document.getElementById('qs-status');
function renderArray(arr, pivotIdx = -1, leftRange = [], rightRange = []) {
arrayDiv.innerHTML = arr.map((n, i) => {
let color = 'bg-blue-600';
if (i === pivotIdx) color = 'bg-yellow-500';
else if (leftRange.includes(i)) color = 'bg-green-600';
else if (rightRange.includes(i)) color = 'bg-purple-600';
return `
<div class="w-10 rounded-t-lg flex flex-col items-center justify-end transition-all ${color}" style="height: ${n * 1.5}px">
<span class="text-xs font-bold mb-1">${n}</span>
</div>
`;
}).join('');
}
async function quickSort(arr, left = 0, right = arr.length - 1, stack = []) {
if (!animationRunning) return arr;
if (left >= right) return arr;
stack.push(`qs(${left}, ${right})`);
updateCallStack([...stack]);
// 选择基准
const pivotIdx = left;
const pivot = arr[pivotIdx];
status.innerHTML = `基准值: <span class="text-yellow-400">${pivot}</span>`;
renderArray(arr, pivotIdx);
await sleep(800);
// 分区
let i = left + 1;
let j = right;
while (i <= j) {
while (i <= right && arr[i] <= pivot) i++;
while (j > left && arr[j] > pivot) j--;
if (i < j) {
[arr[i], arr[j]] = [arr[j], arr[i]];
renderArray(arr, pivotIdx);
await sleep(400);
}
}
// 交换基准到正确位置
[arr[left], arr[j]] = [arr[j], arr[left]];
renderArray(arr, j);
status.innerHTML = `基准值 ${pivot} 放到位置 ${j}`;
await sleep(600);
// 递归排序左右两部分
await quickSort(arr, left, j - 1, stack);
await quickSort(arr, j + 1, right, stack);
stack.pop();
updateCallStack([...stack]);
return arr;
}
renderArray(arr);
await quickSort(arr);
status.innerHTML = '<span class="text-green-400">✅ 排序完成!</span>';
renderArray(arr);
};
}
// 10. 分形图动画
function initFractal() {
const area = document.getElementById('animation-area');
area.innerHTML = `
<div class="flex flex-col items-center h-full">
<div class="mb-4">
<label class="text-gray-400 mr-2">递归深度:</label>
<input type="range" id="fractal-depth" min="0" max="6" value="4" class="w-32">
<span id="depth-value" class="text-cyan-400 ml-2">4</span>
</div>
<canvas id="fractal-canvas" width="400" height="280" class="fractal-canvas rounded-xl"></canvas>
</div>
`;
const depthSlider = document.getElementById('fractal-depth');
const depthValue = document.getElementById('depth-value');
depthSlider.addEventListener('input', () => {
depthValue.textContent = depthSlider.value;
});
demos[9].start = async () => {
const canvas = document.getElementById('fractal-canvas');
const ctx = canvas.getContext('2d');
const maxDepth = parseInt(document.getElementById('fractal-depth').value);
ctx.clearRect(0, 0, canvas.width, canvas.height);
const colors = ['#06B6D4', '#8B5CF6', '#EC4899', '#F59E0B', '#10B981', '#3B82F6', '#EF4444'];
function drawTriangle(x, y, size, color) {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x - size / 2, y + size * 0.866);
ctx.lineTo(x + size / 2, y + size * 0.866);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
async function sierpinski(x, y, size, depth, stack = []) {
if (!animationRunning) return;
stack.push(`sierpinski(${depth})`);
updateCallStack([...stack]);
if (depth === 0) {
drawTriangle(x, y, size, colors[Math.floor(Math.random() * colors.length)]);
await sleep(20);
stack.pop();
updateCallStack([...stack]);
return;
}
const half = size / 2;
const h = half * 0.866;
await sierpinski(x, y, half, depth - 1, stack);
await sierpinski(x - half / 2, y + h, half, depth - 1, stack);
await sierpinski(x + half / 2, y + h, half, depth - 1, stack);
stack.pop();
updateCallStack([...stack]);
}
await sierpinski(canvas.width / 2, 20, 300, maxDepth);
};
}
function sleep(ms) {
return new Promise(resolve => {
animationId = setTimeout(resolve, ms);
});
}
// 初始化第一个演示
showDemo(0);
</script>
</body>
</html>