268 lines
13 KiB
JavaScript
268 lines
13 KiB
JavaScript
import React, { useState } from 'react';
|
||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||
import { generateMinPathSumCase, solveMinPathSumCase, runMinPathSumBenchmark } from '../api';
|
||
|
||
const MinPathSum = () => {
|
||
const [params, setParams] = useState({ rows: 5, cols: 5, min: 1, max: 20 });
|
||
const [currentCase, setCurrentCase] = useState(null);
|
||
const [solveResults, setSolveResults] = useState(null);
|
||
const [benchmarkResults, setBenchmarkResults] = useState(null);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
const [selectedAlgorithms, setSelectedAlgorithms] = useState({
|
||
dp: true,
|
||
recursion: true
|
||
});
|
||
|
||
const algorithmsList = [
|
||
{ id: 'dp', name: '动态规划 (DP) O(MN)' },
|
||
{ id: 'recursion', name: '递归 (Recursion) O(2^(M+N))' }
|
||
];
|
||
|
||
const getSelectedAlgoKeys = () => {
|
||
return Object.keys(selectedAlgorithms).filter(k => selectedAlgorithms[k]);
|
||
};
|
||
|
||
const handleGenerate = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const data = await generateMinPathSumCase(params.rows, params.cols, params.min, params.max);
|
||
setCurrentCase(data.grid);
|
||
setSolveResults(null);
|
||
} catch (error) {
|
||
console.error("Error generating case:", error);
|
||
alert("生成测试用例失败");
|
||
}
|
||
setLoading(false);
|
||
};
|
||
|
||
const handleSolve = async () => {
|
||
if (!currentCase) return;
|
||
const algos = getSelectedAlgoKeys();
|
||
if (algos.length === 0) {
|
||
alert("请至少选择一种算法");
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
try {
|
||
const results = await solveMinPathSumCase(currentCase, algos);
|
||
setSolveResults(results);
|
||
} catch (error) {
|
||
console.error("Error solving case:", error);
|
||
alert("求解失败");
|
||
}
|
||
setLoading(false);
|
||
};
|
||
|
||
const handleBenchmark = async () => {
|
||
const algos = getSelectedAlgoKeys();
|
||
if (algos.length === 0) {
|
||
alert("请至少选择一种算法");
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
try {
|
||
// Sizes for Square grids (N x N)
|
||
const sizes = [2, 4, 6, 8, 10, 12, 14];
|
||
// Recursion will be very slow around 15x15 (2^30 operations roughly is too big, but O(2^(M+N)) is loose bound.
|
||
// Actually C(M+N-2, N-1) paths. C(28, 14) = 40 million calls. Too slow for web.
|
||
// Stick to small sizes.
|
||
|
||
const results = await runMinPathSumBenchmark(sizes, algos);
|
||
|
||
const chartData = results.map(item => {
|
||
const point = { size: item.size };
|
||
item.algorithms.forEach(algo => {
|
||
if (algo.time_seconds !== undefined && algo.time_seconds !== null) {
|
||
point[algo.algorithm] = algo.time_seconds;
|
||
}
|
||
});
|
||
return point;
|
||
});
|
||
|
||
setBenchmarkResults(chartData);
|
||
} catch (error) {
|
||
console.error("Error running benchmark:", error);
|
||
alert("性能测试失败");
|
||
}
|
||
setLoading(false);
|
||
};
|
||
|
||
const toggleAlgorithm = (algoId) => {
|
||
setSelectedAlgorithms(prev => ({
|
||
...prev,
|
||
[algoId]: !prev[algoId]
|
||
}));
|
||
};
|
||
|
||
return (
|
||
<div className="p-6 space-y-8">
|
||
<h1 className="text-3xl font-bold text-gray-800">最小路径和 (Minimum Path Sum)</h1>
|
||
<p className="text-gray-600">目标:寻找从网格左上角到右下角的路径,使得路径上的数字总和最小。每次只能向下或向右移动。</p>
|
||
|
||
{/* Algorithm Selection */}
|
||
<div className="bg-white p-6 rounded-lg shadow">
|
||
<h2 className="text-xl font-semibold mb-4">算法选择</h2>
|
||
<div className="flex flex-wrap gap-4">
|
||
{algorithmsList.map(algo => (
|
||
<label key={algo.id} className="flex items-center space-x-2 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={selectedAlgorithms[algo.id]}
|
||
onChange={() => toggleAlgorithm(algo.id)}
|
||
className="form-checkbox h-5 w-5 text-blue-600"
|
||
/>
|
||
<span className="text-gray-700">{algo.name}</span>
|
||
</label>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Section 1: Generate & Solve */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div className="bg-white p-6 rounded-lg shadow">
|
||
<h2 className="text-xl font-semibold mb-4">1. 生成测试用例</h2>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700">行数 (Rows)</label>
|
||
<input
|
||
type="number"
|
||
value={params.rows}
|
||
onChange={(e) => setParams({ ...params, rows: parseInt(e.target.value) })}
|
||
className="mt-1 block w-full border rounded-md p-2"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700">列数 (Cols)</label>
|
||
<input
|
||
type="number"
|
||
value={params.cols}
|
||
onChange={(e) => setParams({ ...params, cols: parseInt(e.target.value) })}
|
||
className="mt-1 block w-full border rounded-md p-2"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700">最小值</label>
|
||
<input
|
||
type="number"
|
||
value={params.min}
|
||
onChange={(e) => setParams({ ...params, min: parseInt(e.target.value) })}
|
||
className="mt-1 block w-full border rounded-md p-2"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700">最大值</label>
|
||
<input
|
||
type="number"
|
||
value={params.max}
|
||
onChange={(e) => setParams({ ...params, max: parseInt(e.target.value) })}
|
||
className="mt-1 block w-full border rounded-md p-2"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={handleGenerate}
|
||
disabled={loading}
|
||
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400"
|
||
>
|
||
{loading ? '处理中...' : '生成测试用例'}
|
||
</button>
|
||
</div>
|
||
|
||
{currentCase && (
|
||
<div className="mt-4">
|
||
<h3 className="font-medium mb-2">网格预览:</h3>
|
||
<div className="overflow-auto max-h-64 border rounded p-2 bg-gray-50">
|
||
<table className="table-auto border-collapse w-full text-center text-sm">
|
||
<tbody>
|
||
{currentCase.map((row, rIdx) => (
|
||
<tr key={rIdx}>
|
||
{row.map((val, cIdx) => (
|
||
<td key={cIdx} className="border px-2 py-1">{val}</td>
|
||
))}
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<p className="text-xs text-gray-500 mt-1">规模: {currentCase.length} x {currentCase[0].length}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="bg-white p-6 rounded-lg shadow">
|
||
<h2 className="text-xl font-semibold mb-4">2. 求解与验证</h2>
|
||
<button
|
||
onClick={handleSolve}
|
||
disabled={!currentCase || loading}
|
||
className="w-full bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700 disabled:bg-gray-400 mb-4"
|
||
>
|
||
运行选中算法
|
||
</button>
|
||
|
||
{solveResults && (
|
||
<div className="overflow-x-auto">
|
||
<table className="min-w-full divide-y divide-gray-200">
|
||
<thead>
|
||
<tr>
|
||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">算法</th>
|
||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">最小路径和</th>
|
||
<th className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">耗时 (秒)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-gray-200">
|
||
{solveResults.map((res, idx) => (
|
||
<tr key={idx}>
|
||
<td className="px-4 py-2 text-sm font-medium text-gray-900">{res.algorithm}</td>
|
||
<td className="px-4 py-2 text-sm text-gray-500">{res.min_path_sum}</td>
|
||
<td className="px-4 py-2 text-sm text-gray-500">{res.time_seconds?.toFixed(6)}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Section 2: Benchmark */}
|
||
<div className="bg-white p-6 rounded-lg shadow">
|
||
<div className="flex justify-between items-center mb-6">
|
||
<h2 className="text-xl font-semibold">3. 性能对比测试</h2>
|
||
<button
|
||
onClick={handleBenchmark}
|
||
disabled={loading}
|
||
className="bg-purple-600 text-white py-2 px-4 rounded-md hover:bg-purple-700 disabled:bg-gray-400"
|
||
>
|
||
运行性能测试
|
||
</button>
|
||
</div>
|
||
|
||
{benchmarkResults && (
|
||
<div className="h-96 w-full">
|
||
<ResponsiveContainer width="100%" height="100%">
|
||
<LineChart
|
||
data={benchmarkResults}
|
||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||
>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="size" label={{ value: '网格大小 (N x N)', position: 'insideBottomRight', offset: -10 }} />
|
||
<YAxis label={{ value: '耗时 (秒)', angle: -90, position: 'insideLeft' }} />
|
||
<Tooltip />
|
||
<Legend />
|
||
<Line type="monotone" dataKey="dp" stroke="#00ff00" name="动态规划" />
|
||
<Line type="monotone" dataKey="recursion" stroke="#ff0000" name="递归" />
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default MinPathSum;
|