feat: Implement Min Path Sum, Flower Planting, and Advantage Shuffle problems with corresponding backend and frontend components.
This commit is contained in:
parent
3dedaf0899
commit
8ee91428cd
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
@ -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")
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)]
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 背包
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('min_path_sum')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium ${activeTab === 'min_path_sum'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
最小路径和
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('flower_planting')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium ${activeTab === 'flower_planting'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
种花问题
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('advantage_shuffle')}
|
||||
className={`px-3 py-2 rounded-md text-sm font-medium ${activeTab === 'advantage_shuffle'
|
||||
? 'bg-blue-100 text-blue-700'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
田忌赛马
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -63,8 +93,22 @@ function App() {
|
|||
{activeTab === 'interval_scheduling' && <IntervalScheduling />}
|
||||
{activeTab === 'n_queens' && <NQueens />}
|
||||
{activeTab === 'knapsack' && <Knapsack />}
|
||||
{activeTab === 'min_path_sum' && <MinPathSum />}
|
||||
{activeTab === 'flower_planting' && <FlowerPlanting />}
|
||||
{activeTab === 'advantage_shuffle' && <AdvantageShuffle />}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer className="bg-white shadow-inner py-6 mt-auto">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-gray-600">
|
||||
<p>
|
||||
<a href="mailto:i@hissin.net" className="text-blue-600 hover:underline">
|
||||
韩天泽 (i@hissin.net)
|
||||
</a>
|
||||
{' '} | 电计4班 | 1120230489
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="p-6 space-y-8">
|
||||
<h1 className="text-3xl font-bold text-gray-800">优势洗牌 (田忌赛马)</h1>
|
||||
<p className="text-gray-600">目标:给定两个数组 A 和 B,重新排列 A,使得 A[i] {'>'} B[i] 的索引数目最大化。</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">数组长度 (N)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.size}
|
||||
onChange={(e) => setParams({ ...params, size: 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 space-y-2">
|
||||
<div>
|
||||
<h3 className="font-medium">我方马匹 (A):</h3>
|
||||
<div className="font-mono text-sm bg-blue-50 p-2 rounded overflow-x-auto">
|
||||
{currentCase.nums1.join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium">敌方马匹 (B):</h3>
|
||||
<div className="font-mono text-sm bg-red-50 p-2 rounded overflow-x-auto">
|
||||
{currentCase.nums2.join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
</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">胜场数 (Advantage)</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 font-bold">{res.advantage_score}</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)', position: 'insideBottomRight', offset: -10 }} />
|
||||
<YAxis label={{ value: '耗时 (秒)', angle: -90, position: 'insideLeft' }} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="greedy" stroke="#00ff00" name="田忌赛马(Greedy)" />
|
||||
<Line type="monotone" dataKey="random" stroke="#0000ff" name="随机乱序(Random)" />
|
||||
<Line type="monotone" dataKey="brute_force" stroke="#ff0000" name="暴力(Brute Force)" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvantageShuffle;
|
||||
|
|
@ -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 (
|
||||
<div className="p-6 space-y-8">
|
||||
<h1 className="text-3xl font-bold text-gray-800">种花问题 (Flower Planting)</h1>
|
||||
<p className="text-gray-600">目标:判断在不打破种植规则(花不能相邻)的情况下,能否在花坛中再种下 N 朵花。</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">花坛长度</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.length}
|
||||
onChange={(e) => setParams({ ...params, length: 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">目标 N (0=随机)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={params.n}
|
||||
onChange={(e) => setParams({ ...params, n: 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">花坛状态 (N={currentN}):</h3>
|
||||
<div className="font-mono bg-gray-100 p-2 rounded break-all text-sm">
|
||||
{currentCase.map(x => x === 1 ? '🌸' : '🌱').join('')}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">长度: {currentCase.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.can_plant === true ? <span className="text-green-600 font-bold">是</span> :
|
||||
res.can_plant === false ? <span className="text-red-600 font-bold">否</span> : '-'}
|
||||
</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: '花坛长度', position: 'insideBottomRight', offset: -10 }} />
|
||||
<YAxis label={{ value: '耗时 (秒)', angle: -90, position: 'insideLeft' }} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="greedy" stroke="#00ff00" name="贪心算法" />
|
||||
<Line type="monotone" dataKey="brute_force" stroke="#ff0000" name="暴力回溯" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlowerPlanting;
|
||||
|
|
@ -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 (
|
||||
<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;
|
||||
Loading…
Reference in New Issue