feat: add initial frontend and backend dependencies
This commit is contained in:
commit
3dedaf0899
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env/
|
||||||
|
.env
|
||||||
|
*.egg-info/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Node.js
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.npm
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# OS / IDE
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
thumbs.db
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Multi-stage Dockerfile for Algorithm Platform
|
||||||
|
|
||||||
|
# Stage 1: Build Frontend
|
||||||
|
FROM node:18-alpine AS frontend-build
|
||||||
|
WORKDIR /app/frontend
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY frontend/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Stage 2: Setup Backend
|
||||||
|
FROM python:3.11-slim AS backend
|
||||||
|
WORKDIR /app/backend
|
||||||
|
COPY backend/requirements.txt ./
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY backend/ ./
|
||||||
|
|
||||||
|
# Stage 3: Final - Nginx + Backend
|
||||||
|
FROM nginx:alpine
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install Python and dependencies in final stage
|
||||||
|
RUN apk add --no-cache python3 py3-pip
|
||||||
|
|
||||||
|
# Copy backend
|
||||||
|
COPY --from=backend /app/backend /app/backend
|
||||||
|
COPY --from=backend /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
|
||||||
|
|
||||||
|
# Copy frontend build to Nginx
|
||||||
|
COPY --from=frontend-build /app/frontend/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Copy Nginx configuration
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
# Expose ports
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Start script
|
||||||
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
RUN chmod +x /docker-entrypoint.sh
|
||||||
|
|
||||||
|
CMD ["/docker-entrypoint.sh"]
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Algorithm Test Case Generation and Verification Platform
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
- `backend/`: FastAPI Python Backend
|
||||||
|
- `frontend/`: React Vite Frontend
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- Python 3.8+
|
||||||
|
- Node.js 16+
|
||||||
|
|
||||||
|
## Setup & Run
|
||||||
|
|
||||||
|
### Quick Start with Docker (Recommended)
|
||||||
|
```bash
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
Then access:
|
||||||
|
- **Frontend**: http://localhost
|
||||||
|
- **Backend API**: http://localhost:8000
|
||||||
|
- **API Docs**: http://localhost:8000/docs
|
||||||
|
|
||||||
|
### Manual Setup
|
||||||
|
|
||||||
|
### 1. Backend
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
# Optional: Create virtual env
|
||||||
|
# python -m venv venv
|
||||||
|
# source venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
uvicorn main:app --reload
|
||||||
|
```
|
||||||
|
Backend will run at `http://localhost:8000`
|
||||||
|
|
||||||
|
### 2. Frontend
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
Frontend will run at `http://localhost:5173`
|
||||||
|
|
||||||
|
## Features Implemented
|
||||||
|
1. **Maximum Subarray Sum**:
|
||||||
|
- Brute Force ($O(N^2)$)
|
||||||
|
- Divide & Conquer ($O(N \log N)$)
|
||||||
|
- Kadane's Algorithm ($O(N)$)
|
||||||
|
- Automatic Random Case Generation
|
||||||
|
- Performance Benchmarking & Visualization
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
class BaseProblem(ABC):
|
||||||
|
"""
|
||||||
|
Abstract base class for all algorithm problems.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def generate_case(self, **kwargs) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Generate a random test case based on parameters.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def solve(self, input_data: Any, algorithm: str) -> Any:
|
||||||
|
"""
|
||||||
|
Solve the problem using the specified algorithm.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def verify(self, input_data: Any, result: Any) -> bool:
|
||||||
|
"""
|
||||||
|
Verify the correctness of the result (optional, or compare with standard answer).
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import time
|
||||||
|
import functools
|
||||||
|
from typing import Tuple, Any
|
||||||
|
|
||||||
|
def time_execution(func):
|
||||||
|
"""
|
||||||
|
Decorator to measure execution time of a function.
|
||||||
|
Returns a tuple (result, execution_time_in_seconds).
|
||||||
|
"""
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs) -> Tuple[Any, float]:
|
||||||
|
start_time = time.perf_counter()
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
end_time = time.perf_counter()
|
||||||
|
execution_time = end_time - start_time
|
||||||
|
return result, execution_time
|
||||||
|
return wrapper
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from routers import max_subarray_router
|
||||||
|
|
||||||
|
app = FastAPI(title="Algorithm Platform API")
|
||||||
|
|
||||||
|
# Allow CORS for frontend
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # In production, replace with specific frontend origin
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def read_root():
|
||||||
|
return {"message": "Welcome to Algorithm Verification Platform API"}
|
||||||
|
|
||||||
|
app.include_router(max_subarray_router.router)
|
||||||
|
from routers import interval_scheduling_router
|
||||||
|
app.include_router(interval_scheduling_router.router)
|
||||||
|
from routers import n_queens_router
|
||||||
|
app.include_router(n_queens_router.router)
|
||||||
|
from routers import knapsack_router
|
||||||
|
app.include_router(knapsack_router.router)
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import random
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
def generate_interval_scheduling_case(size: int = 10, min_time: int = 0, max_time: int = 100) -> List[Tuple[int, int]]:
|
||||||
|
"""
|
||||||
|
Generates a list of random intervals (start, end).
|
||||||
|
"""
|
||||||
|
intervals = []
|
||||||
|
for _ in range(size):
|
||||||
|
start = random.randint(min_time, max_time - 1)
|
||||||
|
# Ensure end > start
|
||||||
|
duration = random.randint(1, (max_time - start) // 2 + 1 if (max_time - start) > 2 else 1)
|
||||||
|
end = min(start + duration, max_time)
|
||||||
|
if end <= start:
|
||||||
|
end = start + 1
|
||||||
|
intervals.append((start, end))
|
||||||
|
return intervals
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
from common.base_problem import BaseProblem
|
||||||
|
from problems.interval_scheduling.generator import generate_interval_scheduling_case
|
||||||
|
from problems.interval_scheduling.solvers import solve_greedy, solve_brute_force
|
||||||
|
from common.timer_utils import time_execution
|
||||||
|
|
||||||
|
class IntervalSchedulingProblem(BaseProblem):
|
||||||
|
def generate_case(self, size: int = 10, min_time: int = 0, max_time: int = 100) -> Dict[str, Any]:
|
||||||
|
intervals = generate_interval_scheduling_case(size, min_time, max_time)
|
||||||
|
return {"intervals": intervals}
|
||||||
|
|
||||||
|
def solve(self, input_data: Dict[str, Any], algorithm: str) -> Dict[str, Any]:
|
||||||
|
intervals = input_data.get("intervals", [])
|
||||||
|
# Ensure intervals are tuples
|
||||||
|
intervals = [tuple(x) for x in intervals]
|
||||||
|
|
||||||
|
result = None
|
||||||
|
duration = 0.0
|
||||||
|
|
||||||
|
if algorithm == "greedy":
|
||||||
|
result, duration = time_execution(solve_greedy)(intervals)
|
||||||
|
elif algorithm == "brute_force":
|
||||||
|
result, duration = time_execution(solve_brute_force)(intervals)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown algorithm: {algorithm}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"algorithm": algorithm,
|
||||||
|
"result_count": len(result),
|
||||||
|
"result_intervals": result,
|
||||||
|
"time_seconds": duration
|
||||||
|
}
|
||||||
|
|
||||||
|
def verify(self, input_data: Any, result: Any) -> bool:
|
||||||
|
# Use Greedy as the source of truth (it is optimal for this problem)
|
||||||
|
intervals = input_data.get("intervals", [])
|
||||||
|
intervals = [tuple(x) for x in intervals]
|
||||||
|
truth, _ = time_execution(solve_greedy)(intervals)
|
||||||
|
return result.get("result_count") == len(truth)
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
def solve_greedy(intervals: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
|
||||||
|
"""
|
||||||
|
Greedy algorithm: Sort by end time, pick non-overlapping.
|
||||||
|
O(N log N)
|
||||||
|
"""
|
||||||
|
if not intervals:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Sort by end time
|
||||||
|
sorted_intervals = sorted(intervals, key=lambda x: x[1])
|
||||||
|
|
||||||
|
selected = []
|
||||||
|
last_end_time = -float('inf')
|
||||||
|
|
||||||
|
for start, end in sorted_intervals:
|
||||||
|
if start >= last_end_time:
|
||||||
|
selected.append((start, end))
|
||||||
|
last_end_time = end
|
||||||
|
|
||||||
|
return selected
|
||||||
|
|
||||||
|
def solve_brute_force(intervals: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
|
||||||
|
"""
|
||||||
|
Brute Force algorithm: Try all combinations.
|
||||||
|
O(2^N) - Only for small inputs!
|
||||||
|
"""
|
||||||
|
n = len(intervals)
|
||||||
|
max_count = 0
|
||||||
|
best_subset = []
|
||||||
|
|
||||||
|
# Helper to check if a subset of intervals is valid (non-overlapping)
|
||||||
|
def is_valid(subset):
|
||||||
|
sorted_subset = sorted(subset, key=lambda x: x[1])
|
||||||
|
last_end = -float('inf')
|
||||||
|
for start, end in sorted_subset:
|
||||||
|
if start < last_end:
|
||||||
|
return False
|
||||||
|
last_end = end
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Iterate through all 2^N subsets (using bitmask)
|
||||||
|
# Limit N to avoid hanging
|
||||||
|
if n > 20:
|
||||||
|
# Fallback or error for safety, though we handle this in router usually
|
||||||
|
return solve_greedy(intervals) # Cheating slightly for safety if called directly with large N
|
||||||
|
|
||||||
|
for i in range(1 << n):
|
||||||
|
subset = []
|
||||||
|
for j in range(n):
|
||||||
|
if (i >> j) & 1:
|
||||||
|
subset.append(intervals[j])
|
||||||
|
|
||||||
|
if is_valid(subset):
|
||||||
|
if len(subset) > max_count:
|
||||||
|
max_count = len(subset)
|
||||||
|
best_subset = subset
|
||||||
|
|
||||||
|
return best_subset
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import random
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
def generate_knapsack_case(num_items: int = 10, max_weight: int = 50, max_value: int = 100, capacity_ratio: float = 0.5) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Generates a case for 0/1 Knapsack.
|
||||||
|
"""
|
||||||
|
items = []
|
||||||
|
total_weight = 0
|
||||||
|
for i in range(num_items):
|
||||||
|
w = random.randint(1, max_weight)
|
||||||
|
v = random.randint(1, max_value)
|
||||||
|
items.append({"id": i, "weight": w, "value": v})
|
||||||
|
total_weight += w
|
||||||
|
|
||||||
|
capacity = int(total_weight * capacity_ratio)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"items": items,
|
||||||
|
"capacity": capacity
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
from common.base_problem import BaseProblem
|
||||||
|
from problems.knapsack.generator import generate_knapsack_case
|
||||||
|
from problems.knapsack.solvers import solve_dp, solve_backtracking
|
||||||
|
from common.timer_utils import time_execution
|
||||||
|
|
||||||
|
class KnapsackProblem(BaseProblem):
|
||||||
|
def generate_case(self, num_items: int = 10, max_weight: int = 50, max_value: int = 100) -> Dict[str, Any]:
|
||||||
|
return generate_knapsack_case(num_items, max_weight, max_value)
|
||||||
|
|
||||||
|
def solve(self, input_data: Dict[str, Any], algorithm: str) -> Dict[str, Any]:
|
||||||
|
items = input_data.get("items", [])
|
||||||
|
capacity = input_data.get("capacity", 0)
|
||||||
|
|
||||||
|
result = None
|
||||||
|
duration = 0.0
|
||||||
|
|
||||||
|
if algorithm == "dp":
|
||||||
|
result, duration = time_execution(solve_dp)(items, capacity)
|
||||||
|
elif algorithm == "backtracking":
|
||||||
|
result, duration = time_execution(solve_backtracking)(items, capacity)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown algorithm: {algorithm}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"algorithm": algorithm,
|
||||||
|
"max_value": result["max_value"],
|
||||||
|
"selected_items": result["selected_items"],
|
||||||
|
"time_seconds": duration
|
||||||
|
}
|
||||||
|
|
||||||
|
def verify(self, input_data: Any, result: Any) -> bool:
|
||||||
|
# Use DP as source of truth
|
||||||
|
items = input_data.get("items", [])
|
||||||
|
capacity = input_data.get("capacity", 0)
|
||||||
|
truth, _ = time_execution(solve_dp)(items, capacity)
|
||||||
|
return result["max_value"] == truth["max_value"]
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
from typing import List, Dict, Any, Tuple
|
||||||
|
|
||||||
|
def solve_dp(items: List[Dict[str, int]], capacity: int) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Dynamic Programming solution for 0/1 Knapsack.
|
||||||
|
O(N * W)
|
||||||
|
"""
|
||||||
|
n = len(items)
|
||||||
|
# dp[i][w] = max value using first i items with capacity w
|
||||||
|
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
|
||||||
|
|
||||||
|
for i in range(1, n + 1):
|
||||||
|
item = items[i-1]
|
||||||
|
w, v = item["weight"], item["value"]
|
||||||
|
for j in range(capacity + 1):
|
||||||
|
if w <= j:
|
||||||
|
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w] + v)
|
||||||
|
else:
|
||||||
|
dp[i][j] = dp[i-1][j]
|
||||||
|
|
||||||
|
max_value = dp[n][capacity]
|
||||||
|
|
||||||
|
# Backtrack to find selected items
|
||||||
|
selected_indices = []
|
||||||
|
w = capacity
|
||||||
|
for i in range(n, 0, -1):
|
||||||
|
if dp[i][w] != dp[i-1][w]:
|
||||||
|
item = items[i-1]
|
||||||
|
selected_indices.append(item["id"])
|
||||||
|
w -= item["weight"]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"max_value": max_value,
|
||||||
|
"selected_items": selected_indices
|
||||||
|
}
|
||||||
|
|
||||||
|
def solve_backtracking(items: List[Dict[str, int]], capacity: int) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Backtracking solution for 0/1 Knapsack.
|
||||||
|
O(2^N)
|
||||||
|
"""
|
||||||
|
n = len(items)
|
||||||
|
max_val = 0
|
||||||
|
best_selection = []
|
||||||
|
|
||||||
|
def backtrack(index, current_weight, current_value, selection):
|
||||||
|
nonlocal max_val, best_selection
|
||||||
|
|
||||||
|
if current_weight > capacity:
|
||||||
|
return
|
||||||
|
|
||||||
|
if current_value > max_val:
|
||||||
|
max_val = current_value
|
||||||
|
best_selection = selection[:]
|
||||||
|
|
||||||
|
if index == n:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Include current item
|
||||||
|
item = items[index]
|
||||||
|
if current_weight + item["weight"] <= capacity:
|
||||||
|
selection.append(item["id"])
|
||||||
|
backtrack(index + 1, current_weight + item["weight"], current_value + item["value"], selection)
|
||||||
|
selection.pop()
|
||||||
|
|
||||||
|
# Exclude current item
|
||||||
|
backtrack(index + 1, current_weight, current_value, selection)
|
||||||
|
|
||||||
|
backtrack(0, 0, 0, [])
|
||||||
|
return {
|
||||||
|
"max_value": max_val,
|
||||||
|
"selected_items": best_selection
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import random
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
|
def generate_max_subarray_case(size: int = 100, min_val: int = -100, max_val: int = 100) -> List[int]:
|
||||||
|
"""
|
||||||
|
Generate a list of random integers.
|
||||||
|
"""
|
||||||
|
return [random.randint(min_val, max_val) for _ in range(size)]
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
from common.base_problem import BaseProblem
|
||||||
|
from problems.max_subarray.generator import generate_max_subarray_case
|
||||||
|
from problems.max_subarray.solvers import solve_brute_force, solve_divide_conquer, solve_kadane
|
||||||
|
from common.timer_utils import time_execution
|
||||||
|
|
||||||
|
class MaxSubarrayProblem(BaseProblem):
|
||||||
|
def generate_case(self, size: int = 100, min_val: int = -100, max_val: int = 100) -> Dict[str, Any]:
|
||||||
|
arr = generate_max_subarray_case(size, min_val, max_val)
|
||||||
|
return {"array": arr}
|
||||||
|
|
||||||
|
def solve(self, input_data: Dict[str, Any], algorithm: str) -> Dict[str, Any]:
|
||||||
|
arr = input_data.get("array", [])
|
||||||
|
result = None
|
||||||
|
duration = 0.0
|
||||||
|
|
||||||
|
if algorithm == "brute_force":
|
||||||
|
result, duration = time_execution(solve_brute_force)(arr)
|
||||||
|
elif algorithm == "divide_conquer":
|
||||||
|
result, duration = time_execution(solve_divide_conquer)(arr)
|
||||||
|
elif algorithm == "kadane":
|
||||||
|
result, duration = time_execution(solve_kadane)(arr)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown algorithm: {algorithm}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"algorithm": algorithm,
|
||||||
|
"result": result,
|
||||||
|
"time_seconds": duration
|
||||||
|
}
|
||||||
|
|
||||||
|
def verify(self, input_data: Any, result: Any) -> bool:
|
||||||
|
# Use Kadane as the source of truth
|
||||||
|
truth, _ = time_execution(solve_kadane)(input_data.get("array", []))
|
||||||
|
return result == truth
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
# 1. Brute Force O(N^2)
|
||||||
|
def solve_brute_force(arr: List[int]) -> int:
|
||||||
|
max_sum = float('-inf')
|
||||||
|
n = len(arr)
|
||||||
|
for i in range(n):
|
||||||
|
current_sum = 0
|
||||||
|
for j in range(i, n):
|
||||||
|
current_sum += arr[j]
|
||||||
|
if current_sum > max_sum:
|
||||||
|
max_sum = current_sum
|
||||||
|
return max_sum
|
||||||
|
|
||||||
|
# 2. Divide and Conquer O(N log N)
|
||||||
|
def solve_divide_conquer(arr: List[int]) -> int:
|
||||||
|
def max_crossing_sum(arr, low, mid, high):
|
||||||
|
left_sum = float('-inf')
|
||||||
|
curr_sum = 0
|
||||||
|
for i in range(mid, low - 1, -1):
|
||||||
|
curr_sum += arr[i]
|
||||||
|
if curr_sum > left_sum:
|
||||||
|
left_sum = curr_sum
|
||||||
|
|
||||||
|
right_sum = float('-inf')
|
||||||
|
curr_sum = 0
|
||||||
|
for i in range(mid + 1, high + 1):
|
||||||
|
curr_sum += arr[i]
|
||||||
|
if curr_sum > right_sum:
|
||||||
|
right_sum = curr_sum
|
||||||
|
|
||||||
|
return left_sum + right_sum
|
||||||
|
|
||||||
|
def solve_recursive(arr, low, high):
|
||||||
|
if low == high:
|
||||||
|
return arr[low]
|
||||||
|
|
||||||
|
mid = (low + high) // 2
|
||||||
|
|
||||||
|
return max(
|
||||||
|
solve_recursive(arr, low, mid),
|
||||||
|
solve_recursive(arr, mid + 1, high),
|
||||||
|
max_crossing_sum(arr, low, mid, high)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not arr:
|
||||||
|
return 0
|
||||||
|
return solve_recursive(arr, 0, len(arr) - 1)
|
||||||
|
|
||||||
|
# 3. Dynamic Programming (Kadane's Algorithm) O(N)
|
||||||
|
def solve_kadane(arr: List[int]) -> int:
|
||||||
|
if not arr:
|
||||||
|
return 0
|
||||||
|
max_so_far = arr[0]
|
||||||
|
curr_max = arr[0]
|
||||||
|
|
||||||
|
for i in range(1, len(arr)):
|
||||||
|
curr_max = max(arr[i], curr_max + arr[i])
|
||||||
|
max_so_far = max(max_so_far, curr_max)
|
||||||
|
|
||||||
|
return max_so_far
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
def generate_n_queens_case(n: int = 8) -> Dict[str, int]:
|
||||||
|
"""
|
||||||
|
Generates a case for N-Queens.
|
||||||
|
Actually just returns N since the problem is defined by the board size.
|
||||||
|
"""
|
||||||
|
return {"n": n}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
from common.base_problem import BaseProblem
|
||||||
|
from problems.n_queens.generator import generate_n_queens_case
|
||||||
|
from problems.n_queens.solvers import solve_backtracking
|
||||||
|
from common.timer_utils import time_execution
|
||||||
|
|
||||||
|
class NQueensProblem(BaseProblem):
|
||||||
|
def generate_case(self, n: int = 8) -> Dict[str, Any]:
|
||||||
|
return generate_n_queens_case(n)
|
||||||
|
|
||||||
|
def solve(self, input_data: Dict[str, Any], algorithm: str) -> Dict[str, Any]:
|
||||||
|
n = input_data.get("n", 8)
|
||||||
|
result = None
|
||||||
|
duration = 0.0
|
||||||
|
|
||||||
|
if algorithm == "backtracking":
|
||||||
|
# For visualization, we usually just want one solution,
|
||||||
|
# but for benchmarking maybe we want to count all?
|
||||||
|
# Let's default to finding ONE solution for "solve" to be fast enough for UI
|
||||||
|
# If N is small (< 12), we can find all.
|
||||||
|
find_all = n < 10
|
||||||
|
result, duration = time_execution(solve_backtracking)(n, find_all)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown algorithm: {algorithm}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"algorithm": algorithm,
|
||||||
|
"solution_count": len(result),
|
||||||
|
"first_solution": result[0] if result else None,
|
||||||
|
"time_seconds": duration
|
||||||
|
}
|
||||||
|
|
||||||
|
def verify(self, input_data: Any, result: Any) -> bool:
|
||||||
|
# Verification is tricky without a ground truth.
|
||||||
|
# We can check if the returned solution is valid.
|
||||||
|
sol = result.get("first_solution")
|
||||||
|
if not sol:
|
||||||
|
return True # No solution found (could be valid for some N, though N>=4 always has sol)
|
||||||
|
|
||||||
|
n = len(sol)
|
||||||
|
for r in range(n):
|
||||||
|
c = sol[r]
|
||||||
|
# Check conflicts
|
||||||
|
for prev_r in range(r):
|
||||||
|
prev_c = sol[prev_r]
|
||||||
|
if c == prev_c or abs(r - prev_r) == abs(c - prev_c):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
from typing import List, Tuple, Optional
|
||||||
|
|
||||||
|
def solve_backtracking(n: int, find_all: bool = False) -> List[List[int]]:
|
||||||
|
"""
|
||||||
|
Backtracking algorithm for N-Queens.
|
||||||
|
Returns a list of solutions. Each solution is a list of column indices for each row.
|
||||||
|
"""
|
||||||
|
solutions = []
|
||||||
|
board = [-1] * n # board[row] = col
|
||||||
|
cols = [False] * n
|
||||||
|
diag1 = [False] * (2 * n - 1) # row + col
|
||||||
|
diag2 = [False] * (2 * n - 1) # row - col + (n - 1)
|
||||||
|
|
||||||
|
def backtrack(row: int):
|
||||||
|
if row == n:
|
||||||
|
solutions.append(board[:])
|
||||||
|
return True
|
||||||
|
|
||||||
|
for col in range(n):
|
||||||
|
if not cols[col] and not diag1[row + col] and not diag2[row - col + n - 1]:
|
||||||
|
board[row] = col
|
||||||
|
cols[col] = True
|
||||||
|
diag1[row + col] = True
|
||||||
|
diag2[row - col + n - 1] = True
|
||||||
|
|
||||||
|
if backtrack(row + 1):
|
||||||
|
if not find_all:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Backtrack
|
||||||
|
cols[col] = False
|
||||||
|
diag1[row + col] = False
|
||||||
|
diag2[row - col + n - 1] = False
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
backtrack(0)
|
||||||
|
return solutions
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
matplotlib
|
||||||
|
pandas
|
||||||
|
numpy
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List, Optional, Dict, Any, Tuple
|
||||||
|
from problems.interval_scheduling.problem import IntervalSchedulingProblem
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix="/interval_scheduling",
|
||||||
|
tags=["interval_scheduling"],
|
||||||
|
responses={404: {"description": "Not found"}},
|
||||||
|
)
|
||||||
|
|
||||||
|
problem_solver = IntervalSchedulingProblem()
|
||||||
|
|
||||||
|
class GenerateRequest(BaseModel):
|
||||||
|
size: int = 10
|
||||||
|
min_time: int = 0
|
||||||
|
max_time: int = 100
|
||||||
|
|
||||||
|
class SolveRequest(BaseModel):
|
||||||
|
intervals: List[Tuple[int, int]]
|
||||||
|
algorithms: List[str] = ["greedy", "brute_force"]
|
||||||
|
|
||||||
|
class BenchmarkRequest(BaseModel):
|
||||||
|
sizes: List[int] = [5, 10, 15, 20]
|
||||||
|
algorithms: List[str] = ["greedy", "brute_force"]
|
||||||
|
|
||||||
|
@router.post("/generate")
|
||||||
|
async def generate_case(req: GenerateRequest):
|
||||||
|
return problem_solver.generate_case(size=req.size, min_time=req.min_time, max_time=req.max_time)
|
||||||
|
|
||||||
|
@router.post("/solve")
|
||||||
|
async def solve_case(req: SolveRequest):
|
||||||
|
input_data = {"intervals": req.intervals}
|
||||||
|
results = []
|
||||||
|
for algo in req.algorithms:
|
||||||
|
try:
|
||||||
|
# Skip Brute Force for large inputs to avoid timeout
|
||||||
|
if len(req.intervals) > 22 and algo == "brute_force":
|
||||||
|
results.append({
|
||||||
|
"algorithm": algo,
|
||||||
|
"error": "Input too large for Brute Force (limit 22)",
|
||||||
|
"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:
|
||||||
|
input_data = problem_solver.generate_case(size=size)
|
||||||
|
|
||||||
|
size_result = {"size": size, "algorithms": []}
|
||||||
|
for algo in req.algorithms:
|
||||||
|
try:
|
||||||
|
if size > 22 and algo == "brute_force":
|
||||||
|
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,76 @@
|
||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List, Optional, Dict, Any
|
||||||
|
from problems.knapsack.problem import KnapsackProblem
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix="/knapsack",
|
||||||
|
tags=["knapsack"],
|
||||||
|
responses={404: {"description": "Not found"}},
|
||||||
|
)
|
||||||
|
|
||||||
|
problem_solver = KnapsackProblem()
|
||||||
|
|
||||||
|
class GenerateRequest(BaseModel):
|
||||||
|
num_items: int = 10
|
||||||
|
max_weight: int = 50
|
||||||
|
max_value: int = 100
|
||||||
|
|
||||||
|
class SolveRequest(BaseModel):
|
||||||
|
items: List[Dict[str, int]]
|
||||||
|
capacity: int
|
||||||
|
algorithms: List[str] = ["dp", "backtracking"]
|
||||||
|
|
||||||
|
class BenchmarkRequest(BaseModel):
|
||||||
|
sizes: List[int] = [10, 20, 30]
|
||||||
|
algorithms: List[str] = ["dp", "backtracking"]
|
||||||
|
|
||||||
|
@router.post("/generate")
|
||||||
|
async def generate_case(req: GenerateRequest):
|
||||||
|
return problem_solver.generate_case(num_items=req.num_items, max_weight=req.max_weight, max_value=req.max_value)
|
||||||
|
|
||||||
|
@router.post("/solve")
|
||||||
|
async def solve_case(req: SolveRequest):
|
||||||
|
input_data = {"items": req.items, "capacity": req.capacity}
|
||||||
|
results = []
|
||||||
|
for algo in req.algorithms:
|
||||||
|
try:
|
||||||
|
# Skip Backtracking for large inputs
|
||||||
|
if len(req.items) > 22 and algo == "backtracking":
|
||||||
|
results.append({
|
||||||
|
"algorithm": algo,
|
||||||
|
"error": "Input too large for Backtracking (limit 22)",
|
||||||
|
"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:
|
||||||
|
input_data = problem_solver.generate_case(num_items=size)
|
||||||
|
|
||||||
|
size_result = {"size": size, "algorithms": []}
|
||||||
|
for algo in req.algorithms:
|
||||||
|
try:
|
||||||
|
if size > 22 and algo == "backtracking":
|
||||||
|
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,70 @@
|
||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List, Optional, Dict, Any
|
||||||
|
from problems.max_subarray.problem import MaxSubarrayProblem
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix="/max_subarray",
|
||||||
|
tags=["max_subarray"],
|
||||||
|
responses={404: {"description": "Not found"}},
|
||||||
|
)
|
||||||
|
|
||||||
|
problem_solver = MaxSubarrayProblem()
|
||||||
|
|
||||||
|
class GenerateRequest(BaseModel):
|
||||||
|
size: int = 100
|
||||||
|
min_val: int = -100
|
||||||
|
max_val: int = 100
|
||||||
|
|
||||||
|
class SolveRequest(BaseModel):
|
||||||
|
array: List[int]
|
||||||
|
algorithms: List[str] = ["kadane", "divide_conquer", "brute_force"]
|
||||||
|
|
||||||
|
class BenchmarkRequest(BaseModel):
|
||||||
|
sizes: List[int] = [10, 50, 100, 500, 1000]
|
||||||
|
algorithms: List[str] = ["kadane", "divide_conquer", "brute_force"]
|
||||||
|
|
||||||
|
@router.post("/generate")
|
||||||
|
async def generate_case(req: GenerateRequest):
|
||||||
|
return problem_solver.generate_case(size=req.size, min_val=req.min_val, max_val=req.max_val)
|
||||||
|
|
||||||
|
@router.post("/solve")
|
||||||
|
async def solve_case(req: SolveRequest):
|
||||||
|
input_data = {"array": req.array}
|
||||||
|
results = []
|
||||||
|
for algo in req.algorithms:
|
||||||
|
try:
|
||||||
|
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:
|
||||||
|
# Generate a random case for this size
|
||||||
|
# To reduce noise, we might want to run multiple times and average,
|
||||||
|
# but for now single run per size is fine for demonstration
|
||||||
|
input_data = problem_solver.generate_case(size=size)
|
||||||
|
|
||||||
|
size_result = {"size": size, "algorithms": []}
|
||||||
|
for algo in req.algorithms:
|
||||||
|
try:
|
||||||
|
# Skip O(N^2) for very large inputs to avoid timeout
|
||||||
|
if size > 5000 and algo == "brute_force":
|
||||||
|
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,66 @@
|
||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import List, Optional, Dict, Any
|
||||||
|
from problems.n_queens.problem import NQueensProblem
|
||||||
|
|
||||||
|
router = APIRouter(
|
||||||
|
prefix="/n_queens",
|
||||||
|
tags=["n_queens"],
|
||||||
|
responses={404: {"description": "Not found"}},
|
||||||
|
)
|
||||||
|
|
||||||
|
problem_solver = NQueensProblem()
|
||||||
|
|
||||||
|
class GenerateRequest(BaseModel):
|
||||||
|
n: int = 8
|
||||||
|
|
||||||
|
class SolveRequest(BaseModel):
|
||||||
|
n: int
|
||||||
|
algorithms: List[str] = ["backtracking"]
|
||||||
|
|
||||||
|
class BenchmarkRequest(BaseModel):
|
||||||
|
sizes: List[int] = [4, 8, 10, 12]
|
||||||
|
algorithms: List[str] = ["backtracking"]
|
||||||
|
|
||||||
|
@router.post("/generate")
|
||||||
|
async def generate_case(req: GenerateRequest):
|
||||||
|
return problem_solver.generate_case(n=req.n)
|
||||||
|
|
||||||
|
@router.post("/solve")
|
||||||
|
async def solve_case(req: SolveRequest):
|
||||||
|
input_data = {"n": req.n}
|
||||||
|
results = []
|
||||||
|
for algo in req.algorithms:
|
||||||
|
try:
|
||||||
|
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:
|
||||||
|
input_data = {"n": size}
|
||||||
|
|
||||||
|
size_result = {"size": size, "algorithms": []}
|
||||||
|
for algo in req.algorithms:
|
||||||
|
try:
|
||||||
|
# Skip large N for backtracking if finding all solutions (though our solve defaults to one for large N)
|
||||||
|
# But for benchmark we might want to be careful.
|
||||||
|
if size > 14 and algo == "backtracking":
|
||||||
|
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,58 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
# Add backend to path
|
||||||
|
sys.path.append(os.path.abspath("/Users/hissin/测试/25-cxsj-final/backend"))
|
||||||
|
|
||||||
|
from problems.max_subarray.problem import MaxSubarrayProblem
|
||||||
|
from problems.interval_scheduling.problem import IntervalSchedulingProblem
|
||||||
|
from problems.n_queens.problem import NQueensProblem
|
||||||
|
from problems.knapsack.problem import KnapsackProblem
|
||||||
|
|
||||||
|
def test_max_subarray():
|
||||||
|
print("\nTesting Max Subarray...")
|
||||||
|
prob = MaxSubarrayProblem()
|
||||||
|
case = prob.generate_case(size=10)
|
||||||
|
res = prob.solve(case, "kadane")
|
||||||
|
print(f"Max Subarray Result: {res['result']}")
|
||||||
|
assert prob.verify(case, res['result'])
|
||||||
|
print("Max Subarray Passed!")
|
||||||
|
|
||||||
|
def test_interval_scheduling():
|
||||||
|
print("\nTesting Interval Scheduling...")
|
||||||
|
prob = IntervalSchedulingProblem()
|
||||||
|
case = prob.generate_case(size=10)
|
||||||
|
res = prob.solve(case, "greedy")
|
||||||
|
print(f"Interval Scheduling Count: {res['result_count']}")
|
||||||
|
assert prob.verify(case, res)
|
||||||
|
print("Interval Scheduling Passed!")
|
||||||
|
|
||||||
|
def test_n_queens():
|
||||||
|
print("\nTesting N-Queens...")
|
||||||
|
prob = NQueensProblem()
|
||||||
|
case = prob.generate_case(n=8)
|
||||||
|
res = prob.solve(case, "backtracking")
|
||||||
|
print(f"N-Queens (N=8) Solutions: {res['solution_count']}")
|
||||||
|
assert res['solution_count'] == 92
|
||||||
|
print("N-Queens Passed!")
|
||||||
|
|
||||||
|
def test_knapsack():
|
||||||
|
print("\nTesting Knapsack...")
|
||||||
|
prob = KnapsackProblem()
|
||||||
|
case = prob.generate_case(num_items=10, max_weight=20, max_value=100)
|
||||||
|
res_dp = prob.solve(case, "dp")
|
||||||
|
res_back = prob.solve(case, "backtracking")
|
||||||
|
|
||||||
|
print(f"Knapsack DP Value: {res_dp['max_value']}")
|
||||||
|
print(f"Knapsack Backtracking Value: {res_back['max_value']}")
|
||||||
|
|
||||||
|
assert res_dp['max_value'] == res_back['max_value']
|
||||||
|
assert prob.verify(case, res_dp)
|
||||||
|
print("Knapsack Passed!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_max_subarray()
|
||||||
|
test_interval_scheduling()
|
||||||
|
test_n_queens()
|
||||||
|
test_knapsack()
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Add backend to path
|
||||||
|
sys.path.append(os.path.abspath("/Users/hissin/测试/25-cxsj-final/backend"))
|
||||||
|
|
||||||
|
from problems.interval_scheduling.generator import generate_interval_scheduling_case
|
||||||
|
from problems.interval_scheduling.solvers import solve_greedy, solve_brute_force
|
||||||
|
from problems.interval_scheduling.problem import IntervalSchedulingProblem
|
||||||
|
|
||||||
|
def test_generator():
|
||||||
|
print("Testing Generator...")
|
||||||
|
intervals = generate_interval_scheduling_case(size=10, min_time=0, max_time=100)
|
||||||
|
print(f"Generated {len(intervals)} intervals: {intervals}")
|
||||||
|
assert len(intervals) == 10
|
||||||
|
for start, end in intervals:
|
||||||
|
assert start < end
|
||||||
|
assert start >= 0
|
||||||
|
assert end <= 100
|
||||||
|
print("Generator Passed!")
|
||||||
|
|
||||||
|
def test_solvers():
|
||||||
|
print("\nTesting Solvers...")
|
||||||
|
# Known case
|
||||||
|
intervals = [(1, 3), (2, 4), (3, 5)]
|
||||||
|
# Greedy should pick (1,3) and (3,5) -> count 2
|
||||||
|
greedy_res = solve_greedy(intervals)
|
||||||
|
print(f"Greedy result: {greedy_res}")
|
||||||
|
assert len(greedy_res) == 2
|
||||||
|
|
||||||
|
brute_res = solve_brute_force(intervals)
|
||||||
|
print(f"Brute Force result: {brute_res}")
|
||||||
|
assert len(brute_res) == 2
|
||||||
|
|
||||||
|
# Complex case
|
||||||
|
intervals = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 9), (5, 9), (6, 10), (8, 11), (8, 12), (2, 14), (12, 16)]
|
||||||
|
greedy_res = solve_greedy(intervals)
|
||||||
|
brute_res = solve_brute_force(intervals)
|
||||||
|
print(f"Complex Greedy: {len(greedy_res)}")
|
||||||
|
print(f"Complex Brute: {len(brute_res)}")
|
||||||
|
assert len(greedy_res) == len(brute_res)
|
||||||
|
print("Solvers Passed!")
|
||||||
|
|
||||||
|
def test_problem_class():
|
||||||
|
print("\nTesting Problem Class...")
|
||||||
|
prob = IntervalSchedulingProblem()
|
||||||
|
case = prob.generate_case(size=5)
|
||||||
|
res_greedy = prob.solve(case, "greedy")
|
||||||
|
res_brute = prob.solve(case, "brute_force")
|
||||||
|
|
||||||
|
print(f"Class Greedy: {res_greedy}")
|
||||||
|
print(f"Class Brute: {res_brute}")
|
||||||
|
|
||||||
|
assert prob.verify(case, res_greedy)
|
||||||
|
assert res_greedy["result_count"] == res_brute["result_count"]
|
||||||
|
print("Problem Class Passed!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_generator()
|
||||||
|
test_solvers()
|
||||||
|
test_problem_class()
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
# 算法测试用例生成与自我验证平台 - 设计文档
|
||||||
|
|
||||||
|
## 1. 项目概述
|
||||||
|
本项目旨在构建一个集“自动出题”、“多算法求解”、“性能分析”于一体的算法学习与验证平台。通过针对特定算法问题生成随机测试数据,并使用不同复杂度的算法进行求解,验证结果一致性并对比运行效率。项目包含一个现代化 Web 前端界面,用于交互式地配置参数与展示图表。
|
||||||
|
|
||||||
|
## 2. 系统架构设计
|
||||||
|
系统采用 **前后端分离** 架构:
|
||||||
|
|
||||||
|
### 2.1 核心模块
|
||||||
|
#### 后端 (Backend)
|
||||||
|
1. **Problem Interface (问题接口)**
|
||||||
|
* 定义问题的通用接口,包括 `generate_case()` (生成用例), `solve()` (求解), `verify()` (验证)。
|
||||||
|
2. **Solver Engine (求解引擎)**
|
||||||
|
* 针对每个问题,实现多种算法策略(暴力、分治、DP、贪心等)。
|
||||||
|
3. **Performance Analyzer (性能分析器)**
|
||||||
|
* 执行基准测试,验证正确性,统计运行时间。
|
||||||
|
4. **API Layer (API 层)**
|
||||||
|
* 基于 **FastAPI** 提供 RESTful 接口,处理前端的配置请求并返回计算结果。
|
||||||
|
|
||||||
|
#### 前端 (Frontend)
|
||||||
|
1. **Control Panel (控制面板)**
|
||||||
|
* 选择算法问题(如“最大子数组和”、“0/1背包”)。
|
||||||
|
* 配置生成参数(数据规模、数值范围、稀疏度等)。
|
||||||
|
2. **Visualization (可视化展示)**
|
||||||
|
* 使用图表库展示不同算法的时间复杂度对比曲线。
|
||||||
|
* 展示具体的测试用例数据与解题结果(用于教学演示)。
|
||||||
|
|
||||||
|
### 2.2 技术栈
|
||||||
|
* **后端**: Python 3.x, FastAPI (Web框架), Matplotlib/Pandas (数据分析)
|
||||||
|
* **前端**: React, TailwindCSS (样式), Recharts (图表可视化), Axios (网络请求)
|
||||||
|
|
||||||
|
## 3. 选定算法问题
|
||||||
|
|
||||||
|
### 问题一:最大子数组和 (Maximum Subarray Sum)
|
||||||
|
**描述**: 给定一个整数数组,找到一个具有最大和的连续子数组。
|
||||||
|
**测试用例生成**:
|
||||||
|
* 随机生成长度为 $N$ 的整数数组。
|
||||||
|
* 数值范围可配置(包含正负数)。
|
||||||
|
**算法实现**:
|
||||||
|
1. **暴力枚举 (Brute Force)**: 双重循环枚举所有子数组,时间复杂度 $O(N^2)$。
|
||||||
|
2. **分治算法 (Divide and Conquer)**: 递归将数组分为左右两半,最大子数组可能在左、右或跨越中点,时间复杂度 $O(N \log N)$。
|
||||||
|
3. **动态规划 (Kadane's Algorithm)**: 线性扫描,时间复杂度 $O(N)$。
|
||||||
|
|
||||||
|
### 问题二:0/1 背包问题 (0/1 Knapsack Problem)
|
||||||
|
**描述**: 给定一组物品,每种物品都有自己的重量和价值,在限定的总重量内,选择其中若干个(也即每种物品可以选0个或1个),使得物品的总价值最高。
|
||||||
|
**测试用例生成**:
|
||||||
|
* 随机生成 $N$ 个物品,每个物品具有重量 $w$ 和价值 $v$。
|
||||||
|
* 随机生成背包容量 $C$。
|
||||||
|
**算法实现**:
|
||||||
|
1. **回溯法 (Backtracking)**: 递归枚举所有可能的选择情况,时间复杂度 $O(2^N)$。
|
||||||
|
2. **动态规划 (Dynamic Programming)**: 使用二维或一维数组记录状态,时间复杂度 $O(N \cdot C)$。
|
||||||
|
3. **贪心算法 (Greedy, 近似解/对比)**: 按价值密度排序选择(注意:0/1背包贪心法通常不能得到最优解,可作为对比项展示“错误”或“近似”的算法,或者用于对比部分背包问题)。*注:本项目中为了验证正确性,主要对比回溯与DP,贪心可作为错误示范分析。*
|
||||||
|
|
||||||
|
## 4. 目录结构规划
|
||||||
|
```
|
||||||
|
algorithm_platform/
|
||||||
|
├── README.md
|
||||||
|
├── DESIGN_DOCUMENT.md
|
||||||
|
├── backend/ # 后端代码
|
||||||
|
│ ├── main.py # FastAPI 入口
|
||||||
|
│ ├── common/ # 通用工具
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── base_problem.py
|
||||||
|
│ │ └── timer_utils.py
|
||||||
|
│ ├── problems/ # 算法实现模块
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── max_subarray/
|
||||||
|
│ │ └── knapsack/
|
||||||
|
│ └── routers/ # API 路由
|
||||||
|
└── frontend/ # 前端代码
|
||||||
|
├── src/
|
||||||
|
│ ├── components/ # UI 组件
|
||||||
|
│ ├── pages/ # 页面
|
||||||
|
│ └── services/ # API 调用
|
||||||
|
├── package.json
|
||||||
|
└── vite.config.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 开发计划
|
||||||
|
1. **第一阶段**: 搭建后端核心骨架 (FastAPI + Base Classes)。
|
||||||
|
2. **第二阶段**: 实现“最大子数组和”算法逻辑与 API。
|
||||||
|
3. **第三阶段**: 搭建 React 前端框架,实现基础交互与图表组件。
|
||||||
|
4. **第四阶段**: 实现“0/1背包”算法逻辑,并接入前端。
|
||||||
|
5. **第五阶段**: 优化 UI/UX,完善性能对比的可视化报告。
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Algorithm Platform</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "algorithm-platform-frontend",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.6.0",
|
||||||
|
"lucide-react": "^0.292.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"recharts": "^2.9.0",
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"tailwind-merge": "^2.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.2.37",
|
||||||
|
"@types/react-dom": "^18.2.15",
|
||||||
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
|
"autoprefixer": "^10.4.16",
|
||||||
|
"postcss": "^8.4.31",
|
||||||
|
"tailwindcss": "^3.3.5",
|
||||||
|
"vite": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,72 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import MaxSubarray from './components/MaxSubarray';
|
||||||
|
import IntervalScheduling from './components/IntervalScheduling';
|
||||||
|
import NQueens from './components/NQueens';
|
||||||
|
import Knapsack from './components/Knapsack';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [activeTab, setActiveTab] = useState('max_subarray');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50">
|
||||||
|
<nav className="bg-white shadow-sm">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex justify-between h-16">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span className="text-xl font-bold text-gray-800">算法验证平台</span>
|
||||||
|
<div className="ml-10 flex items-baseline space-x-4">
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('max_subarray')}
|
||||||
|
className={`px-3 py-2 rounded-md text-sm font-medium ${activeTab === 'max_subarray'
|
||||||
|
? 'bg-blue-100 text-blue-700'
|
||||||
|
: 'text-gray-600 hover:bg-gray-100'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
最大子数组和
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('interval_scheduling')}
|
||||||
|
className={`px-3 py-2 rounded-md text-sm font-medium ${activeTab === 'interval_scheduling'
|
||||||
|
? 'bg-blue-100 text-blue-700'
|
||||||
|
: 'text-gray-600 hover:bg-gray-100'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
区间调度
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('n_queens')}
|
||||||
|
className={`px-3 py-2 rounded-md text-sm font-medium ${activeTab === 'n_queens'
|
||||||
|
? 'bg-blue-100 text-blue-700'
|
||||||
|
: 'text-gray-600 hover:bg-gray-100'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
N皇后
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('knapsack')}
|
||||||
|
className={`px-3 py-2 rounded-md text-sm font-medium ${activeTab === 'knapsack'
|
||||||
|
? 'bg-blue-100 text-blue-700'
|
||||||
|
: 'text-gray-600 hover:bg-gray-100'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
0/1 背包
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||||
|
<div className="px-4 py-6 sm:px-0">
|
||||||
|
{activeTab === 'max_subarray' && <MaxSubarray />}
|
||||||
|
{activeTab === 'interval_scheduling' && <IntervalScheduling />}
|
||||||
|
{activeTab === 'n_queens' && <NQueens />}
|
||||||
|
{activeTab === 'knapsack' && <Knapsack />}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_URL = 'http://localhost:8000';
|
||||||
|
|
||||||
|
export const generateCase = async (size, minVal, maxVal) => {
|
||||||
|
const response = await axios.post(`${API_URL}/max_subarray/generate`, {
|
||||||
|
size,
|
||||||
|
min_val: minVal,
|
||||||
|
max_val: maxVal
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const solveCase = async (array, algorithms) => {
|
||||||
|
const response = await axios.post(`${API_URL}/max_subarray/solve`, {
|
||||||
|
array,
|
||||||
|
algorithms
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runBenchmark = async (sizes, algorithms) => {
|
||||||
|
const response = await axios.post(`${API_URL}/max_subarray/benchmark`, {
|
||||||
|
sizes,
|
||||||
|
algorithms
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Interval Scheduling API
|
||||||
|
export const generateIntervalCase = async (size, minTime, maxTime) => {
|
||||||
|
const response = await axios.post(`${API_URL}/interval_scheduling/generate`, {
|
||||||
|
size,
|
||||||
|
min_time: minTime,
|
||||||
|
max_time: maxTime
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const solveIntervalCase = async (intervals, algorithms) => {
|
||||||
|
const response = await axios.post(`${API_URL}/interval_scheduling/solve`, {
|
||||||
|
intervals,
|
||||||
|
algorithms
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runIntervalBenchmark = async (sizes, algorithms) => {
|
||||||
|
const response = await axios.post(`${API_URL}/interval_scheduling/benchmark`, {
|
||||||
|
sizes,
|
||||||
|
algorithms
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// N-Queens API
|
||||||
|
export const generateNQueensCase = async (n) => {
|
||||||
|
const response = await axios.post(`${API_URL}/n_queens/generate`, { n });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const solveNQueensCase = async (n, algorithms) => {
|
||||||
|
const response = await axios.post(`${API_URL}/n_queens/solve`, {
|
||||||
|
n,
|
||||||
|
algorithms
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runNQueensBenchmark = async (sizes, algorithms) => {
|
||||||
|
const response = await axios.post(`${API_URL}/n_queens/benchmark`, {
|
||||||
|
sizes,
|
||||||
|
algorithms
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Knapsack API
|
||||||
|
export const generateKnapsackCase = async (numItems, maxWeight, maxValue) => {
|
||||||
|
const response = await axios.post(`${API_URL}/knapsack/generate`, {
|
||||||
|
num_items: numItems,
|
||||||
|
max_weight: maxWeight,
|
||||||
|
max_value: maxValue
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const solveKnapsackCase = async (items, capacity, algorithms) => {
|
||||||
|
const response = await axios.post(`${API_URL}/knapsack/solve`, {
|
||||||
|
items,
|
||||||
|
capacity,
|
||||||
|
algorithms
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runKnapsackBenchmark = async (sizes, algorithms) => {
|
||||||
|
const response = await axios.post(`${API_URL}/knapsack/benchmark`, {
|
||||||
|
sizes,
|
||||||
|
algorithms
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||||
|
import { generateIntervalCase, solveIntervalCase, runIntervalBenchmark } from '../api';
|
||||||
|
|
||||||
|
const IntervalScheduling = () => {
|
||||||
|
const [params, setParams] = useState({ size: 10, min: 0, max: 100 });
|
||||||
|
const [currentCase, setCurrentCase] = useState(null);
|
||||||
|
const [solveResults, setSolveResults] = useState(null);
|
||||||
|
const [benchmarkResults, setBenchmarkResults] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
// Algorithm selection state
|
||||||
|
const [selectedAlgorithms, setSelectedAlgorithms] = useState({
|
||||||
|
greedy: true,
|
||||||
|
brute_force: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const algorithmsList = [
|
||||||
|
{ id: 'greedy', name: '贪心算法 (Greedy) O(N log 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 generateIntervalCase(params.size, params.min, params.max);
|
||||||
|
setCurrentCase(data.intervals);
|
||||||
|
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 solveIntervalCase(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 {
|
||||||
|
// Smaller sizes for Brute Force safety
|
||||||
|
const sizes = [5, 10, 15, 20, 22];
|
||||||
|
const results = await runIntervalBenchmark(sizes, algos);
|
||||||
|
|
||||||
|
// Transform data for Recharts
|
||||||
|
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">区间调度问题 (Interval Scheduling)</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-3 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.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">已生成区间 (预览):</h3>
|
||||||
|
<div className="font-mono text-sm bg-gray-100 p-2 rounded break-all max-h-64 overflow-y-auto">
|
||||||
|
{currentCase.map((interval, idx) => (
|
||||||
|
<div key={idx} className="mb-1">
|
||||||
|
[{interval[0]}, {interval[1]}]
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</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.result_count}</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="贪心算法" />
|
||||||
|
<Line type="monotone" dataKey="brute_force" stroke="#ff0000" name="暴力求解" />
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IntervalScheduling;
|
||||||
|
|
@ -0,0 +1,261 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||||
|
import { generateKnapsackCase, solveKnapsackCase, runKnapsackBenchmark } from '../api';
|
||||||
|
|
||||||
|
const Knapsack = () => {
|
||||||
|
const [params, setParams] = useState({ num_items: 10, max_weight: 50, max_value: 100 });
|
||||||
|
const [currentCase, setCurrentCase] = useState(null);
|
||||||
|
const [solveResults, setSolveResults] = useState(null);
|
||||||
|
const [benchmarkResults, setBenchmarkResults] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
// Algorithm selection state
|
||||||
|
const [selectedAlgorithms, setSelectedAlgorithms] = useState({
|
||||||
|
dp: true,
|
||||||
|
backtracking: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const algorithmsList = [
|
||||||
|
{ id: 'dp', name: '动态规划 (DP) O(NW)' },
|
||||||
|
{ id: 'backtracking', name: '回溯算法 (Backtracking) O(2^N)' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const getSelectedAlgoKeys = () => {
|
||||||
|
return Object.keys(selectedAlgorithms).filter(k => selectedAlgorithms[k]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerate = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await generateKnapsackCase(params.num_items, params.max_weight, params.max_value);
|
||||||
|
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 solveKnapsackCase(currentCase.items, currentCase.capacity, 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, 22, 25];
|
||||||
|
const results = await runKnapsackBenchmark(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">0/1 背包问题 (0/1 Knapsack)</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-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700">物品数量</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={params.num_items}
|
||||||
|
onChange={(e) => setParams({ ...params, num_items: 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_weight}
|
||||||
|
onChange={(e) => setParams({ ...params, max_weight: 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_value}
|
||||||
|
onChange={(e) => setParams({ ...params, max_value: 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">背包容量: {currentCase.capacity}</h3>
|
||||||
|
<h3 className="font-medium mt-2">物品列表 (预览):</h3>
|
||||||
|
<div className="font-mono text-sm bg-gray-100 p-2 rounded break-all max-h-64 overflow-y-auto">
|
||||||
|
<table className="min-w-full text-xs">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="text-left">ID</th>
|
||||||
|
<th className="text-left">重量</th>
|
||||||
|
<th className="text-left">价值</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{currentCase.items.map((item) => (
|
||||||
|
<tr key={item.id}>
|
||||||
|
<td>{item.id}</td>
|
||||||
|
<td>{item.weight}</td>
|
||||||
|
<td>{item.value}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</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">最大价值</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.max_value}</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="dp" stroke="#00ff00" name="动态规划" />
|
||||||
|
<Line type="monotone" dataKey="backtracking" stroke="#ff0000" name="回溯算法" />
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Knapsack;
|
||||||
|
|
@ -0,0 +1,248 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||||
|
import { generateCase, solveCase, runBenchmark } from '../api';
|
||||||
|
|
||||||
|
const MaxSubarray = () => {
|
||||||
|
const [params, setParams] = useState({ size: 100, min: -100, max: 100 });
|
||||||
|
const [currentCase, setCurrentCase] = useState(null);
|
||||||
|
const [solveResults, setSolveResults] = useState(null);
|
||||||
|
const [benchmarkResults, setBenchmarkResults] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
// Algorithm selection state
|
||||||
|
const [selectedAlgorithms, setSelectedAlgorithms] = useState({
|
||||||
|
brute_force: true,
|
||||||
|
divide_conquer: true,
|
||||||
|
kadane: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const algorithmsList = [
|
||||||
|
{ id: 'brute_force', name: '暴力求解 (Brute Force) O(N^2)' },
|
||||||
|
{ id: 'divide_conquer', name: '分治算法 (Divide & Conquer) O(N log N)' },
|
||||||
|
{ id: 'kadane', name: 'Kadane算法 (Kadane) O(N)' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const getSelectedAlgoKeys = () => {
|
||||||
|
return Object.keys(selectedAlgorithms).filter(k => selectedAlgorithms[k]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerate = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await generateCase(params.size, params.min, params.max);
|
||||||
|
setCurrentCase(data.array);
|
||||||
|
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 solveCase(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 {
|
||||||
|
// Smaller sizes for quick demo
|
||||||
|
const sizes = [10, 50, 100, 200, 500, 1000, 2000];
|
||||||
|
const results = await runBenchmark(sizes, algos);
|
||||||
|
|
||||||
|
// Transform data for Recharts
|
||||||
|
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">最大子数组和问题 (Maximum Subarray Sum)</h1>
|
||||||
|
|
||||||
|
{/* 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-3 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.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">已生成数组 (预览):</h3>
|
||||||
|
<p className="font-mono text-sm bg-gray-100 p-2 rounded break-all max-h-32 overflow-y-auto">
|
||||||
|
[{currentCase.slice(0, 50).join(', ')}{currentCase.length > 50 ? '...' : ''}]
|
||||||
|
</p>
|
||||||
|
<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.result}</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="brute_force" stroke="#ff0000" name="暴力求解" />
|
||||||
|
<Line type="monotone" dataKey="divide_conquer" stroke="#00ff00" name="分治算法" />
|
||||||
|
<Line type="monotone" dataKey="kadane" stroke="#0000ff" name="Kadane算法" />
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MaxSubarray;
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||||
|
import { generateNQueensCase, solveNQueensCase, runNQueensBenchmark } from '../api';
|
||||||
|
|
||||||
|
const NQueens = () => {
|
||||||
|
const [n, setN] = useState(8);
|
||||||
|
const [currentSolution, setCurrentSolution] = useState(null);
|
||||||
|
const [solutionCount, setSolutionCount] = useState(null);
|
||||||
|
const [solveTime, setSolveTime] = useState(null);
|
||||||
|
const [benchmarkResults, setBenchmarkResults] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleSolve = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
// Generate is trivial (just N), so we skip explicit generate step for UI simplicity
|
||||||
|
// and just call solve directly
|
||||||
|
const results = await solveNQueensCase(n, ["backtracking"]);
|
||||||
|
if (results && results.length > 0) {
|
||||||
|
const res = results[0];
|
||||||
|
setCurrentSolution(res.first_solution);
|
||||||
|
setSolutionCount(res.solution_count);
|
||||||
|
setSolveTime(res.time_seconds);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error solving N-Queens:", error);
|
||||||
|
alert("求解失败");
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBenchmark = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const sizes = [4, 8, 10, 12, 13];
|
||||||
|
const results = await runNQueensBenchmark(sizes, ["backtracking"]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render the chess board
|
||||||
|
const renderBoard = () => {
|
||||||
|
if (!currentSolution) return null;
|
||||||
|
|
||||||
|
const boardSize = 400;
|
||||||
|
const cellSize = boardSize / n;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative border-2 border-gray-800"
|
||||||
|
style={{ width: boardSize, height: boardSize }}
|
||||||
|
>
|
||||||
|
{Array.from({ length: n }).map((_, row) => (
|
||||||
|
Array.from({ length: n }).map((_, col) => {
|
||||||
|
const isBlack = (row + col) % 2 === 1;
|
||||||
|
const hasQueen = currentSolution[row] === col;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`${row}-${col}`}
|
||||||
|
className={`absolute flex items-center justify-center ${isBlack ? 'bg-gray-600' : 'bg-white'}`}
|
||||||
|
style={{
|
||||||
|
width: cellSize,
|
||||||
|
height: cellSize,
|
||||||
|
top: row * cellSize,
|
||||||
|
left: col * cellSize
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{hasQueen && <span className="text-red-500 font-bold" style={{ fontSize: cellSize * 0.7 }}>♛</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6 space-y-8">
|
||||||
|
<h1 className="text-3xl font-bold text-gray-800">N皇后问题 (N-Queens)</h1>
|
||||||
|
<p className="text-gray-600">目标:在 N×N 的棋盘上放置 N 个皇后,使得它们互不攻击。</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{/* Controls */}
|
||||||
|
<div className="bg-white p-6 rounded-lg shadow space-y-6">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700">棋盘大小 (N)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="4"
|
||||||
|
max="14"
|
||||||
|
value={n}
|
||||||
|
onChange={(e) => setN(parseInt(e.target.value))}
|
||||||
|
className="mt-1 block w-full border rounded-md p-2"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-gray-500 mt-1">建议 N ≤ 14,否则计算时间过长</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleSolve}
|
||||||
|
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 ? '计算中...' : '求解 (Backtracking)'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{solutionCount !== null && (
|
||||||
|
<div className="mt-4 p-4 bg-green-50 rounded-md">
|
||||||
|
<p className="font-medium text-green-800">找到解的数量: {solutionCount}</p>
|
||||||
|
<p className="text-sm text-green-600">耗时: {solveTime?.toFixed(6)} 秒</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Visualization */}
|
||||||
|
<div className="bg-white p-6 rounded-lg shadow flex justify-center items-center min-h-[450px]">
|
||||||
|
{currentSolution ? renderBoard() : (
|
||||||
|
<div className="text-gray-400 text-center">
|
||||||
|
<p>点击求解以查看结果</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 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">性能测试 (N=4 to 13)</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="backtracking" stroke="#ff0000" name="回溯算法" />
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NQueens;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import App from './App.jsx'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
upstream backend {
|
||||||
|
server backend:8000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# Frontend static files
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy API requests to backend
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://backend/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy docs to backend
|
||||||
|
location /docs {
|
||||||
|
proxy_pass http://backend/docs;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /openapi.json {
|
||||||
|
proxy_pass http://backend/openapi.json;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue