ussu321's picture
Upload 50 files
bd86de0 verified
Raw
History Blame Contribute Delete
16.4 kB
"""
USSU Algorithm Analyzer v4.0 - ADA Theory & Analysis Tools
Master Theorem, Recurrence Relations, Amortized Analysis, NP-Completeness.
"""
import math
import random
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass
@dataclass
class MasterTheoremResult:
a: float
b: float
f_n: str
log_b_a: float
case: int
solution: str
explanation: str
verified: bool
class ADATools:
"""Analysis and Design of Algorithms theoretical tools"""
# ==================== ASYMPTOTIC NOTATION REFERENCE ====================
@staticmethod
def big_o_reference() -> List[Dict]:
return [
{"notation": "O(1)", "name": "Constant", "example": "Array access, hash lookup", "class": "P"},
{"notation": "O(log n)", "name": "Logarithmic", "example": "Binary search, balanced BST", "class": "P"},
{"notation": "O(√n)", "name": "Square Root", "example": "Prime checking, Grover's algorithm", "class": "P"},
{"notation": "O(n)", "name": "Linear", "example": "Linear search, array traversal", "class": "P"},
{"notation": "O(n log n)", "name": "Linearithmic", "example": "Merge sort, heap sort, Dijkstra", "class": "P"},
{"notation": "O(n²)", "name": "Quadratic", "example": "Bubble sort, matrix multiplication", "class": "P"},
{"notation": "O(n³)", "name": "Cubic", "example": "Floyd-Warshall, naive matrix mult", "class": "P"},
{"notation": "O(2ⁿ)", "name": "Exponential", "example": "Recursive Fibonacci, subsets", "class": "NP"},
{"notation": "O(n!)", "name": "Factorial", "example": "Traveling salesman brute force", "class": "NP"},
]
@staticmethod
def asymptotic_definitions() -> Dict[str, str]:
return {
"Big-O (O)": "f(n) = O(g(n)) if ∃ c > 0, n₀ > 0 such that 0 ≤ f(n) ≤ c·g(n) ∀ n ≥ n₀. Upper bound.",
"Big-Omega (Ω)": "f(n) = Ω(g(n)) if ∃ c > 0, n₀ > 0 such that 0 ≤ c·g(n) ≤ f(n) ∀ n ≥ n₀. Lower bound.",
"Big-Theta (Θ)": "f(n) = Θ(g(n)) if ∃ c₁, c₂ > 0, n₀ > 0 such that c₁·g(n) ≤ f(n) ≤ c₂·g(n) ∀ n ≥ n₀. Tight bound.",
"Little-o (o)": "f(n) = o(g(n)) if ∀ c > 0, ∃ n₀ > 0 such that 0 ≤ f(n) < c·g(n) ∀ n ≥ n₀. Strictly smaller.",
"Little-omega (ω)": "f(n) = ω(g(n)) if ∀ c > 0, ∃ n₀ > 0 such that 0 ≤ c·g(n) < f(n) ∀ n ≥ n₀. Strictly larger.",
}
# ==================== MASTER THEOREM SOLVER ====================
@staticmethod
def master_theorem(a: float, b: float, f_n: str, n: int = 1000) -> MasterTheoremResult:
"""
Solve T(n) = aT(n/b) + f(n)
f_n should be in form like 'n', 'n^2', 'n*log(n)', '1', etc.
"""
log_b_a = math.log(a) / math.log(b)
# Parse f(n) to determine polynomial degree
f_n_clean = f_n.replace(" ", "").lower()
k = 0 # polynomial degree of f(n)
if f_n_clean in ['1', 'c', 'constant']:
k = 0
elif 'n^' in f_n_clean or 'n**' in f_n_clean:
# Extract exponent
parts = f_n_clean.split('^') if '^' in f_n_clean else f_n_clean.split('**')
if len(parts) > 1:
try:
k = float(parts[1].split('*')[0].split('/')[0])
except:
k = 1
elif f_n_clean == 'n':
k = 1
elif 'n*log' in f_n_clean or 'nlog' in f_n_clean:
k = 1 # n log n is n^1 * polylog, treated as n^1 for case 2 extended
elif 'log' in f_n_clean and 'n' not in f_n_clean:
k = 0 # log n is polylog
epsilon = 1e-9
diff = k - log_b_a
if diff < -epsilon:
case = 1
solution = f"Θ(n^{log_b_a:.3f})"
explanation = (f"f(n) = O(n^{k}) is polynomially smaller than n^{log_b_a:.3f}. "
f"Since k < log_b(a) = {log_b_a:.3f}, Case 1 applies.")
elif abs(diff) < epsilon:
case = 2
if 'log' in f_n_clean:
solution = f"Θ(n^{log_b_a:.3f} · log² n)" if 'log' in f_n_clean and k == log_b_a else f"Θ(n^{log_b_a:.3f} · log n)"
explanation = (f"f(n) = Θ(n^{log_b_a:.3f} · log^k n). Case 2 (extended) applies. "
"Work is evenly distributed across levels.")
else:
solution = f"Θ(n^{log_b_a:.3f} · log n)"
explanation = (f"f(n) = Θ(n^{log_b_a:.3f}). Case 2 applies. "
"Work at each level is roughly equal.")
else:
case = 3
# Check regularity condition (simplified)
solution = f"Θ({f_n})"
explanation = (f"f(n) = Ω(n^{k}) is polynomially larger than n^{log_b_a:.3f}. "
f"Since k > log_b(a) = {log_b_a:.3f}, Case 3 applies. "
"Root dominates total cost.")
return MasterTheoremResult(a, b, f_n, log_b_a, case, solution, explanation, True)
# ==================== RECURSION TREE CALCULATOR ====================
@staticmethod
def recursion_tree_cost(a: float, b: float, f_n_func, n: float, levels: int = 6) -> Dict:
"""Calculate level-by-level cost for T(n) = aT(n/b) + f(n)"""
tree = []
total_cost = 0.0
current_n = n
current_nodes = 1
for level in range(levels):
if current_n < 1:
break
level_cost = current_nodes * f_n_func(current_n)
tree.append({
'level': level,
'nodes': int(current_nodes),
'subproblem_size': current_n,
'cost': level_cost,
})
total_cost += level_cost
current_nodes *= a
current_n /= b
return {
'tree': tree,
'total_cost_approx': total_cost,
'levels_computed': len(tree),
}
# ==================== SUBSTITUTION METHOD DEMO ====================
@staticmethod
def substitution_guess(guess: str, recurrence: str, base_case: str) -> Dict:
"""Demonstrate substitution method with a guess"""
return {
'method': 'Substitution',
'recurrence': recurrence,
'guess': guess,
'base_case': base_case,
'steps': [
f"1. Guess: T(n) = {guess}",
f"2. Substitute into recurrence: {recurrence}",
f"3. Verify base case: {base_case}",
f"4. Use mathematical induction to prove bound.",
],
'note': 'Substitution requires creativity to guess the correct bound. Often used after recursion tree intuition.'
}
# ==================== AMORTIZED ANALYSIS ====================
@staticmethod
def amortized_dynamic_array(n_operations: int = 20) -> Dict:
"""Aggregate analysis of dynamic array doubling"""
costs = []
size = 1
count = 0
for i in range(1, n_operations + 1):
if count == size:
# Double: copy all elements + insert new
cost = size + 1 # size copies + 1 insert
size *= 2
else:
cost = 1
count += 1
costs.append({'operation': i, 'actual_cost': cost, 'array_size': size, 'elements': count})
total = sum(c['actual_cost'] for c in costs)
amortized = total / n_operations
return {
'scenario': 'Dynamic Array (Aggregate Analysis)',
'operations': costs,
'total_cost': total,
'amortized_cost': amortized,
'theoretical': 2.0, # O(1) amortized, actual approaches 2 per op
'explanation': 'Expensive doubling operations are rare. Total cost ≤ 2n, so amortized cost is O(1).'
}
@staticmethod
def amortized_binary_counter(n_bits: int = 8, increments: int = 20) -> Dict:
"""Amortized analysis of binary counter using accounting method"""
counter = [0] * n_bits
history = []
total_flips = 0
for inc in range(1, increments + 1):
flips = 0
i = 0
while i < n_bits and counter[i] == 1:
counter[i] = 0
flips += 1
i += 1
if i < n_bits:
counter[i] = 1
flips += 1
total_flips += flips
history.append({
'increment': inc,
'flips': flips,
'counter_state': ''.join(map(str, reversed(counter))),
'total_flips': total_flips,
})
return {
'scenario': 'Binary Counter (Accounting Method)',
'history': history,
'total_flips': total_flips,
'amortized_per_increment': total_flips / increments,
'theoretical': 2.0,
'explanation': 'Each bit flips every 2^i increments. Total flips for n increments ≤ 2n. Amortized cost per increment: O(1).'
}
@staticmethod
def amortized_stack_multipop(operations: List[str]) -> Dict:
"""Accounting method for stack with multipop"""
stack = []
history = []
total_cost = 0
for op in operations:
if op.startswith('push'):
stack.append(int(op.split()[1]))
cost = 2 # 1 for push, 1 credit stored on item
total_cost += cost
history.append({'op': op, 'stack_size': len(stack), 'actual_cost': 1, 'charge': 2, 'credits': len(stack)})
elif op.startswith('pop'):
if stack:
stack.pop()
cost = 0 # Paid by credit
total_cost += 0
history.append({'op': op, 'stack_size': len(stack), 'actual_cost': 1, 'charge': 0, 'credits': len(stack)})
elif op.startswith('multipop'):
k = int(op.split()[1])
actual = min(k, len(stack))
for _ in range(actual):
stack.pop()
cost = 0 # Paid by credits
total_cost += 0
history.append({'op': op, 'stack_size': len(stack), 'actual_cost': actual, 'charge': 0, 'credits': len(stack)})
return {
'scenario': 'Stack with Multipop (Accounting Method)',
'history': history,
'total_amortized_cost': total_cost,
'explanation': 'Charge 2 per push: 1 for push, 1 credit stored. Pop/Multipop use stored credits. Amortized cost per operation: O(1).'
}
# ==================== PROBABILISTIC ANALYSIS ====================
@staticmethod
def hiring_problem(n_candidates: int) -> Dict:
"""Expected cost of hiring problem (order statistics)"""
# Expected number of hires = H_n = ln(n) + gamma
harmonic = sum(1/i for i in range(1, n_candidates + 1))
return {
'problem': 'Hiring Problem',
'n_candidates': n_candidates,
'expected_hires': harmonic,
'approximation': math.log(n_candidates) + 0.5772,
'explanation': 'With random order, expected hires = H_n ≈ ln(n) + γ. Each candidate has 1/i chance of being best so far.'
}
@staticmethod
def randomized_quicksort_analysis(n: int) -> Dict:
"""Expected comparisons for randomized quicksort"""
# E[comparisons] ≈ 2n ln n - 1.51n
expected = 2 * n * math.log(n) - 1.51 * n if n > 1 else 0
return {
'algorithm': 'Randomized Quicksort',
'input_size': n,
'expected_comparisons': expected,
'expected_time': f'O(n log n) ≈ {expected:.0f} comparisons',
'explanation': 'Random pivot selection makes running time independent of input distribution. Expected comparisons ≈ 2n ln n.'
}
# ==================== NP-COMPLETENESS REFERENCE ====================
@staticmethod
def np_completeness_reference() -> Dict[str, List[Dict]]:
return {
'complexity_classes': [
{'class': 'P', 'definition': 'Decision problems solvable in polynomial time by deterministic Turing machine.', 'examples': ['Sorting', 'Shortest Path', 'MST', 'Matching']},
{'class': 'NP', 'definition': 'Decision problems where solutions can be verified in polynomial time.', 'examples': ['SAT', 'Clique', 'Hamiltonian Cycle', 'Subset Sum']},
{'class': 'NP-Complete', 'definition': 'Problems in NP that are at least as hard as every problem in NP. If one is in P, all are.', 'examples': ['3-SAT', 'Vertex Cover', 'TSP (decision)', 'Graph Coloring']},
{'class': 'NP-Hard', 'definition': 'Problems at least as hard as NP-Complete, but not necessarily in NP.', 'examples': ['TSP (optimization)', 'Halting Problem', 'Chess Generalization']},
{'class': 'co-NP', 'definition': 'Complements of NP problems. Verifying NO answers in polynomial time.', 'examples': ['co-SAT', 'Primality (now in P)']},
],
'reductions': [
{'from': '3-SAT', 'to': 'Clique', 'type': '≤p'},
{'from': 'Clique', 'to': 'Vertex Cover', 'type': '≤p'},
{'from': 'Vertex Cover', 'to': 'Hamiltonian Cycle', 'type': '≤p'},
{'from': 'Hamiltonian Cycle', 'to': 'TSP', 'type': '≤p'},
],
'open_problems': [
'P vs NP (Millennium Prize Problem, $1M)',
'NP vs co-NP',
'P = BPP? (Randomized algorithms)',
'Quantum polynomial time (BQP) vs NP',
]
}
# ==================== PARADIGM COMPARISON ====================
@staticmethod
def paradigm_comparison() -> List[Dict]:
return [
{
'paradigm': 'Divide & Conquer',
'approach': 'Break into independent subproblems, solve recursively, combine.',
'examples': ['Merge Sort', 'Quick Sort', 'Strassen Matrix', 'Closest Pair'],
'key_property': 'Optimal substructure',
'when_to_use': 'Subproblems are independent and non-overlapping.',
'time_pattern': 'T(n) = aT(n/b) + f(n)',
},
{
'paradigm': 'Dynamic Programming',
'approach': 'Break into overlapping subproblems, memoize to avoid recomputation.',
'examples': ['Knapsack', 'LCS', 'Edit Distance', 'Matrix Chain'],
'key_property': 'Optimal substructure + Overlapping subproblems',
'when_to_use': 'Subproblems overlap and optimal solution contains optimal subsolutions.',
'time_pattern': 'Usually polynomial in states',
},
{
'paradigm': 'Greedy',
'approach': 'Make locally optimal choice at each step, never reconsider.',
'examples': ['Dijkstra', 'Prim/Kruskal', 'Huffman', 'Activity Selection'],
'key_property': 'Greedy choice property + Optimal substructure',
'when_to_use': 'Local optimal leads to global optimal. Proof by exchange argument.',
'time_pattern': 'Often O(n log n) due to sorting',
},
{
'paradigm': 'Backtracking',
'approach': 'DFS over state space, prune invalid branches.',
'examples': ['N-Queens', 'Sudoku', 'Graph Coloring', 'Subset Sum'],
'key_property': 'State space exploration with constraints',
'when_to_use': 'Need all solutions or exact answer, exponential space acceptable.',
'time_pattern': 'Exponential in worst case',
},
{
'paradigm': 'Branch & Bound',
'approach': 'BFS/DFS with bounds to prune suboptimal branches.',
'examples': ['TSP', 'Job Assignment', 'Knapsack (0/1)'],
'key_property': 'Bound function + Priority queue',
'when_to_use': 'Optimization problems where bounds can prune effectively.',
'time_pattern': 'Exponential worst, often better in practice',
},
]