# jemdoc: addcss{rbh.css}, addcss{jacob.css} = hard problems ~~~ {}{raw} bad   hard   knap:bf   knap:dp   knap:dpv   set cover   sat   ~~~ #~~~ #{}{raw} #

baby, you with the bad boys now

#~~~ # #~~~ #from Out of Sight by Elmore Leonard #~~~ ~~~ {}{raw}

hard problems

~~~ ~~~ for many problems, no known polytime algorithm - [https://en.wikipedia.org/wiki/Graph_isomorphism graph isomorphism] - [https://en.wikipedia.org/wiki/Knapsack_problem 0-1 knapsack] - [https://en.wikipedia.org/wiki/Set_cover_problem set cover] - [https://en.wikipedia.org/wiki/Boolean_satisfiability_problem satisfiability] ~~~ ~~~ {recent news} - [https://www.sciencenews.org/article/new-algorithm-cracks-graph-problem graph iso in quasi-poly time ?] - [https://anthonybonato.com/2017/01/18/graph-isomorphism-and-babais-proof/ Bonato on Babai] - [https://en.wikipedia.org/wiki/Time_complexity\#Quasi-polynomial_time time complexity] ~~~ ~~~ {}{raw}

0-1 knapsack: brute force

~~~ ~~~ for each subset - if weight ok and value best-so-far -- print subset, update best-so-far-value ~~~ ~~~ {}{} def powerset(iterable): # all subsets #https://docs.python.org/2/library/itertools.html#recipes "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" s = list(iterable) return chain.from_iterable(combinations(s, r) for r in range(len(s)+1)) def bruteforce(n,val,wt,W): indices = range(n) # [0 1 .. n-1] bestVal = 0 for indexset in powerset(indices): v, w = 0, 0 for t in indexset: v+= val[t] w+= wt[t] if w <= W and v > bestVal: bestVal = v print indexset, w, v ~~~ ~~~ {brute force 0-1 knapsack} runtime ? - {{ Ω(2n) }} correct ? - yes: considers every subset can we do better ? ? ? ~~~ ~~~ {}{raw}

0-1 knapsack: dynamic programming

~~~ ~~~ subproblems ? - by set of allowed items - by allowed total weight set of allowed items ? - {{ {1}   {1 2}   {1 2 3}   ... }} allowed total weight ? - {{ 0   1   2   ... }} {{K[w][j]}}: best value with items {{1..j}}, total wt {{≤}} w compute {{K[w][j]}} - item j out? value {{K[w][j-1]}} - item j in? value {{K[w-w_j][j-1] + v_j}} - so set {{K[w][j]}} to max of these two ~~~ ~~~ {0-1 knapsack: dp}{} #python indexing: val[t],wt[t] corresponds to K[][t+1] # K[w][j] will be best value knapsack, weight at most w, # using subset of { item_1, item_2, ..., item_j } # warning: K[][] needs an extra 0th column, so its indices # are off-by-one w.r.t. val[], wt[] # eg. val[v], wt[t] refer to t+1'st item # also K[][t+1] refers to t+1'st item def solveknapsack(val,wt,W): n = len(val) K = [[0 for j in xrange(n+1)] for w in xrange(W+1)] for j in range(1,n+1): for w in range(W+1): K[w][j] = K[w][j-1] if w < wt[j-1] \ else max(K[w][j-1], K[w-wt[j-1]][j-1]+val[j-1]) ~~~ ~~~ {0-1 knapsack: dp example}{} max value knapsack, weight at most 27 ? val [ 6, 9, 7, 9, 8, 7] wt [11, 6, 8, 10, 8, 9] 0 [ 0, 0, 0, 0, 0, 0, 0] 1 [ 0, 0, 0, 0, 0, 0, 0] 2 [ 0, 0, 0, 0, 0, 0, 0] 3 [ 0, 0, 0, 0, 0, 0, 0] 4 [ 0, 0, 0, 0, 0, 0, 0] 5 [ 0, 0, 0, 0, 0, 0, 0] 6 [ 0, 0, 9, 9, 9, 9, 9] 7 [ 0, 0, 9, 9, 9, 9, 9] 8 [ 0, 0, 9, 9, 9, 9, 9] 9 [ 0, 0, 9, 9, 9, 9, 9] 10 [ 0, 0, 9, 9, 9, 9, 9] 11 [ 0, 6, 9, 9, 9, 9, 9] 12 [ 0, 6, 9, 9, 9, 9, 9] 13 [ 0, 6, 9, 9, 9, 9, 9] 14 [ 0 ? ? ? ? ? ?] 14 [ 0, 6, 9, 16, 16, 17, 17] 15 [ 0, 6, 9, 16, 16, 17, 17] 16 [ 0, 6, 9, 16, 18, 18, 18] 17 [ 0, 6, 15, 16, 18, 18, 18] 18 [ 0, 6, 15, 16, 18, 18, 18] 19 [ 0, 6, 15, 16, 18, 18, 18] 20 [ 0, 6, 15, 16, 18, 18, 18] 21 [ 0, 6, 15, 16, 18, 18, 18] 22 [ 0, 6, 15, 16, 18, 24, 24] 23 [ 0, 6, 15, 16, 18, 24, 24] 24 [ 0, ? ? ? ? ? ?] 24 [ 0, 6, 15, 16, 25, 26, 26] 25 [ 0, 6, 15, 22, 25, 26, 26] 26 [ 0, 6, 15, 22, 25, 26, 26] 27 [ 0, 6, 15, 22, 25, 26, 26] ----------------------------- solution 24 [0, 1, 0, 1, 1, 0] val 26 ~~~ ~~~ {dp knapsack wc superpoly} - consider these instances -- each value and weight has n bits, so in {{[ 2n-1 ... 2n -1 ]}} -- average weight {{  ( 2n-1+2n -1 ) / 2   ≈   .75 2n}} -- let W be n times average weight, so {{  ≈ .75 n 2n}} -- input size {{  Θ( n2 )   }} bits - brute force wc time -- {{2n}} subsets, each weight {{Θ(n)}} bits -- for each subset t of size s, perform s add'ns -- total number of add'ns is sum, over all subsets t, of size(t)-1 -- sum, over all subsets t, of size(t) is {{n*2n-1}} (pair each subset with its complement) -- total number add'ns: {{   n*2n-1 - 2n   }} in {{  Θ( n * 2n)}} -- avg add'n involves n/2 weights, sum has {{  Θ( n2 )}} bits, time {{  Θ(n2)}} -- total alg'm time in {{  Θ( n3 2n )}} - dyn. prog. wc time -- avg wt. {{  Θ(n2)}} bits -- avg add'n cost {{  Θ(n2)}} -- wc W {{  Θ(n * 2n)}} -- {{Θ(n2 * n * W )   =   Θ( n4 2n )}}, worse than BF! - let {{  t = n2   so n = √ t     let f(t) = t2 2√t}} -- each alg. input {{  Θ( t ) bits   }} wc time {{  O( f(t) )}} - [https://en.wikipedia.org/wiki/Time_complexity\#Table_of_common_time_complexities time complexities] - {{2O(log t)}} polynomial - {{2o(t)}} subexponential - {{2Ω(t)}} exponential - here -- {{ limt → ∞   2k log t / f(t)   = 0,  }} so f(t) superpoly -- {{f(t)/2t   =   t2 2√t  /  2t   =   t2 (2 / 2√t)√t}} -- {{ limt → ∞   f(t) / 2t   = 0,  }} so f(t) subexp ~~~ ~~~ {}{raw}

0-1 knapsack: another DP solution

~~~ ~~~ - how do you like DPW if -- each weight is in {{ { 2^{n-1}, ..., 2^n -1 } }} and -- each values is in {{ { 1, ..., 2n } }} ? - organize subproblems by value (instead of weight) - also useful in constructing knapsack PTAS (polytime approx scheme) ~~~ ~~~ {}{} def knapDPV(val,wt,V): #dynamic programming by value # A[v][j] is min weight of subset of items 0..j with exact val v infinity = 1 + sum(val) #larger than any possible sum of values n = len(val) A = [[infinity for j in xrange(n)] for v in xrange(V+1)] for j in range(n): A[0][j] = 0 A[val[0]][0] = wt[0] # end initialization for v in range(1,V+1): # row 0 already initialized for j in range(1,n): # column 0 " " A[v][j] = A[v][j-1] if v < val[j] \ else min(A[v][j-1], A[v - val[j]][j-1] + wt[j]) ~~~ ~~~ - [http://webdocs.cs.ualberta.ca/~hayward/304/asn/knapdpvdemo.pdf knapsack DPV hand-trace] ~~~ ~~~ {knapsack DPV} - time in O(nV), but V can be big - correctness ? ~~~ ~~~ {}{raw}

setcover

~~~ ~~~ {brute force set cover}{} from random import randint from itertools import chain, combinations def powerset(iterable): #https://docs.python.org/2/library/itertools.html#recipes "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" s = list(iterable) return chain.from_iterable(combinations(s, r) for r in range(len(s)+1)) def settostring(s,n): # set as pretty binary string x = '' for j in range(n): if j in s: x += ' *' else: x += ' -' return x def gensubset(a,b,n): # subset of 0..n-1, each with prob a/b s = set() for j in range(n): if randint(1,b) <= a: s.add(j) return s def initsubsets(n,m,a,b): # m subs of 0..n-1 # an element in a subset with probability a/b L = [] # list of subsets for _ in range(m): L.append(gensubset(a,b,n)) sumlen = 0 for x in L: sumlen += len(x) print settostring(x,n) print 'universe size', n,' avg subset size', sumlen*1.0/m return L def bruteforce(n,m,L): indices = range(m) # [0 1 .. m-1] bestcover = () for indexset in powerset(indices): subsetunion = set() for t in indexset: subsetunion |= L[t] # indexsets sorted by cardinality, so first found is min if len(subsetunion) == n: print 'size', len(indexset), 'cover', indexset for j in indexset: print settostring(L[j],n) return # so return once one is found n,m,a,b = 40,25,2,11 L = initsubsets(n,m,a,b) bruteforce(n,m,L) ~~~ ~~~ {set cover example}{} set universe 0 1 2 3 4 5 6 7 8 9 0 - - * - - - - * * - 1 - * - - - - - * - * 2 - - - - * - - * * - 3 - - - - - - - - * - 4 - - - - - * - * * - 5 - - - - * - * * - * 6 * * - * - - - - - - 7 - - - - - - * * - - min cover (0, 4, 5, 6) 0 - - * - - - - * * - 4 - - - - - * - * * - 5 - - - - * - * * - * 6 * * - * - - - - - - ~~~ ~~~ {another example}{} set universe 0 1 2 3 4 5 6 7 8 910111213141516171819 0 * - * - - - * - * - - - - * - * - - * - 1 - - - * - * - * - - - * - - * * - - - - 2 - * - - - - * - * - - - - - - - - * - - 3 - - - * - - - - - - - - - - - - - - * - 4 * - - - - * - * - * - - - - - * - - - - 5 - - - * - - * * - - - - - * - * - - - - 6 - - - - - * - - * - - * * - - * - - * - 7 - * - - * - - - * - * * * - * * - - - - 8 - * - - * - - - * - - - - * * - - - * * 9 * - * - - - * * - - - - - - - - - - - * 10 - * - - - - * - - * * - * - - - * - * - min cover (0, 1, 2, 8, 10) 0 * - * - - - * - * - - - - * - * - - * - 1 - - - * - * - * - - - * - - * * - - - - 2 - * - - - - * - * - - - - - - - - * - - 8 - * - - * - - - * - - - - * * - - - * * 10 - * - - - - * - - * * - * - - - * - * - ~~~ ~~~ {greedy set cover} - [https://webdocs.cs.ualberta.ca/~hayward/304/asn/greedysc.pdf greedy set cover] - Dasgupta et al. section 5.4 ~~~ ~~~ {school location problem}{} - input: some towns, and a threshold distance (e.g. 30 km) - problem: build schools in minimum number of towns, s.t. distance to school at most the threshold - model as a graph problem G = (V,E) - V = set of towns - E = pairs of towns at most threshold distance apart - for each town, its neighbor-set is set of all towns whose distance away is at most the threshold - problem: pick the minimum number of neighbor-sets that cover V - in other words: this is an instance of min set cover ~~~ ~~~ {how hard is set cover?}{} - answer: NP-hard, by reduction from vertex cover ~~~ ~~~ {}{raw}

satisfiability

~~~ ~~~ given a boolean formula in CNF, is it satisfiable? assume input has n variables and m clauses brute force: {{2n}} possible assignments brute force: worst case time {{ O(m2n) }} ~~~ ~~~ {sat example}{} ( x1 or ~x2 or x3 ) and ( x1 or ~x2 or x4 ) and ( x1 or x2 or x3 ) and ( x1 or x2 or x4 ) and (~x1 or ~x2 or ~x3 ) and (~x1 or ~x2 or x3 ) and (~x1 or x2 or x4 ) and ( x2 or ~x3 or ~x4 ) and ( x2 or x3 or ~x4 ) [ 1, -2, 3] [ 1, -2, -4] [ 1, 2, 3] [ 1, 2, 4] [ -1, -2, -3] [ -1, -2, 3] [ -1, 2, 4] [ 2, -3, -4] [ 2, 3, -4] satisfiable (0, 1, 1, 0) ~~~ ~~~ {another example}{} [ -4, -5, 6] [ -4, 5, -6] [ -2, 4, -5] [ -2, 5, -6] [ -1, -3, -4] [ -1, -3, 4] [ -1, 4, -6] [ 1, -4, 5] [ 1, -3, -5] [ 1, -3, 4] [ 1, 5, -6] [ 2, 5, 6] [ 3, -5, -6] [ 4, -5, 6] [ 4, 5, -6] [ 4, 5, 6] satisfiable (1, 1, 0, 1, 0, 0) ~~~ ~~~ {brute force sat solver}{} # I/O: literals x1 -x1 x2 -x2 ... rept'd by 1 -1 2 -2 ... def clause_sat(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 clause_sat(clause,asnmt): return False return True n, k, m = 20, 3, 90 #max m: (n choose k)(2^k) myf = formula(n,k,m) show(myf, False) allAsn = product([0,1],repeat=n) for a in allAsn: if sat(myf,a): print 'satisfiable', a ~~~