加时间复杂度
This commit is contained in:
+16
-4
@@ -16,10 +16,22 @@ export const chapters = [
|
||||
'递归算法复杂度',
|
||||
'实验对比分析方法'
|
||||
],
|
||||
files: [
|
||||
{ path: 'c/ch1/first.c', name: 'first.c', label: '递归打印图案', description: '递归打印矩形/三角形图案,演示递归基本结构', pyPath: 'py/ch1/first.py' },
|
||||
{ path: 'c/ch1/compare.c', name: 'compare.c', label: '性能对比(累加 vs 高斯公式)', description: '比较循环累加与高斯求和公式的耗时差异,直观感受算法效率', pyPath: 'py/ch1/compare.py' },
|
||||
{ path: 'c/ch1/compareall.c', name: 'compareall.c', label: '多项性能对比', description: '更多累加与高斯公式的计时对比实验' }
|
||||
subfolders: [
|
||||
{
|
||||
name: 'complexity-demo',
|
||||
label: '🧪 时间复杂度实验',
|
||||
demo: { path: '/complexity-demo', label: '📊 动态演示', description: '通过动画直观理解 O(1)、O(log n)、O(n)、O(n log n)、O(n²)、O(2ⁿ) 的增长差异' },
|
||||
files: []
|
||||
},
|
||||
{
|
||||
name: 'code-examples',
|
||||
label: '代码示例',
|
||||
files: [
|
||||
{ path: 'c/ch1/first.c', name: 'first.c', label: '递归打印图案', description: '递归打印矩形/三角形图案,演示递归基本结构', pyPath: 'py/ch1/first.py' },
|
||||
{ path: 'c/ch1/compare.c', name: 'compare.c', label: '性能对比(累加 vs 高斯公式)', description: '比较循环累加与高斯求和公式的耗时差异,直观感受算法效率', pyPath: 'py/ch1/compare.py' },
|
||||
{ path: 'c/ch1/compareall.c', name: 'compareall.c', label: '多项性能对比', description: '更多累加与高斯公式的计时对比实验' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Home from '../views/Home.vue'
|
||||
import ChapterView from '../views/ChapterView.vue'
|
||||
import SortDemo from '../views/SortDemo.vue'
|
||||
import ComplexityDemo from '../views/ComplexityDemo.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -19,6 +20,11 @@ const routes = [
|
||||
path: '/sort-demo',
|
||||
name: 'SortDemo',
|
||||
component: SortDemo
|
||||
},
|
||||
{
|
||||
path: '/complexity-demo',
|
||||
name: 'ComplexityDemo',
|
||||
component: ComplexityDemo
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -170,6 +170,8 @@ function closeDrawer() {
|
||||
|
||||
function getFolderDescription(folderName) {
|
||||
const descriptions = {
|
||||
'complexity-demo': '通过交互式动画,直观体验不同时间复杂度算法的执行过程与操作次数差异。',
|
||||
'code-examples': '本章相关的 C 语言和 Python 代码示例。',
|
||||
mergesort: '归并排序是分治法的经典应用:将数组一分为二,分别排序后合并。时间复杂度 O(n log n)。',
|
||||
quicksort: '快速排序通过选择一个基准元素,将数组分为左右两部分,递归排序。平均时间复杂度 O(n log n)。',
|
||||
halfsearch: '二分查找在有序数组中通过每次将搜索范围缩小一半来查找目标值,时间复杂度 O(log n)。',
|
||||
|
||||
@@ -0,0 +1,853 @@
|
||||
<template>
|
||||
<div class="complexity-demo">
|
||||
<header class="demo-header">
|
||||
<div class="demo-icon">📊</div>
|
||||
<div>
|
||||
<h1 class="demo-title">时间复杂度动态演示</h1>
|
||||
<p class="demo-subtitle">通过动画和计数直观理解不同算法的时间复杂度</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 控制面板 -->
|
||||
<section class="ctrl-section">
|
||||
<div class="ctrl-row">
|
||||
<label class="ctrl-label">输入规模 n:</label>
|
||||
<input type="range" min="1" max="100" v-model.number="n" class="n-slider" />
|
||||
<span class="n-value">{{ n }}</span>
|
||||
</div>
|
||||
<div class="ctrl-row">
|
||||
<label class="ctrl-label">选择算法:</label>
|
||||
<select v-model="selectedAlgo" class="algo-select">
|
||||
<option v-for="a in algorithms" :key="a.id" :value="a.id">
|
||||
{{ a.label }} — {{ a.complexity }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ctrl-actions">
|
||||
<button class="ctrl-btn" @click="run" :disabled="running">▶ 运行演示</button>
|
||||
<button class="ctrl-btn" @click="stepForward" :disabled="running || stepIndex >= totalSteps - 1">下一步</button>
|
||||
<button class="ctrl-btn" @click="resetDemo">⟳ 重置</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 算法说明 -->
|
||||
<section class="info-section" v-if="currentAlgo">
|
||||
<div class="algo-info-card">
|
||||
<h3>{{ currentAlgo.label }}</h3>
|
||||
<div class="complexity-badges">
|
||||
<span class="badge time-badge">时间复杂度:{{ currentAlgo.complexity }}</span>
|
||||
<span class="badge space-badge">空间复杂度:{{ currentAlgo.space }}</span>
|
||||
</div>
|
||||
<p>{{ currentAlgo.description }}</p>
|
||||
<div class="op-count" v-if="opCount !== null">
|
||||
操作次数:<strong>{{ opCount }}</strong>
|
||||
<span class="op-formula">≈ {{ currentAlgo.formula.replace('n', n) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 动态演示区域 -->
|
||||
<section class="demo-section" v-if="currentAlgo">
|
||||
<div class="visualization-area">
|
||||
<!-- 数组可视化 -->
|
||||
<div class="array-display" v-if="arrayData.length">
|
||||
<div class="array-label">数据状态:</div>
|
||||
<div class="array-bars">
|
||||
<div
|
||||
v-for="(val, idx) in arrayData"
|
||||
:key="idx"
|
||||
class="array-bar-wrapper"
|
||||
:style="{ height: barHeight(val) + 'px' }"
|
||||
>
|
||||
<div
|
||||
class="array-bar"
|
||||
:class="barClass(idx)"
|
||||
:style="{ height: '100%' }"
|
||||
>
|
||||
<span class="bar-val">{{ val }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日志/步骤说明 -->
|
||||
<div class="step-log" v-if="steps.length">
|
||||
<div class="log-header">📝 执行步骤</div>
|
||||
<div class="log-body" ref="logRef">
|
||||
<div
|
||||
v-for="(step, idx) in visibleSteps"
|
||||
:key="idx"
|
||||
class="log-line"
|
||||
:class="{ current: idx === stepIndex, done: idx < stepIndex }"
|
||||
>
|
||||
<span class="log-step-num">{{ idx + 1 }}</span>
|
||||
<span class="log-text">{{ step }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 复杂度对比图表 -->
|
||||
<div class="chart-section" v-if="showChart">
|
||||
<div class="chart-header">📈 复杂度增长对比</div>
|
||||
<div class="chart-canvas">
|
||||
<div class="chart-row" v-for="item in chartData" :key="item.label">
|
||||
<div class="chart-label">{{ item.label }}</div>
|
||||
<div class="chart-bar-track">
|
||||
<div
|
||||
class="chart-bar"
|
||||
:style="{ width: item.percent + '%', backgroundColor: item.color }"
|
||||
>
|
||||
<span class="chart-bar-text" v-if="item.percent > 15">{{ item.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="nav-back">
|
||||
<router-link to="/chapter/ch1" class="back-link">← 返回第一章</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const n = ref(10)
|
||||
const selectedAlgo = ref('linear')
|
||||
const running = ref(false)
|
||||
const opCount = ref(null)
|
||||
const steps = ref([])
|
||||
const stepIndex = ref(-1)
|
||||
const arrayData = ref([])
|
||||
const highlights = ref({})
|
||||
const showChart = ref(false)
|
||||
const logRef = ref(null)
|
||||
|
||||
const algorithms = [
|
||||
{
|
||||
id: 'constant',
|
||||
label: '常数阶 — 数组随机访问',
|
||||
complexity: 'O(1)',
|
||||
space: 'O(1)',
|
||||
formula: '1',
|
||||
description: '直接通过索引访问数组元素,无论数组多大,执行时间恒定。这是最优的时间复杂度。'
|
||||
},
|
||||
{
|
||||
id: 'logarithmic',
|
||||
label: '对数阶 — 二分查找',
|
||||
complexity: 'O(log n)',
|
||||
space: 'O(1)',
|
||||
formula: '⌈log₂(n)⌉ = ',
|
||||
description: '每次将搜索范围缩小一半。即使 n 很大,需要的步数也增长很慢。例如 n=1024 时只需 10 步。'
|
||||
},
|
||||
{
|
||||
id: 'linear',
|
||||
label: '线性阶 — 线性查找',
|
||||
complexity: 'O(n)',
|
||||
space: 'O(1)',
|
||||
formula: 'n = ',
|
||||
description: '顺序遍历所有元素。执行时间与输入规模成正比,是最直观的算法复杂度。'
|
||||
},
|
||||
{
|
||||
id: 'nlogn',
|
||||
label: '线性对数阶 — 归并排序分解',
|
||||
complexity: 'O(n log n)',
|
||||
space: 'O(n)',
|
||||
formula: 'n·⌈log₂(n)⌉ = ',
|
||||
description: '常见的"分治"算法复杂度。每一层处理 n 个元素,共有 log n 层。'
|
||||
},
|
||||
{
|
||||
id: 'quadratic',
|
||||
label: '平方阶 — 冒泡排序',
|
||||
complexity: 'O(n²)',
|
||||
space: 'O(1)',
|
||||
formula: 'n(n-1)/2 = ',
|
||||
description: '双重循环嵌套,最常见于简单排序算法。当 n 翻倍时,运行时间变为原来的 4 倍。'
|
||||
},
|
||||
{
|
||||
id: 'exponential',
|
||||
label: '指数阶 — 递归斐波那契',
|
||||
complexity: 'O(2ⁿ)',
|
||||
space: 'O(n)',
|
||||
formula: '2ⁿ = ',
|
||||
description: '递归树呈指数增长。n 稍大时就会爆炸,因此实用中必须优化(如动态规划)。'
|
||||
}
|
||||
]
|
||||
|
||||
const currentAlgo = computed(() => algorithms.find(a => a.id === selectedAlgo.value))
|
||||
|
||||
const totalSteps = computed(() => steps.value.length)
|
||||
|
||||
const visibleSteps = computed(() => {
|
||||
// 最多显示最近 50 步
|
||||
if (stepIndex.value < 50) return steps.value.slice(0, Math.max(stepIndex.value + 1, 0))
|
||||
return steps.value.slice(stepIndex.value - 49, stepIndex.value + 1)
|
||||
})
|
||||
|
||||
const maxVal = computed(() => {
|
||||
if (!arrayData.value.length) return 1
|
||||
return Math.max(...arrayData.value, 1)
|
||||
})
|
||||
|
||||
function barHeight(val) {
|
||||
return Math.max(20, (val / maxVal.value) * 200)
|
||||
}
|
||||
|
||||
function barClass(idx) {
|
||||
const h = highlights.value || {}
|
||||
const classes = []
|
||||
if (h.sorted?.includes(idx)) classes.push('bar-sorted')
|
||||
if (h.comparing?.includes(idx)) classes.push('bar-comparing')
|
||||
if (h.current === idx) classes.push('bar-current')
|
||||
return classes
|
||||
}
|
||||
|
||||
function resetDemo() {
|
||||
running.value = false
|
||||
opCount.value = null
|
||||
steps.value = []
|
||||
stepIndex.value = -1
|
||||
arrayData.value = []
|
||||
highlights.value = {}
|
||||
showChart.value = false
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(r => setTimeout(r, ms))
|
||||
}
|
||||
|
||||
async function run() {
|
||||
if (running.value) return
|
||||
running.value = true
|
||||
showChart.value = false
|
||||
steps.value = []
|
||||
stepIndex.value = -1
|
||||
opCount.value = 0
|
||||
highlights.value = {}
|
||||
|
||||
const algo = selectedAlgo.value
|
||||
|
||||
switch (algo) {
|
||||
case 'constant': await runConstant(); break
|
||||
case 'logarithmic': await runLogarithmic(); break
|
||||
case 'linear': await runLinear(); break
|
||||
case 'nlogn': await runNlogn(); break
|
||||
case 'quadratic': await runQuadratic(); break
|
||||
case 'exponential': await runExponential(); break
|
||||
}
|
||||
|
||||
running.value = false
|
||||
showChart.value = true
|
||||
scrollToLogBottom()
|
||||
}
|
||||
|
||||
function scrollToLogBottom() {
|
||||
setTimeout(() => {
|
||||
if (logRef.value) {
|
||||
logRef.value.scrollTop = logRef.value.scrollHeight
|
||||
}
|
||||
}, 50)
|
||||
}
|
||||
|
||||
function addStep(msg) {
|
||||
steps.value.push(msg)
|
||||
stepIndex.value = steps.value.length - 1
|
||||
}
|
||||
|
||||
// O(1) — 数组随机访问
|
||||
async function runConstant() {
|
||||
const arr = Array.from({ length: n.value }, () => Math.floor(Math.random() * 100) + 1)
|
||||
arrayData.value = arr
|
||||
addStep(`生成包含 ${n.value} 个元素的数组`)
|
||||
await sleep(300)
|
||||
|
||||
const idx = Math.floor(Math.random() * n.value)
|
||||
highlights.value = { current: idx }
|
||||
addStep(`直接通过索引 arr[${idx}] 访问元素,执行 1 次操作`)
|
||||
await sleep(600)
|
||||
|
||||
opCount.value = 1
|
||||
addStep(`✅ 无论 n 多大,始终只需 1 次操作 → O(1)`)
|
||||
}
|
||||
|
||||
// O(log n) — 二分查找
|
||||
async function runLogarithmic() {
|
||||
const arr = Array.from({ length: n.value }, (_, i) => i + 1)
|
||||
arrayData.value = arr
|
||||
const target = Math.floor(Math.random() * n.value) + 1
|
||||
addStep(`有序数组 [1..${n.value}],查找目标值 ${target}`)
|
||||
await sleep(400)
|
||||
|
||||
let left = 0, right = n.value - 1
|
||||
let count = 0
|
||||
|
||||
while (left <= right) {
|
||||
count++
|
||||
const mid = Math.floor((left + right) / 2)
|
||||
highlights.value = { comparing: [left, right], current: mid }
|
||||
addStep(`第 ${count} 次:区间 [${left + 1}, ${right + 1}],中间索引 ${mid + 1} (值=${arr[mid]})`)
|
||||
await sleep(500)
|
||||
|
||||
if (arr[mid] === target) {
|
||||
highlights.value = { sorted: [mid], current: mid }
|
||||
addStep(`✅ 找到目标 ${target} 在位置 ${mid + 1},共 ${count} 次比较`)
|
||||
opCount.value = count
|
||||
return
|
||||
} else if (arr[mid] < target) {
|
||||
left = mid + 1
|
||||
addStep(` ${arr[mid]} < ${target},搜右侧`)
|
||||
} else {
|
||||
right = mid - 1
|
||||
addStep(` ${arr[mid]} > ${target},搜左侧`)
|
||||
}
|
||||
await sleep(400)
|
||||
}
|
||||
|
||||
addStep(`❌ 未找到,共 ${count} 次比较`)
|
||||
opCount.value = count
|
||||
}
|
||||
|
||||
// O(n) — 线性查找
|
||||
async function runLinear() {
|
||||
const arr = Array.from({ length: n.value }, () => Math.floor(Math.random() * 100))
|
||||
arrayData.value = arr
|
||||
const target = Math.floor(Math.random() * 100)
|
||||
addStep(`数组长度=${n.value},查找目标值 ${target}`)
|
||||
await sleep(400)
|
||||
|
||||
let count = 0
|
||||
for (let i = 0; i < n.value; i++) {
|
||||
count++
|
||||
highlights.value = { current: i }
|
||||
addStep(`第 ${count} 次:比较 arr[${i}] = ${arr[i]} ${arr[i] === target ? '✅ 找到' : ''}`)
|
||||
await sleep(200)
|
||||
if (arr[i] === target) {
|
||||
highlights.value = { sorted: [i] }
|
||||
addStep(`✅ 找到目标 ${target} 在位置 ${i + 1},共 ${count} 次比较`)
|
||||
opCount.value = count
|
||||
return
|
||||
}
|
||||
}
|
||||
addStep(`❌ 未找到,共 ${count} 次比较`)
|
||||
opCount.value = count
|
||||
}
|
||||
|
||||
// O(n log n) — 归并排序分解过程
|
||||
async function runNlogn() {
|
||||
const size = Math.min(n.value, 32)
|
||||
const arr = Array.from({ length: size }, () => Math.floor(Math.random() * 100))
|
||||
arrayData.value = arr
|
||||
addStep(`数组 [${arr.join(', ')}] (n=${size})`)
|
||||
await sleep(400)
|
||||
|
||||
let count = 0
|
||||
|
||||
async function mergeSort(arr, left, right, depth = 0) {
|
||||
if (left >= right) return
|
||||
const mid = Math.floor((left + right) / 2)
|
||||
count++
|
||||
highlights.value = { range: [left, right], current: mid }
|
||||
addStep(` 分解 [${left + 1}..${right + 1}] → 左[${left + 1}..${mid + 1}] 右[${mid + 2}..${right + 1}]`)
|
||||
await sleep(300)
|
||||
|
||||
await mergeSort(arr, left, mid, depth + 1)
|
||||
await mergeSort(arr, mid + 1, right, depth + 1)
|
||||
|
||||
// 合并
|
||||
const temp = []
|
||||
let i = left, j = mid + 1
|
||||
while (i <= mid && j <= right) {
|
||||
count++
|
||||
if (arr[i] <= arr[j]) temp.push(arr[i++])
|
||||
else temp.push(arr[j++])
|
||||
}
|
||||
while (i <= mid) { temp.push(arr[i++]); count++ }
|
||||
while (j <= right) { temp.push(arr[j++]); count++ }
|
||||
|
||||
for (let k = left; k <= right; k++) {
|
||||
arr[k] = temp[k - left]
|
||||
highlights.value = { sorted: Array.from({ length: right + 1 }, (_, i) => i).slice(0, k + 1) }
|
||||
}
|
||||
addStep(` 合并完成 [${left + 1}..${right + 1}]`)
|
||||
await sleep(300)
|
||||
}
|
||||
|
||||
await mergeSort(arr, 0, size - 1)
|
||||
arrayData.value = [...arr]
|
||||
const nlogn = Math.ceil(size * Math.log2(size))
|
||||
opCount.value = count
|
||||
addStep(`✅ 归并排序完成,总操作约 ${count} 次,理论值 O(n·log₂n) ≈ ${nlogn}`)
|
||||
}
|
||||
|
||||
// O(n²) — 冒泡排序
|
||||
async function runQuadratic() {
|
||||
const size = Math.min(n.value, 20)
|
||||
const arr = Array.from({ length: size }, () => Math.floor(Math.random() * 100))
|
||||
arrayData.value = arr
|
||||
addStep(`初始数组 [${arr.join(', ')}] (n=${size})`)
|
||||
await sleep(400)
|
||||
|
||||
let count = 0
|
||||
const len = arr.length
|
||||
|
||||
for (let i = 0; i < len - 1; i++) {
|
||||
for (let j = 0; j < len - 1 - i; j++) {
|
||||
count++
|
||||
highlights.value = { comparing: [j, j + 1] }
|
||||
addStep(`比较 arr[${j}]=${arr[j]} 和 arr[${j + 1}]=${arr[j + 1]}`)
|
||||
await sleep(200)
|
||||
|
||||
if (arr[j] > arr[j + 1]) {
|
||||
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
|
||||
highlights.value = { swapping: [j, j + 1] }
|
||||
addStep(` 交换 → [${arr.join(', ')}]`)
|
||||
await sleep(200)
|
||||
}
|
||||
}
|
||||
highlights.value = { sorted: Array.from({ length: len }, (_, k) => k).slice(len - 1 - i) }
|
||||
}
|
||||
|
||||
arrayData.value = [...arr]
|
||||
const expected = size * (size - 1) / 2
|
||||
opCount.value = count
|
||||
addStep(`✅ 冒泡排序完成,共 ${count} 次比较,理论值 n(n-1)/2 = ${expected}`)
|
||||
}
|
||||
|
||||
// O(2ⁿ) — 递归斐波那契
|
||||
async function runExponential() {
|
||||
const size = Math.min(n.value, 10) // 限制,防止浏览器卡死
|
||||
addStep(`递归计算 Fibonacci(${size}),展示递归调用树`)
|
||||
await sleep(400)
|
||||
|
||||
let count = 0
|
||||
let callStack = []
|
||||
|
||||
function fib(k) {
|
||||
count++
|
||||
if (k <= 1) {
|
||||
addStep(` fib(${k}) = ${k} (基准情况)`)
|
||||
return k
|
||||
}
|
||||
addStep(` fib(${k}) = fib(${k - 1}) + fib(${k - 2})`)
|
||||
const a = fib(k - 1)
|
||||
const b = fib(k - 2)
|
||||
const result = a + b
|
||||
addStep(` fib(${k}) = ${a} + ${b} = ${result}`)
|
||||
return result
|
||||
}
|
||||
|
||||
const result = fib(size)
|
||||
const expected = Math.pow(2, size)
|
||||
opCount.value = count
|
||||
addStep(`✅ Fibonacci(${size}) = ${result},递归调用 ${count} 次,理论 O(2ⁿ) ≈ ${expected}`)
|
||||
}
|
||||
|
||||
// 单步前进
|
||||
function stepForward() {
|
||||
if (stepIndex.value < totalSteps.value - 1) {
|
||||
stepIndex.value++
|
||||
scrollToLogBottom()
|
||||
}
|
||||
}
|
||||
|
||||
// 对比图表数据
|
||||
const chartData = computed(() => {
|
||||
const currentN = n.value
|
||||
const items = [
|
||||
{ label: 'O(1)', count: 1, color: '#22c55e' },
|
||||
{ label: 'O(log n)', count: Math.ceil(Math.log2(currentN)), color: '#3b82f6' },
|
||||
{ label: 'O(n)', count: currentN, color: '#f59e0b' },
|
||||
{ label: 'O(n log n)', count: Math.ceil(currentN * Math.log2(currentN)), color: '#f97316' },
|
||||
{ label: 'O(n²)', count: currentN * currentN, color: '#ef4444' },
|
||||
{ label: 'O(2ⁿ)', count: Math.min(Math.pow(2, currentN), 999999), color: '#dc2626' }
|
||||
]
|
||||
const maxCount = Math.max(...items.map(i => i.count), 1)
|
||||
return items.map(item => ({
|
||||
...item,
|
||||
percent: Math.min((item.count / maxCount) * 100, 100)
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.complexity-demo {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
padding: 28px 0 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.demo-icon {
|
||||
font-size: 40px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.demo-subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 控制面板 */
|
||||
.ctrl-section {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.ctrl-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.ctrl-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.n-slider {
|
||||
flex: 1;
|
||||
max-width: 300px;
|
||||
accent-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.n-value {
|
||||
font-family: var(--mono);
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.algo-select {
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ctrl-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ctrl-btn {
|
||||
padding: 8px 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg);
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.ctrl-btn:hover:not(:disabled) {
|
||||
background: var(--hover-bg);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.ctrl-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.ctrl-btn:first-child {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.ctrl-btn:first-child:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 算法说明 */
|
||||
.info-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.algo-info-card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.algo-info-card h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.complexity-badges {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
padding: 4px 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.time-badge {
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.space-badge {
|
||||
background: #f0fdf4;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.algo-info-card p {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.op-count {
|
||||
font-size: 15px;
|
||||
color: var(--text-primary);
|
||||
padding: 8px 14px;
|
||||
background: var(--hover-bg);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.op-formula {
|
||||
font-family: var(--mono);
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary);
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 可视化区域 */
|
||||
.visualization-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.array-display {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.array-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.array-bars {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 3px;
|
||||
min-height: 220px;
|
||||
}
|
||||
|
||||
.array-bar-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
min-width: 12px;
|
||||
}
|
||||
|
||||
.array-bar {
|
||||
width: 100%;
|
||||
background: var(--primary-color);
|
||||
border-radius: 4px 4px 0 0;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.bar-val {
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.bar-current {
|
||||
background: #f59e0b;
|
||||
}
|
||||
|
||||
.bar-comparing {
|
||||
background: #60a5fa;
|
||||
}
|
||||
|
||||
.bar-sorted {
|
||||
background: #22c55e;
|
||||
}
|
||||
|
||||
/* 步骤日志 */
|
||||
.step-log {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.log-body {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.log-line {
|
||||
padding: 5px 16px;
|
||||
font-size: 13px;
|
||||
font-family: var(--mono);
|
||||
color: var(--text-tertiary);
|
||||
line-height: 1.5;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.log-line.current {
|
||||
color: var(--text-primary);
|
||||
background: var(--active-bg);
|
||||
border-left-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.log-line.done {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.log-step-num {
|
||||
display: inline-block;
|
||||
min-width: 28px;
|
||||
color: var(--text-tertiary);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* 对比图表 */
|
||||
.chart-section {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.chart-canvas {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chart-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.chart-label {
|
||||
min-width: 90px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
font-family: var(--mono);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.chart-bar-track {
|
||||
flex: 1;
|
||||
height: 28px;
|
||||
background: var(--hover-bg);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chart-bar {
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
transition: width 0.5s ease;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.chart-bar-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.time-badge { background: #1e3a5f; color: #60a5fa; }
|
||||
.space-badge { background: #14532d; color: #4ade80; }
|
||||
}
|
||||
</style>
|
||||
+1
-1
@@ -8,7 +8,7 @@ export default defineConfig({
|
||||
port: 1025,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3001',
|
||||
target: 'http://localhost:3002',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user