Algorithm for Shuffling a Linked List in n log n time

后端 未结 7 648
北荒
北荒 2021-01-30 05:37

I\'m trying to shuffle a linked list using a divide-and-conquer algorithm that randomly shuffles a linked list in linearithmic (n log n) time and logarithmic (log n) extra space

7条回答
  •  忘了有多久
    2021-01-30 06:02

    You can actually do better than that: the best list shuffle algorithm is O(n log n) time and just O(1) space. (You can also shuffle in O(n) time and O(n) space by constructing a pointer array for the list, shuffling it in place using Knuth and re-threading the list accordingly.)

    Complexity proof

    To see why O(n log n) time is minimal for O(1) space, observe that:

    • With O(1) space, updating the successor of an arbitrary list element necessarily takes O(n) time.
    • Wlog, you can assume that whenever you update one element, you also update all the other elements (leaving them unchanged if you wish), as this also takes just O(n) time.
    • With O(1) space, there are at most O(1) elements to choose from for the successor of any element you're updating (which specific elements these are will obviously depend on the algorithm).
    • Therefore, a single O(n) time update of all the elements could result in at most c^n different list permutations.
    • Since there are n! = O(n^n) = O(c^(n log n)) possible list permutations, you require at least O(log n) updates.

    Linked-list data structure (because Python)

    import collections
    
    class Cons(collections.Sequence):
        def __init__(self, head, tail=None):
            self.head = head
            self.tail = tail
    
        def __getitem__(self, index):
            current, n = self, index
            while n > 0:
                if isinstance(current, Cons):
                    current, n = current.tail, n - 1
                else:
                    raise ValueError("Out of bounds index [{0}]".format(index))
            return current
    
        def __len__(self):
            current, length = self, 0
            while isinstance(current, Cons):
                current, length = current.tail, length + 1
            return length
    
        def __repr__(self):
            current, rep = self, []
            while isinstance(current, Cons):
                rep.extend((str(current.head), "::"))
                current = current.tail
            rep.append(str(current))
            return "".join(rep)
    

    Merge-style algorithm

    Here is an O(n log n) time and O(1) space algorithm based on iterative merge sort. The basic idea is simple: shuffle the left half, then the right half, then merge them by randomly selecting from the two lists. Two things worth noting:

    1. By making the algorithm iterative rather than recursive, and returning a pointer to the new last element at the end of every merge step, we reduce the space requirement to O(1) while keeping the time cost minimal.
    2. To make sure that all possibilities are equally likely for all input sizes, we use probabilities from the Gilbert–Shannon–Reeds model riffle shuffle when merging (see http://en.wikipedia.org/wiki/Gilbert%E2%80%93Shannon%E2%80%93Reeds_model).
    import random
    
    def riffle_lists(head, list1, len1, list2, len2):
        """Riffle shuffle two sublists in place. Returns the new last element."""
        for _ in range(len1 + len2):
            if random.random() < (len1 / (len1 + len2)):
                next, list1, len1 = list1, list1.tail, len1 - 1
            else:
                next, list2, len2 = list2, list2.tail, len2 - 1
            head.tail, head = next, next
        head.tail = list2
        return head
    
    def shuffle_list(list):
        """Shuffle a list in place using an iterative merge-style algorithm."""
        dummy = Cons(None, list)
        i, n = 1, len(list)
        while (i < n):
            head, nleft = dummy, n
            while (nleft > i):
                head = riffle_lists(head, head[1], i, head[i + 1], min(i, nleft - i))
                nleft -= 2 * i
            i *= 2
        return dummy[1]
    

    Another algorithm

    Another interesting O(n log n) algorithm that produces not-quite-uniform shuffles involves simply riffle shuffling the list 3/2 log_2(n) times. As described in http://en.wikipedia.org/wiki/Gilbert%E2%80%93Shannon%E2%80%93Reeds_model, this leaves only a constant number of bits of information.

提交回复
热议问题