# -*- Mode: Python -*- # N people meet for the first time in a room. # If we want everybody to shake hands with everyone else, then we'll # # (N-1)*N # need ------- handshakes in total. # 2 # # ok, now think like an algorithms geek. Tell the N people how to # do it. The simplest solution is to pick a guy, have him shake # everyone's hand, and sit down. But that's pretty inefficient. O(N^2). # Can we do better? Sure! # What's the best we can possibly do? # Well, we can't shake more than N/2 times in one step. That works # out to a best possible solution of N-1 steps, with no wasted shakes. O(N). # I played around with this for a while, but the solution is not very # obvious. It seemed to me that if you put people into two rings, # with one rotating, that some combination of swap/rotate could solve # it. So I wrote this code to search that space. There are a lot of # solutions, but no super-obvious simple pattern that leaps out. # However, there's one that I think is simple enough to explain to a # crowd, look at the bottom of this file for my choice. import sys from pprint import pprint as pp def gen_perms (n): result = [] for i in range (n): for j in range (i): if i < j: result.append ((i,j)) else: result.append ((j,i)) return result # ok, now treat it like a ring puzzle # see if we can always find a solution # 3 # 0 # 2 1 # 5 4 def rotations (p, name): # there are two rotations, either forward or backward # 012345 => 012453, 012534 # so the second half rotates perm0 = p[:] perm1 = p[:] n = len(p)/2 for i in range (n-1): perm0[i+n] = p[i+n+1] perm1[i+n+1] = p[i+n] perm0[-1] = p[n] perm1[n] = p[-1] return [(name+'l',perm0), (name+'r', perm1)] def gen_moves (p): moves = rotations (p, '') # now try a swap then turn n = len(p)/2 for i in range (n): perm = p[:] perm[i], perm[i+n] = p[i+n], p[i] moves.extend (rotations (perm, str(i))) return moves def gen_pairs (p): n = len(p) / 2 r = set() for a, b in [(p[i], p[i+n]) for i in range (n)]: if a < b: r.add ((a,b)) else: r.add ((b,a)) return r solutions = [] def search (p, solution, left, d=0): # algorithm only works for even numbers assert (n%2 == 0) moves = gen_moves (p) for op, move in moves: # a move is only valid if # 1) it creates pairs we have not yet seen pairs = gen_pairs (move) for pair in pairs: if pair not in left: break else: # it's a valid move. # are we done? if len(left) == len (pairs): solutions.append (solution + [(op, move)]) #solutions.append (solution + [op]) else: search (move, solution + [(op, move)], left - pairs, d+1) #search (move, solution + [op], left - pairs, d+1) def go (n): global solutions p = range (n) # remove the initial pairs... left = set (gen_perms (n)) left = left - gen_pairs (p) search (p, [], left, 0) for i in range (len (solutions)): solution = solutions[i] print '%2d:' % (i,), for o, s in solution: print o, print def animate (solution): W = sys.stdout.write p = range (n) solution = [ ('start', p)] + solution for i in range (len (solution)): move, state = solution[i] rp = [str(x) for x in state] W ('%6s: %s\n' % (move, ' '.join (rp[:n/2]))) W ('%6s %s\n' % ('' , ' '.join (rp[n/2:]))) # SPOILER: a relatively simple solution. # arrange the people into two rings. at each step, everyone in the outer ring # shakes with the person next to them on the inner ring. # 1a) shake. # 1b) swap the inner/outer persons at position 0. # 1c) rotate the outer ring clockwise. # 2) repeat 1 a-c (i.e., swap/rotate position 0 twice) # 3) repeat steps 1-2, but swapping at position 1,2,3,4,... # 4) after you've swapped each position twice, you're done! # It's probably more practical to line the people up in two rows, just # remember that when a guy pops off the end upon rotation to put him # on the other end. # my chosen solution always shows up as solution 3N+1 if __name__ == '__main__': if len(sys.argv) > 1: n = int (sys.argv[1]) else: n = 6 go (n) print 'my chosen solution:' animate (solutions[3*n+1])