diff --git a/backend/main.py b/backend/main.py index 14e41bf..3432e1c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -24,3 +24,11 @@ from routers import n_queens_router app.include_router(n_queens_router.router) from routers import knapsack_router app.include_router(knapsack_router.router) +from routers import min_path_sum_router +app.include_router(min_path_sum_router.router) +from routers import flower_planting_router +app.include_router(flower_planting_router.router) +from routers import advantage_shuffle_router +app.include_router(advantage_shuffle_router.router) + + diff --git a/backend/problems/advantage_shuffle/generator.py b/backend/problems/advantage_shuffle/generator.py new file mode 100644 index 0000000..f0f0661 --- /dev/null +++ b/backend/problems/advantage_shuffle/generator.py @@ -0,0 +1,7 @@ +import random +from typing import List + +def generate_advantage_shuffle_case(size: int = 10, max_val: int = 100) -> dict: + nums1 = [random.randint(0, max_val) for _ in range(size)] + nums2 = [random.randint(0, max_val) for _ in range(size)] + return {"nums1": nums1, "nums2": nums2} diff --git a/backend/problems/advantage_shuffle/problem.py b/backend/problems/advantage_shuffle/problem.py new file mode 100644 index 0000000..5e68f7f --- /dev/null +++ b/backend/problems/advantage_shuffle/problem.py @@ -0,0 +1,50 @@ +from typing import Any, Dict, List +from common.base_problem import BaseProblem +from problems.advantage_shuffle.generator import generate_advantage_shuffle_case +from problems.advantage_shuffle.solvers import solve_greedy, solve_brute_force, solve_random +from common.timer_utils import time_execution + +class AdvantageShuffleProblem(BaseProblem): + def generate_case(self, size: int = 10, max_val: int = 100) -> Dict[str, Any]: + return generate_advantage_shuffle_case(size, max_val) + + def solve(self, input_data: Dict[str, Any], algorithm: str) -> Dict[str, Any]: + nums1 = input_data.get("nums1", []) + nums2 = input_data.get("nums2", []) + + result = None + duration = 0.0 + + if algorithm == "greedy": + result, duration = time_execution(solve_greedy)(nums1, nums2) + elif algorithm == "brute_force": + result, duration = time_execution(solve_brute_force)(nums1, nums2) + elif algorithm == "random": + result, duration = time_execution(solve_random)(nums1, nums2) + else: + raise ValueError(f"Unknown algorithm: {algorithm}") + + # Calculate advantage score + score = sum(1 for a, b in zip(result, nums2) if a > b) + + return { + "algorithm": algorithm, + "shuffled_nums1": result, + "advantage_score": score, + "time_seconds": duration + } + + def verify(self, input_data: Any, result: Any) -> bool: + # Verification is hard because there might be multiple optimal solutions. + # We can verify that the score matches the greedy score (which is optimal). + nums1 = input_data.get("nums1", []) + nums2 = input_data.get("nums2", []) + + greedy_res, _ = time_execution(solve_greedy)(nums1, nums2) + max_score = sum(1 for a, b in zip(greedy_res, nums2) if a > b) + + # If algorithm claims a score, is it correct based on its output? + output = result.get("shuffled_nums1", []) + actual_score = sum(1 for a, b in zip(output, nums2) if a > b) + + return actual_score == result.get("advantage_score") diff --git a/backend/problems/advantage_shuffle/solvers.py b/backend/problems/advantage_shuffle/solvers.py new file mode 100644 index 0000000..c6a0824 --- /dev/null +++ b/backend/problems/advantage_shuffle/solvers.py @@ -0,0 +1,117 @@ +from typing import List + +def solve_greedy(nums1: List[int], nums2: List[int]) -> List[int]: + """ + Greedy algorithm (Tian Ji Horse Racing strategy). + Sort nums1. Sort nums2 but keep track of original indices. + O(N log N) + """ + n = len(nums1) + sorted_nums1 = sorted(nums1) + # Store (value, original_index) for nums2 to reconstruct answer order + sorted_nums2 = sorted([(val, i) for i, val in enumerate(nums2)]) + + res = [0] * n + + # Two pointers for nums2 (weakest horse, strongest horse) + left_2, right_2 = 0, n - 1 + + # Iterate through our horses (nums1) from weakest to strongest? + # Actually standard implementation: + # Compare our weakest with their weakest. + # If ours > theirs, use it (win). + # Else, use our weakest to burn their strongest (lose). + + # Let's use a deque or two pointers on nums1 as well? + # Actually, iterating nums1 from start to end (weakest to strongest) works with logic: + # If current nums1 can beat current weakest nums2, do it. + # Else, sacrifice current nums1 against current strongest nums2. + + # Re-verification of strategy: + # We want to maximize wins. + # Consider our sorted nums1. + # Consider sorted nums2. + # If nums1[smallest] > nums2[smallest], we win! Assign nums1[smallest] to nums2[smallest]'s slot. + # If not, nums1[smallest] is useless for winning against weakest. Is it useful for anything? + # No, it's the worst horse. Sacrifice it against nums2[largest]. + + left_1, right_1 = 0, n - 1 + left_2, right_2 = 0, n - 1 + + # We need to assign values to the original indices of nums2 + + # Wait, standard approach iterates nums1 (sorted) or nums2 (sorted)? + # Let's process nums1 from strongest to weakest? Or weakest to strongest? + + # Let's use the standard LC 870 approach: + # Sort A. Sort B (with indices). + # For each b in sorted B (weakest to strongest): + # pick smallest a > b. + # If exists, use it. + # If not, use smallest a available (sacrifice). + + # Better Tian Ji Logic: + # Compare our fastest with their fastest. + # If ours > theirs, win. + # Else (ours <= theirs), compare our slowest with their slowest. + # If ours > theirs, win. + # Else (ours <= theirs), race our slowest against their fastest. + + # Let's implement the standard Greedy for "Advantage Shuffle" (Maximize A[i] > B[i] count): + # 1. Sort A. + # 2. Sort B with indices. + # 3. Use deque for A. + # Iterate B from strongest to weakest? + # Actually: + # Iterate B sorted (strongest to weakest). + # If A's strongest > B's strongest: Match them. + # Else: A's strongest cannot beat B's strongest. Nothing can. + # So sacrifice A's weakest against B's strongest. + + import collections + deque_nums1 = collections.deque(sorted_nums1) + sorted_nums2_desc = sorted([(val, i) for i, val in enumerate(nums2)], key=lambda x: -x[0]) + + for val_b, idx_b in sorted_nums2_desc: + if deque_nums1[-1] > val_b: + res[idx_b] = deque_nums1.pop() # Strongest vs Strongest (Win) + else: + res[idx_b] = deque_nums1.popleft() # Weakest vs Strongest (Sacrifice) + + return res + +def solve_random(nums1: List[int], nums2: List[int]) -> List[int]: + """ + Random shuffling algorithm (Naive). + O(N) per attempt, but effectively random performance. + Just returns a random permutation of nums1. + """ + import random + res = nums1[:] + random.shuffle(res) + return res + +def solve_brute_force(nums1: List[int], nums2: List[int]) -> List[int]: + """ + Try all permutations of nums1 to find max advantage. + O(N!) - Extremely slow. + """ + import itertools + n = len(nums1) + + # Safety limit + if n > 8: + return solve_greedy(nums1, nums2) + + max_score = -1 + best_perm = [] + + for perm in itertools.permutations(nums1): + score = sum(1 for a, b in zip(perm, nums2) if a > b) + if score > max_score: + max_score = score + best_perm = list(perm) + if score == n: # Optimization: max possible found + break + + return best_perm diff --git a/backend/problems/flower_planting/generator.py b/backend/problems/flower_planting/generator.py new file mode 100644 index 0000000..e51a092 --- /dev/null +++ b/backend/problems/flower_planting/generator.py @@ -0,0 +1,37 @@ +import random +from typing import List + +def generate_flower_planting_case(length: int = 10, chance: float = 0.3) -> List[int]: + """ + Generates a flowerbed array of 0s and 1s. + 0: Empty, 1: Planted. + Ensures no adjacent 1s (valid initial state). + """ + flowerbed = [0] * length + for i in range(length): + if random.random() < chance: + # Check constraints + prev_empty = (i == 0) or (flowerbed[i-1] == 0) + # Since we fill left-to-right, we only check left neighbor carefully. + # But we must also ensure we don't invalidate a future planting? + # Actually, just randomly placing 1s might create invalid state (1,1). + # So we place 1 only if i-1 is 0. + if prev_empty: + flowerbed[i] = 1 + + # Double check right neighbor constraint (though we fill sequentially, + # randomness might need a second pass or careful construction. + # Simplified: construction guarantees i-1 is 0. + # But what if we place at i, and then at i+1? + # The loop above allows that. Let's fix logic. + + flowerbed = [0] * length + i = 0 + while i < length: + if random.random() < chance: + flowerbed[i] = 1 + i += 2 # Skip next spot to maintain validity + else: + i += 1 + + return flowerbed diff --git a/backend/problems/flower_planting/problem.py b/backend/problems/flower_planting/problem.py new file mode 100644 index 0000000..8b2e87e --- /dev/null +++ b/backend/problems/flower_planting/problem.py @@ -0,0 +1,46 @@ +from typing import Any, Dict, List +import random +from common.base_problem import BaseProblem +from problems.flower_planting.generator import generate_flower_planting_case +from problems.flower_planting.solvers import solve_greedy, solve_brute_force, max_flowers_greedy +from common.timer_utils import time_execution + +class FlowerPlantingProblem(BaseProblem): + def generate_case(self, length: int = 10, n: int = 0) -> Dict[str, Any]: + flowerbed = generate_flower_planting_case(length) + # If n is not provided (or 0), generate a challenging but possible n + # We calculate max possible, then take a random portion of it or just use it. + # Let's just return the bed and let user/frontend specify n, OR generate a random N. + # Problem requires checking if n can be planted. + if n == 0: + max_possible = max_flowers_greedy(flowerbed) + # Randomly pick n between 0 and max_possible + 1 (to sometimes return False) + n = random.randint(0, max_possible + 1) if max_possible > 0 else 1 + + return {"flowerbed": flowerbed, "n": n} + + def solve(self, input_data: Dict[str, Any], algorithm: str) -> Dict[str, Any]: + flowerbed = input_data.get("flowerbed", []) + n = input_data.get("n", 1) + + result = None + duration = 0.0 + + if algorithm == "greedy": + result, duration = time_execution(solve_greedy)(flowerbed, n) + elif algorithm == "brute_force": + result, duration = time_execution(solve_brute_force)(flowerbed, n) + else: + raise ValueError(f"Unknown algorithm: {algorithm}") + + return { + "algorithm": algorithm, + "can_plant": result, + "time_seconds": duration + } + + def verify(self, input_data: Any, result: Any) -> bool: + flowerbed = input_data.get("flowerbed", []) + n = input_data.get("n", 1) + truth, _ = time_execution(solve_greedy)(flowerbed, n) + return result.get("can_plant") == truth diff --git a/backend/problems/flower_planting/solvers.py b/backend/problems/flower_planting/solvers.py new file mode 100644 index 0000000..fa4c4d3 --- /dev/null +++ b/backend/problems/flower_planting/solvers.py @@ -0,0 +1,76 @@ +from typing import List + +def solve_greedy(flowerbed: List[int], n: int) -> bool: + """ + Greedy algorithm: Plant whenever possible. + O(N) + """ + count = 0 + # Operate on a copy to avoid modifying input in place if passed by reference + bed = flowerbed[:] + length = len(bed) + + for i in range(length): + if bed[i] == 0: + empty_left = (i == 0) or (bed[i-1] == 0) + empty_right = (i == length - 1) or (bed[i+1] == 0) + + if empty_left and empty_right: + bed[i] = 1 + count += 1 + if count >= n: + return True + + return count >= n + +def solve_brute_force(flowerbed: List[int], n: int) -> bool: + """ + Backtracking/Brute Force to see if N flowers can be planted. + O(2^N) - Highly inefficient, good for contrast. + """ + length = len(flowerbed) + + def can_plant(bed, index, remaining): + if remaining == 0: + return True + if index >= length: + return False + + # Try planting at index + if bed[index] == 0: + empty_left = (index == 0) or (bed[index-1] == 0) + empty_right = (index == length - 1) or (bed[index+1] == 0) + + if empty_left and empty_right: + # Option 1: Plant here + bed[index] = 1 + if can_plant(bed, index + 2, remaining - 1): + return True + bed[index] = 0 # Backtrack + + # Option 2: Don't plant here, try next spot + if can_plant(bed, index + 1, remaining): + return True + + return False + + # Safety for large inputs + if length > 25: + # Fallback + return solve_greedy(flowerbed, n) + + return can_plant(flowerbed[:], 0, n) + +# Helper for just counting max flowers (often the core logic needed) +def max_flowers_greedy(flowerbed: List[int]) -> int: + count = 0 + bed = flowerbed[:] + length = len(bed) + for i in range(length): + if bed[i] == 0: + empty_left = (i == 0) or (bed[i-1] == 0) + empty_right = (i == length - 1) or (bed[i+1] == 0) + if empty_left and empty_right: + bed[i] = 1 + count += 1 + return count diff --git a/backend/problems/min_path_sum/generator.py b/backend/problems/min_path_sum/generator.py new file mode 100644 index 0000000..27d591b --- /dev/null +++ b/backend/problems/min_path_sum/generator.py @@ -0,0 +1,8 @@ +import random +from typing import List + +def generate_min_path_sum_case(rows: int = 5, cols: int = 5, min_val: int = 1, max_val: int = 20) -> List[List[int]]: + """ + Generates a rows x cols grid with random integers. + """ + return [[random.randint(min_val, max_val) for _ in range(cols)] for _ in range(rows)] diff --git a/backend/problems/min_path_sum/problem.py b/backend/problems/min_path_sum/problem.py new file mode 100644 index 0000000..dd0ac64 --- /dev/null +++ b/backend/problems/min_path_sum/problem.py @@ -0,0 +1,34 @@ +from typing import Any, Dict, List +from common.base_problem import BaseProblem +from problems.min_path_sum.generator import generate_min_path_sum_case +from problems.min_path_sum.solvers import solve_dp, solve_recursion +from common.timer_utils import time_execution + +class MinPathSumProblem(BaseProblem): + def generate_case(self, rows: int = 5, cols: int = 5, min_val: int = 1, max_val: int = 20) -> Dict[str, Any]: + grid = generate_min_path_sum_case(rows, cols, min_val, max_val) + return {"grid": grid} + + def solve(self, input_data: Dict[str, Any], algorithm: str) -> Dict[str, Any]: + grid = input_data.get("grid", []) + + result = None + duration = 0.0 + + if algorithm == "dp": + result, duration = time_execution(solve_dp)(grid) + elif algorithm == "recursion": + result, duration = time_execution(solve_recursion)(grid) + else: + raise ValueError(f"Unknown algorithm: {algorithm}") + + return { + "algorithm": algorithm, + "min_path_sum": result, + "time_seconds": duration + } + + def verify(self, input_data: Any, result: Any) -> bool: + grid = input_data.get("grid", []) + truth, _ = time_execution(solve_dp)(grid) + return result.get("min_path_sum") == truth diff --git a/backend/problems/min_path_sum/solvers.py b/backend/problems/min_path_sum/solvers.py new file mode 100644 index 0000000..52a8079 --- /dev/null +++ b/backend/problems/min_path_sum/solvers.py @@ -0,0 +1,56 @@ +from typing import List + +def solve_dp(grid: List[List[int]]) -> int: + """ + Dynamic Programming solution for Minimum Path Sum. + O(M * N) + """ + if not grid or not grid[0]: + return 0 + + rows, cols = len(grid), len(grid[0]) + dp = [[0] * cols for _ in range(rows)] + + dp[0][0] = grid[0][0] + + # Initialize first row + for j in range(1, cols): + dp[0][j] = dp[0][j-1] + grid[0][j] + + # Initialize first column + for i in range(1, rows): + dp[i][0] = dp[i-1][0] + grid[i][0] + + # Fill the rest + for i in range(1, rows): + for j in range(1, cols): + dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j] + + return dp[rows-1][cols-1] + +def solve_recursion(grid: List[List[int]]) -> int: + """ + Recursive Brute Force solution (Top-Down without Memoization). + O(2^(M+N)) + Only for very small inputs! + """ + if not grid or not grid[0]: + return 0 + + rows, cols = len(grid), len(grid[0]) + + # Safety check to prevent freezing/recursion depth errors on large inputs if called accidentally + if rows + cols > 18: + # Fallback to DP for safety if input is too large for naive recursion + return solve_dp(grid) + + def helper(r, c): + if r == 0 and c == 0: + return grid[0][0] + + if r < 0 or c < 0: + return float('inf') + + return grid[r][c] + min(helper(r-1, c), helper(r, c-1)) + + return helper(rows-1, cols-1) diff --git a/backend/routers/advantage_shuffle_router.py b/backend/routers/advantage_shuffle_router.py new file mode 100644 index 0000000..067f1e2 --- /dev/null +++ b/backend/routers/advantage_shuffle_router.py @@ -0,0 +1,75 @@ +from fastapi import APIRouter +from pydantic import BaseModel +from typing import List, Any +from problems.advantage_shuffle.problem import AdvantageShuffleProblem + +router = APIRouter( + prefix="/advantage_shuffle", + tags=["advantage_shuffle"], + responses={404: {"description": "Not found"}}, +) + +problem_solver = AdvantageShuffleProblem() + +class GenerateRequest(BaseModel): + size: int = 10 + max_val: int = 100 + +class SolveRequest(BaseModel): + nums1: List[int] + nums2: List[int] + algorithms: List[str] = ["greedy", "brute_force", "random"] + +class BenchmarkRequest(BaseModel): + sizes: List[int] = [5, 8, 100, 1000] + algorithms: List[str] = ["greedy", "brute_force", "random"] + +@router.post("/generate") +async def generate_case(req: GenerateRequest): + return problem_solver.generate_case(size=req.size, max_val=req.max_val) + +@router.post("/solve") +async def solve_case(req: SolveRequest): + input_data = {"nums1": req.nums1, "nums2": req.nums2} + results = [] + + for algo in req.algorithms: + try: + if algo == "brute_force" and len(req.nums1) > 8: + results.append({ + "algorithm": algo, + "error": "Input too large for Brute Force (limit 8)", + "skipped": True + }) + continue + + res = problem_solver.solve(input_data, algo) + results.append(res) + except Exception as e: + results.append({"algorithm": algo, "error": str(e)}) + return results + +@router.post("/benchmark") +async def benchmark(req: BenchmarkRequest): + benchmark_results = [] + for size in req.sizes: + input_data = problem_solver.generate_case(size=size) + + size_result = {"size": size, "algorithms": []} + for algo in req.algorithms: + try: + if algo == "brute_force" and size > 8: + size_result["algorithms"].append({ + "algorithm": algo, + "time_seconds": None, + "skipped": True + }) + continue + + res = problem_solver.solve(input_data, algo) + size_result["algorithms"].append(res) + except Exception as e: + size_result["algorithms"].append({"algorithm": algo, "error": str(e)}) + + benchmark_results.append(size_result) + return benchmark_results diff --git a/backend/routers/flower_planting_router.py b/backend/routers/flower_planting_router.py new file mode 100644 index 0000000..700e01d --- /dev/null +++ b/backend/routers/flower_planting_router.py @@ -0,0 +1,75 @@ +from fastapi import APIRouter +from pydantic import BaseModel +from typing import List, Any +from problems.flower_planting.problem import FlowerPlantingProblem + +router = APIRouter( + prefix="/flower_planting", + tags=["flower_planting"], + responses={404: {"description": "Not found"}}, +) + +problem_solver = FlowerPlantingProblem() + +class GenerateRequest(BaseModel): + length: int = 10 + n: int = 0 # 0 means auto-generate challenging N + +class SolveRequest(BaseModel): + flowerbed: List[int] + n: int + algorithms: List[str] = ["greedy", "brute_force"] + +class BenchmarkRequest(BaseModel): + sizes: List[int] = [10, 15, 20, 25] + algorithms: List[str] = ["greedy", "brute_force"] + +@router.post("/generate") +async def generate_case(req: GenerateRequest): + return problem_solver.generate_case(length=req.length, n=req.n) + +@router.post("/solve") +async def solve_case(req: SolveRequest): + input_data = {"flowerbed": req.flowerbed, "n": req.n} + results = [] + + for algo in req.algorithms: + try: + if algo == "brute_force" and len(req.flowerbed) > 25: + results.append({ + "algorithm": algo, + "error": "Input too large for Brute Force (limit 25)", + "skipped": True + }) + continue + + res = problem_solver.solve(input_data, algo) + results.append(res) + except Exception as e: + results.append({"algorithm": algo, "error": str(e)}) + return results + +@router.post("/benchmark") +async def benchmark(req: BenchmarkRequest): + benchmark_results = [] + for size in req.sizes: + input_data = problem_solver.generate_case(length=size) + + size_result = {"size": size, "algorithms": []} + for algo in req.algorithms: + try: + if algo == "brute_force" and size > 25: + size_result["algorithms"].append({ + "algorithm": algo, + "time_seconds": None, + "skipped": True + }) + continue + + res = problem_solver.solve(input_data, algo) + size_result["algorithms"].append(res) + except Exception as e: + size_result["algorithms"].append({"algorithm": algo, "error": str(e)}) + + benchmark_results.append(size_result) + return benchmark_results diff --git a/backend/routers/min_path_sum_router.py b/backend/routers/min_path_sum_router.py new file mode 100644 index 0000000..78fc389 --- /dev/null +++ b/backend/routers/min_path_sum_router.py @@ -0,0 +1,82 @@ +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel +from typing import List, Optional, Dict, Any +from problems.min_path_sum.problem import MinPathSumProblem + +router = APIRouter( + prefix="/min_path_sum", + tags=["min_path_sum"], + responses={404: {"description": "Not found"}}, +) + +problem_solver = MinPathSumProblem() + +class GenerateRequest(BaseModel): + rows: int = 5 + cols: int = 5 + min_val: int = 1 + max_val: int = 20 + +class SolveRequest(BaseModel): + grid: List[List[int]] + algorithms: List[str] = ["dp", "recursion"] + +class BenchmarkRequest(BaseModel): + sizes: List[int] = [4, 6, 8, 10, 12] # Assuming square grids for simplicity in benchmark input, or handle appropriately + algorithms: List[str] = ["dp", "recursion"] + +@router.post("/generate") +async def generate_case(req: GenerateRequest): + return problem_solver.generate_case(rows=req.rows, cols=req.cols, min_val=req.min_val, max_val=req.max_val) + +@router.post("/solve") +async def solve_case(req: SolveRequest): + input_data = {"grid": req.grid} + results = [] + + # Check dimensions for safety check if needed + rows = len(req.grid) + cols = len(req.grid[0]) if rows > 0 else 0 + + for algo in req.algorithms: + try: + # Recursion safety limit + if algo == "recursion" and (rows + cols > 18): + results.append({ + "algorithm": algo, + "error": "Input too large for Recursion (limit rows+cols <= 18)", + "skipped": True + }) + continue + + res = problem_solver.solve(input_data, algo) + results.append(res) + except ValueError as e: + results.append({"algorithm": algo, "error": str(e)}) + return results + +@router.post("/benchmark") +async def benchmark(req: BenchmarkRequest): + benchmark_results = [] + for size in req.sizes: + # Use size as both rows and cols for benchmarking + input_data = problem_solver.generate_case(rows=size, cols=size) + + size_result = {"size": size, "algorithms": []} + for algo in req.algorithms: + try: + if algo == "recursion" and (size + size > 18): + size_result["algorithms"].append({ + "algorithm": algo, + "time_seconds": None, + "skipped": True + }) + continue + + res = problem_solver.solve(input_data, algo) + size_result["algorithms"].append(res) + except Exception as e: + size_result["algorithms"].append({"algorithm": algo, "error": str(e)}) + + benchmark_results.append(size_result) + return benchmark_results diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5dbe349..1e82ff4 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3,6 +3,9 @@ import MaxSubarray from './components/MaxSubarray'; import IntervalScheduling from './components/IntervalScheduling'; import NQueens from './components/NQueens'; import Knapsack from './components/Knapsack'; +import MinPathSum from './components/MinPathSum'; +import FlowerPlanting from './components/FlowerPlanting'; +import AdvantageShuffle from './components/AdvantageShuffle'; function App() { const [activeTab, setActiveTab] = useState('max_subarray'); @@ -51,6 +54,33 @@ function App() { > 0/1 背包 + + + @@ -63,8 +93,22 @@ function App() { {activeTab === 'interval_scheduling' && } {activeTab === 'n_queens' && } {activeTab === 'knapsack' && } + {activeTab === 'min_path_sum' && } + {activeTab === 'flower_planting' && } + {activeTab === 'advantage_shuffle' && } + + ); } diff --git a/frontend/src/api.js b/frontend/src/api.js index 3155545..18992f1 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -101,3 +101,82 @@ export const runKnapsackBenchmark = async (sizes, algorithms) => { }); return response.data; }; + +// Min Path Sum API +export const generateMinPathSumCase = async (rows, cols, minVal, maxVal) => { + const response = await axios.post(`${API_URL}/min_path_sum/generate`, { + rows, + cols, + min_val: minVal, + max_val: maxVal + }); + return response.data; +}; + +export const solveMinPathSumCase = async (grid, algorithms) => { + const response = await axios.post(`${API_URL}/min_path_sum/solve`, { + grid, + algorithms + }); + return response.data; +}; + +export const runMinPathSumBenchmark = async (sizes, algorithms) => { + const response = await axios.post(`${API_URL}/min_path_sum/benchmark`, { + sizes, + algorithms + }); + return response.data; +}; + +// Flower Planting API +export const generateFlowerCase = async (length, n) => { + const response = await axios.post(`${API_URL}/flower_planting/generate`, { + length, + n + }); + return response.data; +}; + +export const solveFlowerCase = async (flowerbed, n, algorithms) => { + const response = await axios.post(`${API_URL}/flower_planting/solve`, { + flowerbed, + n, + algorithms + }); + return response.data; +}; + +export const runFlowerBenchmark = async (sizes, algorithms) => { + const response = await axios.post(`${API_URL}/flower_planting/benchmark`, { + sizes, + algorithms + }); + return response.data; +}; + +// Advantage Shuffle API +export const generateAdvantageCase = async (size, maxVal) => { + const response = await axios.post(`${API_URL}/advantage_shuffle/generate`, { + size, + max_val: maxVal + }); + return response.data; +}; + +export const solveAdvantageCase = async (nums1, nums2, algorithms) => { + const response = await axios.post(`${API_URL}/advantage_shuffle/solve`, { + nums1, + nums2, + algorithms + }); + return response.data; +}; + +export const runAdvantageBenchmark = async (sizes, algorithms) => { + const response = await axios.post(`${API_URL}/advantage_shuffle/benchmark`, { + sizes, + algorithms + }); + return response.data; +}; diff --git a/frontend/src/components/AdvantageShuffle.jsx b/frontend/src/components/AdvantageShuffle.jsx new file mode 100644 index 0000000..8379728 --- /dev/null +++ b/frontend/src/components/AdvantageShuffle.jsx @@ -0,0 +1,244 @@ +import React, { useState } from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { generateAdvantageCase, solveAdvantageCase, runAdvantageBenchmark } from '../api'; + +const AdvantageShuffle = () => { + const [params, setParams] = useState({ size: 10, max: 100 }); + const [currentCase, setCurrentCase] = useState(null); + const [solveResults, setSolveResults] = useState(null); + const [benchmarkResults, setBenchmarkResults] = useState(null); + const [loading, setLoading] = useState(false); + + const [selectedAlgorithms, setSelectedAlgorithms] = useState({ + greedy: true, + random: true, + brute_force: true + }); + + const algorithmsList = [ + { id: 'greedy', name: '田忌赛马策略 (Greedy) O(N log N)' }, + { id: 'random', name: '随机洗牌 (Random)' }, + { id: 'brute_force', name: '暴力全排列 (Brute Force) O(N!)' } + ]; + + const getSelectedAlgoKeys = () => { + return Object.keys(selectedAlgorithms).filter(k => selectedAlgorithms[k]); + }; + + const handleGenerate = async () => { + setLoading(true); + try { + const data = await generateAdvantageCase(params.size, params.max); + setCurrentCase(data); + 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 solveAdvantageCase(currentCase.nums1, currentCase.nums2, 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 { + const sizes = [5, 7, 9, 11, 100, 500]; + const results = await runAdvantageBenchmark(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 ( +
+

优势洗牌 (田忌赛马)

+

目标:给定两个数组 A 和 B,重新排列 A,使得 A[i] {'>'} B[i] 的索引数目最大化。

+ + {/* Algorithm Selection */} +
+

算法选择

+
+ {algorithmsList.map(algo => ( + + ))} +
+
+ + {/* Section 1: Generate & Solve */} +
+
+

1. 生成测试用例

+
+
+
+ + setParams({ ...params, size: parseInt(e.target.value) })} + className="mt-1 block w-full border rounded-md p-2" + /> +
+
+ + setParams({ ...params, max: parseInt(e.target.value) })} + className="mt-1 block w-full border rounded-md p-2" + /> +
+
+ +
+ + {currentCase && ( +
+
+

我方马匹 (A):

+
+ {currentCase.nums1.join(', ')} +
+
+
+

敌方马匹 (B):

+
+ {currentCase.nums2.join(', ')} +
+
+
+ )} +
+ +
+

2. 求解与验证

+ + + {solveResults && ( +
+ + + + + + + + + + {solveResults.map((res, idx) => ( + + + + + + ))} + +
算法胜场数 (Advantage)耗时 (秒)
{res.algorithm}{res.advantage_score}{res.time_seconds?.toFixed(6)}
+
+ )} +
+
+ + {/* Section 2: Benchmark */} +
+
+

3. 性能对比测试

+ +
+ + {benchmarkResults && ( +
+ + + + + + + + + + + + +
+ )} +
+
+ ); +}; + +export default AdvantageShuffle; diff --git a/frontend/src/components/FlowerPlanting.jsx b/frontend/src/components/FlowerPlanting.jsx new file mode 100644 index 0000000..e824b56 --- /dev/null +++ b/frontend/src/components/FlowerPlanting.jsx @@ -0,0 +1,239 @@ +import React, { useState } from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { generateFlowerCase, solveFlowerCase, runFlowerBenchmark } from '../api'; + +const FlowerPlanting = () => { + const [params, setParams] = useState({ length: 10, n: 0 }); + const [currentCase, setCurrentCase] = useState(null); + const [currentN, setCurrentN] = useState(0); + const [solveResults, setSolveResults] = useState(null); + const [benchmarkResults, setBenchmarkResults] = useState(null); + const [loading, setLoading] = useState(false); + + const [selectedAlgorithms, setSelectedAlgorithms] = useState({ + greedy: true, + brute_force: true + }); + + const algorithmsList = [ + { id: 'greedy', name: '贪心算法 (Greedy) O(N)' }, + { id: 'brute_force', name: '回溯暴力 (Brute Force) O(2^N)' } + ]; + + const getSelectedAlgoKeys = () => { + return Object.keys(selectedAlgorithms).filter(k => selectedAlgorithms[k]); + }; + + const handleGenerate = async () => { + setLoading(true); + try { + const data = await generateFlowerCase(params.length, params.n); + setCurrentCase(data.flowerbed); + setCurrentN(data.n); + 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 solveFlowerCase(currentCase, currentN, 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 { + const sizes = [10, 15, 20, 25, 28]; + const results = await runFlowerBenchmark(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 ( +
+

种花问题 (Flower Planting)

+

目标:判断在不打破种植规则(花不能相邻)的情况下,能否在花坛中再种下 N 朵花。

+ + {/* Algorithm Selection */} +
+

算法选择

+
+ {algorithmsList.map(algo => ( + + ))} +
+
+ + {/* Section 1: Generate & Solve */} +
+
+

1. 生成测试用例

+
+
+
+ + setParams({ ...params, length: parseInt(e.target.value) })} + className="mt-1 block w-full border rounded-md p-2" + /> +
+
+ + setParams({ ...params, n: parseInt(e.target.value) })} + className="mt-1 block w-full border rounded-md p-2" + /> +
+
+ +
+ + {currentCase && ( +
+

花坛状态 (N={currentN}):

+
+ {currentCase.map(x => x === 1 ? '🌸' : '🌱').join('')} +
+

长度: {currentCase.length}

+
+ )} +
+ +
+

2. 求解与验证

+ + + {solveResults && ( +
+ + + + + + + + + + {solveResults.map((res, idx) => ( + + + + + + ))} + +
算法能否种下?耗时 (秒)
{res.algorithm} + {res.can_plant === true ? : + res.can_plant === false ? : '-'} + {res.time_seconds?.toFixed(6)}
+
+ )} +
+
+ + {/* Section 2: Benchmark */} +
+
+

3. 性能对比测试

+ +
+ + {benchmarkResults && ( +
+ + + + + + + + + + + +
+ )} +
+
+ ); +}; + +export default FlowerPlanting; diff --git a/frontend/src/components/MinPathSum.jsx b/frontend/src/components/MinPathSum.jsx new file mode 100644 index 0000000..4d2776c --- /dev/null +++ b/frontend/src/components/MinPathSum.jsx @@ -0,0 +1,267 @@ +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 ( +
+

最小路径和 (Minimum Path Sum)

+

目标:寻找从网格左上角到右下角的路径,使得路径上的数字总和最小。每次只能向下或向右移动。

+ + {/* Algorithm Selection */} +
+

算法选择

+
+ {algorithmsList.map(algo => ( + + ))} +
+
+ + {/* Section 1: Generate & Solve */} +
+
+

1. 生成测试用例

+
+
+
+ + setParams({ ...params, rows: parseInt(e.target.value) })} + className="mt-1 block w-full border rounded-md p-2" + /> +
+
+ + setParams({ ...params, cols: parseInt(e.target.value) })} + className="mt-1 block w-full border rounded-md p-2" + /> +
+
+ + setParams({ ...params, min: parseInt(e.target.value) })} + className="mt-1 block w-full border rounded-md p-2" + /> +
+
+ + setParams({ ...params, max: parseInt(e.target.value) })} + className="mt-1 block w-full border rounded-md p-2" + /> +
+
+ +
+ + {currentCase && ( +
+

网格预览:

+
+ + + {currentCase.map((row, rIdx) => ( + + {row.map((val, cIdx) => ( + + ))} + + ))} + +
{val}
+
+

规模: {currentCase.length} x {currentCase[0].length}

+
+ )} +
+ +
+

2. 求解与验证

+ + + {solveResults && ( +
+ + + + + + + + + + {solveResults.map((res, idx) => ( + + + + + + ))} + +
算法最小路径和耗时 (秒)
{res.algorithm}{res.min_path_sum}{res.time_seconds?.toFixed(6)}
+
+ )} +
+
+ + {/* Section 2: Benchmark */} +
+
+

3. 性能对比测试

+ +
+ + {benchmarkResults && ( +
+ + + + + + + + + + + +
+ )} +
+
+ ); +}; + +export default MinPathSum;