Code is taking too much time

后端 未结 4 1286
死守一世寂寞
死守一世寂寞 2020-12-17 07:02

I wrote code to arrange numbers after taking user input. The ordering requires that the sum of adjacent numbers is prime. Up until 10 as an input code is working fine. If I

4条回答
  •  予麋鹿
    予麋鹿 (楼主)
    2020-12-17 07:53

    Here's my take on a solution. As Tim Peters pointed out, this is a Hamiltonian path problem. So the first step is to generate the graph in some form.

    Well the zeroth step in this case to generate prime numbers. I'm going to use a sieve, but whatever prime test is fine. We need primes upto 2 * n since that is the largest any two numbers can sum to.

    m = 8
    n = m + 1 # Just so I don't have to worry about zero indexes and random +/- 1's
    primelen = 2 * m
    prime = [True] * primelen
    prime[0] = prime[1] = False
    for i in range(4, primelen, 2):
        prime[i] = False
    for i in range(3, primelen, 2):
        if not prime[i]:
            continue
        for j in range(i * i, primelen, i):
            prime[j] = False
    

    Ok, now we can test for primality with prime[i]. Now its easy to make the graph edges. If I have a number i, what numbers can come next. I'll also make use of the fact that i and j have opposite parity.

    pairs = [set(j for j in range(i%2+1, n, 2) if prime[i+j])
             for i in range(n)]
    

    So here pairs[i] is set object whose elements are integers j such that i+j is prime.

    Now we need to walk the graph. This is really where the time consuming part is and all further optimizations will be done here.

    chains = [
        ([], set(range(1, n))
    ]
    

    chains is going to keep track of the valid paths as we walk them. The first element in the tuple will be your result. The second element is all the unused numbers, or unvisited nodes. The idea is to take one chain out of the queue, take a step down the path and put it back.

    while chains:
        chain, unused = chains.pop()
    
        if not chain:
            # we haven't even started, all unused are valid
            valid_next = unused
        else:
            # We need numbers that are both unused and paired with the last node
            # Using sets makes this easy
            valid_next = unused & pairs[chains[-1]]
    
        for num in valid_next:
            # Take a step to the new node and add the new path back to chains
            # Reminder, its important not to mutate anything here, always make new objs
            newchain  = chain + [num]
            newunused = unused - set([num])
            chains.append( (newchain, newunused) )
    
            # are we done?
            if not newunused:
                print newchain
                chains = False
    

    Notice that if there is no valid next step, the path is removed without a replacement.

    This is really memory inefficient, but runs in a reasonable time. The biggest performance bottleneck is walking the graph, so the next optimization would be popping and inserting paths in intelligent places to prioritize the most likely paths. It might be helpful to use a collections.deque or different container for your chains in that case.

    EDIT

    Here is an example of how you can implement your path priority. We will assign each path a score and keep the chains list sorted by this score. For a simple example I will suggest that paths containing "harder to use" nodes are worth more. That is for each step on a path the score will increase by n - len(valid_next) The modified code will look something like this.

    import bisect
    chains = ...
    chains_score = [0]
    while chains:
         chain, unused = chains.pop()
         score = chains_score.pop()
         ...
    
         for num in valid_next:
              newchain = chain + [num]
              newunused = unused - set([num])
              newscore = score + n - len(valid_next)
              index = bisect.bisect(chains_score, newscore)
              chains.insert(index, (newchain, newunused))
              chains_score.insert(index, newscore)
    

    Remember that insertion is O(n) so the overhead of adding this can be rather large. Its worth doing some analysis on your score algorithm to keep the queue length len(chains) managable.

提交回复
热议问题