25-cxsj-final/frontend/src/components/MinPathSum.jsx

268 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;