10个动画介绍递归(用Gemin3生成)

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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
        }

        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>
相关推荐
一条大祥脚3 小时前
26.1.1
数据结构·算法
chushiyunen5 小时前
快慢双指针算法笔记
数据结构·笔记·算法
@小码农5 小时前
202512 电子学会 Scratch图形化编程等级考试三级真题(附答案)
服务器·开发语言·数据结构·数据库·算法
报错小能手7 小时前
数据结构 字典树
开发语言·数据结构
XLYcmy7 小时前
高级密码生成器程序详解:专门设计用于生成基于用户个人信息的密码猜测组合
开发语言·数据结构·python·网络安全·数据安全·源代码·口令安全
AI科技星7 小时前
时空的固有脉动:波动方程 ∇²L = (1/c²) ∂²L/∂t² 的第一性原理推导、诠释与验证
数据结构·人工智能·算法·机器学习·重构
2401_841495647 小时前
【LeetCode刷题】寻找重复数
数据结构·python·算法·leetcode·链表·数组·重复数
Joe_Blue_028 小时前
Matlab入门案例介绍—常用的运算符及优先级
开发语言·数据结构·matlab·matlab基础入门案例介绍
C雨后彩虹8 小时前
二维伞的雨滴效应
java·数据结构·算法·华为·面试