exact (accept non-poly time)
brute force
backtracking
branch and bound
heuristic (accept non-optimal)
approximation (non-optimal, but some bound?)
brute force
n variables: 2n possible assignments
backtracking
consider partial solutions, abort when partial cannot extend
branch and bound (optimization problems)
keep upper bound on extension value
abort extension when bound less than best value of some complete solution
#generate random t-uniform formula import random # literals x1 -x1 x2 -x2 ... output as 1 -1 2 -2 ... # for sorting clauses, # literals 1 -1 2 -2 ... n -n represented by # integers 0 1 2 3 ... n-2 n-1 def litToInt(lit): if lit>0: return 2*lit-2 return 2*(-lit)-1 def intToLit(n): if 0==n%2: return 1+n/2 return -(1+n/2) def randclause(n,k): # n-var uniform random k-clause # floyd's alg, from Bentley's prog. pearls "A sample of brilliance" # if lit in clause then -lit not in clause S = [] for j in range(n+1-k,n+1): t = random.randint(1,j) if S.count(t)>0: S.append(j) else: S.append(t) S.sort() for j in range(k): if random.randint(0,1)==0: S[j] *= -1 return S def formula(n,t,m): # n variables, t-uniform, m clauses f = [] while len(f)<m: new = randclause(n,t) duplicate = False for j in f: if new == j: duplicate = True if not duplicate: f.append(new) # sort lexicographic using x1 < -x1 < x2 < -x2 < ... for m in range(len(f)): for c in range(len(f[m])): f[m][c] = litToInt(f[m][c]) f.sort() for m in range(len(f)): for c in range(len(f[m])): f[m][c] = intToLit(f[m][c]) return f def prettyClause(c): psn = -1 for lit in c: psn, psnOld = litToInt(lit), psn for j in range(psn-(psnOld+1)): print ' ', print '%2d' % lit, print '' def plainClause(c): print '[', for j in range(len(c)-1): print '%2d,' % c[j], print '%2d]' % c[len(c)-1] def show(f,pretty): for c in f: if pretty: prettyClause(c) else: plainClause(c) f = formula(5,3,20) show(f,False) show(f,True)
# literals x_1 ... x_n represented by 1 2 ... n # literals -x_1 ... -x_n represented by -1 -2 ... -n def clausesat(clause,asnmt): for var in clause: if ((var>0 and asnmt[var-1]==1) or (var<0 and asnmt[-(var+1)]==0)): return True return False def sat(formula, asnmt): for clause in formula: if not clausesat(clause,asnmt): return False return True def showf(f): for j in f: print j n,m = 10,40 #max m is n choose k times 2^k, where k=2or3 allAsn = itertools.product([0,1],repeat=n) myf = randsat.formula(3,n,m) print "\nrandom formula",n,"vars",m,"clauses" showf(myf) for a in allAsn: if sat(myf,a): print a break
S <- original problem while not S.isempty() P <- S.pop() P yields solution? yes: return "solved" no: maybe: add resulting subproblems to S return "no solution"
subproblems ?
set next variable TRUE
set next variable FALSE
subproblems: F/T,
subproblems: F/T,
no solution (2-clauses), abort this subproblem
must have T, T, T, solution
return solution: F T T T T * *
better: Davis-Putnam-Logemann–Loveland
# assignment vector: one entry for each variable # set a[0] to UNSAT if formula unsatisfiable UNSAT, UNKNOWN, FALSE, TRUE = -2, -1, 0, 1 def emptyAsnmt(n): a = [UNKNOWN]*n return a def showf(f): for j in f: print j def showfa(f,a): for x in a: print x, print '' showf(f) def fixliteral(t, f, a): # in f, set literal t True, update a index = abs(t)-1 #print "set var",index+1, ("T" if t>0 else "F") assert a[index]== UNKNOWN a[index] = (TRUE if t>0 else FALSE) for clause in f[:]: clauseSat = False for literal in clause[:]: if literal == -t: clause.remove(literal) if len(clause)==0: # f unsat a[0] == UNSAT return elif literal == t: clauseSat = True if clauseSat: f.remove(clause) def mycopy(f,a): newa = list(a) newf = [] for clause in f: newf.append(list(clause)) return newf, newa def sat(f): return (True if len(f)==0 else False) def unsat(a): return (True if a[0]==UNSAT else False) def backsat(f,a): if unsat(a): return f,a if sat(f): return f,a minj = f.index(min(f,key=len)) # clause with fewest literals if len(f[minj])==0: a[0]= UNSAT return f,a if len(f[minj])==1: fixliteral(f[minj][0], f, a) return backsat(f,a) #split: 2 possible bool. vals for literal f[minj][0] #print "split A:", f[minj][0] fcopy, acopy = mycopy(f,a) fixliteral(f[minj][0], f, a) f,a = backsat(f,a) if sat(f): return f, a f,a = fcopy, acopy #print "split B:", -f[minj][0] fixliteral(-f[minj][0], f, a) return backsat(f, a) n,m = 6,20 #max m is n choose k times 2^k, where k=2or3 myf = randsat.formula(3,n,m) asn = emptyAsnmt(n) showfa(myf, asn) f,a = backsat(myf,asn) showfa(f,a)
for typical subpath, e.g. AXCDR…
a lower bound on an extension is:
cost of subpath AXCDR, plus …
min cost edge from A to rest of vertices, plus …
min cost edge from R to rest of vertices, plus …
MST cost on rest of vertices
A 1 B 4 | | 3 H - - - - - 2 - - - - - C 2 5 4 5 G - - - - - 6 - - - - - D 6 | | 1 F 3 E start anywhere, say A AB: bound ? cost AB, 1 + min edge A to rest of vertices, 4 + min edge B to rest of vertices, 3 + MST on rest of vertices, 13 = 21 AF: bound ? cost AF, 5 + min edge A to rest, 1 + min edge F to rest, 3 + MST on rest, 13 = 22 AH: bound ? cost AH, 4 + min edge A to rest, 1 + min edge H to rest, 2 + MST on rest, 17 = 24 so try AB first ... ABC: bound 22 ABE: bound 25 so try ABC first ... ABCD: bound 25 ABCH: bound 23 so try ABCH first ... ABCH extends uniquely to to ABCHGDEFA, cost 23 ... ABCD (>= 25) cannot be better ... ABE (>= 25) cannot be better ... AH (>= 24) cannot be better At this point, we can stop: have found best tour that starts from A and next vertex is one of B,H (and every such TSP has this property; if we explore AF... we will just find our TSP in reverse).
greedy algorithm (repeatedly pick set covering most uncovered vertices) gives log-base-2(n) approx ratio
not hard to find inputs that achieve this ratio
classic 1.5 approximation for metric (i.e. satisfying triangle inequality) TSP