Wieża Hanoi Sort

21

Napisz funkcję / podprogram, aby posortować listę liczb całkowitych w stylu Tower of Hanoi .

Otrzymasz stos liczb całkowitych. To jest główny stos.

Dostajesz także dwa kolejne stosy pomocników. Te stosy pomocnicze mają jednak unikalną właściwość: każdy element musi być mniejszy lub mieć taki sam rozmiar jak element pod nim. Główny stos nie ma takich ograniczeń.

Twoim zadaniem jest posortowanie głównego stosu na miejscu, umieszczenie pod nim największych liczb całkowitych. Twoja funkcja / podprogram zwróci (lub równoważny) liczbę ruchów wykonanych podczas sortowania stosu.
Uwaga: musisz posortować główny stos na miejscu , bez sortowania na innym stosie i nazywania go odpowiedzią. Jeśli jednak z jakiegoś powodu nie możesz tego zrobić, możesz symulować stosy zmienne, ale pamiętaj, że jest to rodzaj Wieży Hanoi; są tylko 3 kołki i tylko 1 kołek może być nieuporządkowany.

Twoja funkcja / podprogram może w dowolnym momencie sprawdzić dowolny stos, ale może wykonać ruch tylko poprzez pop-up i push. Pojedynczy ruch to pop z jednego stosu, który jest popychany na inny.

Przetestuj swoją funkcję / podprogram dla każdej permutacji pierwszych 6 liczb naturalnych. Innymi słowy, sprawdź swoją funkcję / podprogram pod kątem {1},{2},...,{6},{1,1},{1,2},...,{1,6},{2,1},...(powinna to być suma możliwości lub możliwości (dzięki Howard za poprawienie mojej matematyki)). Funkcja / podprogram, który porusza elementy najmniej razy, wygrywa.61+62+...+6655986

Justin
źródło
@JanDvorak To był pomysł na testy. Jeśli programista musi uruchomić funkcję 46656 razy, dlaczego miałby tak długo czekać na wyjście? Czy istnieje inny dobry sposób ograniczenia tego rodzaju rzeczy?
Justin
Jakoś podoba mi się wyzwanie typu blind-zap: możesz powiedzieć tylko „przenieś ze stosu x na stos y” i sprawdź, czy twój ruch się powiedzie, a jeśli tak, zostaniesz za to obciążony; punkty bonusowe to niepowodzenie ruchu jest wskazywane przez prawidłowe rzucenie wyjątku / powrót.
John Dvorak
3
Lista podanych przez ciebie „permutacji” zawiera 6**1+6**2+...+6**6=55986elementy.
Howard
1
@ m.buettner Wyróżnia się to, że są to elementy kartezjańskich produktów od 1 do 6 razy . Prawdopodobnie nazwałbym to „zbiorem permutacji każdego elementu zestawu mocy pierwszych 6 liczb naturalnych, z wyjątkiem zbioru zerowego”.
Justin
1
@Quincunx z wyjątkiem tego, że zestaw mocy nie zawiera zestawów z powtarzającymi się liczbami. ;) ... ale nie sądzę, że i tak powinniśmy traktować to zbyt poważnie, o ile wszyscy wiemy o elementach zestawu.
Martin Ender

Odpowiedzi:

4

Java - optymalne rozwiązanie (1080544 ruchów)

To rozwiązanie buduje najkrótsze drzewo ścieżek od celu i do tyłu, a następnie przechodzi od stanu początkowego do celu. Dużo miejsca na poprawę prędkości, ale nadal rozwiązuje wszystkie 55986 problemów w około minutę.

Zakładając, że algorytm jest poprawnie wdrożony, powinno to być teoretycznie najlepsze rozwiązanie.

import java.util.*;

public class HanoiSort {

    public static void main(String[] args) {
        int sumNumMoves = 0;
        for (int size = 1; size <= 6; ++size) {
            Collection<List<Integer>> initMainStacks = generateInitMainStacks(Collections.<Integer>emptyList(), size);
            for (List<Integer> initMainStack : initMainStacks) {
                sumNumMoves += solve(initMainStack);
            }
        }
        System.out.println(sumNumMoves);
    }

    /*
     * Recursively create initial main stacks
     */
    private static Collection<List<Integer>> generateInitMainStacks(List<Integer> mainStack, int remainingSize) {
        Collection<List<Integer>> initMainStacks;
        if (remainingSize > 0) {
            initMainStacks = new ArrayList<>();
            for (int number = 1; number <= 6; ++number) {
                List<Integer> nextMainStack = new ArrayList<>(mainStack);
                nextMainStack.add(number);
                initMainStacks.addAll(generateInitMainStacks(nextMainStack, remainingSize - 1));
            }
        } else {
            List<Integer> initMainStack = new ArrayList<>(mainStack);
            initMainStacks = Collections.singleton(initMainStack);
        }
        return initMainStacks;
    }

    private static final List<Integer> EMPTY_STACK = Collections.emptyList();

    /*
     * Create a shortest path tree, starting from the target state (sorted main stack). Break when the initial state
     * is found, since there can be no shorter path. This is akin to building a chess endgame tablebase.
     *
     * Traverse the path from initial state to the target state to count the number of moves.
     */
    private static int solve(List<Integer> initMainStack) {
        List<List<Integer>> initState = Arrays.asList(new ArrayList<>(initMainStack), EMPTY_STACK, EMPTY_STACK);
        List<Integer> targetMainStack = new ArrayList<>(initMainStack);
        Collections.sort(targetMainStack);
        List<List<Integer>> targetState = Arrays.asList(new ArrayList<>(targetMainStack), EMPTY_STACK, EMPTY_STACK);
        Map<List<List<Integer>>,List<List<Integer>>> tablebase = new HashMap<>();
        Deque<List<List<Integer>>> workQueue = new ArrayDeque<>();
        tablebase.put(targetState, null);
        workQueue.add(targetState);
        while (!tablebase.containsKey(initState)) {
            assert !workQueue.isEmpty() : initState.toString();
            List<List<Integer>> state = workQueue.removeFirst();
            Collection<List<List<Integer>>> prevStates = calcPrevStates(state);
            for (List<List<Integer>> prevState : prevStates) {
                if (!tablebase.containsKey(prevState)) {
                    tablebase.put(prevState, state);
                    workQueue.add(prevState);
                }
            }
        }

        int numMoves = 0;
        List<List<Integer>> state = tablebase.get(initState);
        while (state != null) {
            ++numMoves;
            state = tablebase.get(state);
        }
        return numMoves;
    }

    /*
     * Given a state, calculate all possible previous states
     */
    private static Collection<List<List<Integer>>> calcPrevStates(List<List<Integer>> state) {
        Collection<List<List<Integer>>> prevStates = new ArrayList<>();
        for (int fromStackNo = 0; fromStackNo < 3; ++fromStackNo) {
            List<Integer> fromStack = state.get(fromStackNo);
            if (!fromStack.isEmpty()) {
                int number = fromStack.get(0);
                for (int toStackNo = 0; toStackNo < 3; ++toStackNo) {
                    if (toStackNo != fromStackNo) {
                        List<Integer> toStack = state.get(toStackNo);
                        if ((toStackNo == 0) || toStack.isEmpty() || (toStack.get(0) >= number)) {
                            List<List<Integer>> prevState = new ArrayList<>(state);
                            List<Integer> prevFromStack = new ArrayList<>(fromStack);
                            prevFromStack.remove(0);
                            prevState.set(fromStackNo, prevFromStack);
                            List<Integer> prevToStack = new ArrayList<>(toStack);
                            prevToStack.add(0, number);
                            prevState.set(toStackNo, prevToStack);
                            prevStates.add(prevState);
                        }
                    }
                }
            }
        }
        return prevStates;
    }

}
MrBackend
źródło
Chcesz wyjaśnić więcej, co rozumiesz przez „drzewo najkrótszej ścieżki”?
justhalf
W każdym razie dzięki za odpowiedź, cieszę się, że mogę osiągnąć prawie optymalne rozwiązanie, używając tylko heurystyki :)
justhalf
Drzewo najkrótszej ścieżki to drzewo, w którym każdy węzeł / stan ma jedną „następną” krawędź, prowadzącą do węzła / stanu, który z kolei ma najkrótszą odległość do stanu głównego / docelowego (= posortowany główny stos). Mogą istnieć inne kandydujące kolejne węzły, które mają taką samą odległość lub więcej, ale żadne z nich nie są bliżej nasady. W przypadku tego problemu wszystkie krawędzie w drzewie najkrótszej ścieżki mają odległość jednego, ponieważ przejście z jednego stanu do drugiego zajmuje jeden ruch. Zasadniczo pełne drzewo najkrótszej ścieżki zawiera rozwiązanie dla wszystkich stanów, które mają ten sam stan docelowy.
MrBackend
@ justhalf Zapomniałem wspomnieć o tobie w moim komentarzu. BTW, patrz także en.wikipedia.org/wiki/Retrograde_analysis
MrBackend
5

C - 2547172 dla 55986 wejść

Jest tu dużo miejsca na ulepszenia. Dla własnego rozsądku uprościłem to, aby można było sprawdzić tylko górny element każdego stosu. Zniesienie tego narzuconego ograniczenia pozwoliłoby na optymalizacje, takie jak wcześniejsze ustalenie ostatecznego zamówienia i próba zminimalizowania liczby ruchów wymaganych do jego osiągnięcia. Ciekawym przykładem jest to, że moja implementacja ma najgorsze zachowanie, jeśli główny stos jest już posortowany.

Algorytm:

  1. Wypełnij oba stosy pomocnicze (tutaj miejsce na optymalizację, możliwe przyporządkowanie do tego stosu na podstawie pewnego rodzaju obrotu).
  2. Scalanie posortuj stosy pomocnicze z powrotem na główny stos.
  3. Powtarzaj 1-2, aż główny stos zostanie posortowany (ale w odwrotnej kolejności).
  4. Odwróć główny stos (więcej miejsca na optymalizację, wielokrotne tasowanie wielu takich samych elementów).

Analiza:

  • Dodatkową złożonością przestrzeni jest O (n) (dla dwóch pomocniczych stosów), co jest dobre, ponieważ wymagało tego problem.
  • Z mojego punktu widzenia złożoność czasowa wynosi O (n ^ 2). Korekty są mile widziane.

#include <assert.h>
#include <stdio.h>

#define SIZE 6

int s0[SIZE + 1];
int s1[SIZE + 1];
int s2[SIZE + 1];

int
count(int *stack)
{
    return stack[0];
}

int
top(int *stack)
{
    return stack[stack[0]];
}

void
push(int *stack, int value)
{
    assert(count(stack) < SIZE && "stack overflow");
    assert((stack == s0 || count(stack) == 0 || value <= top(stack)) && "stack order violated");
    stack[++stack[0]] = value;
}

int
pop(int *stack)
{
    int result = stack[stack[0]];
    assert(count(stack) > 0 && "stack underflow");
    stack[stack[0]] = 0;
    stack[0]--;
    return result;
}

int permutations;

void
permute(int len, int range, void (*cb)(void))
{
    int i;
    if(len == 0)
    {
        permutations++;
        cb();
        return;
    }
    for(i = 1; i <= range; i++)
    {
        push(s0, i);
        permute(len - 1, range, cb);
        pop(s0);
    }
}

void
print(void)
{
    int i;
    for(i = 1; i <= count(s0); i++)
    {
        printf("%d ", s0[i]);
    }
    printf("\n");
}

int save[SIZE + 1];

void
copy(int *src, int *dst)
{
    int i;
    for(i = 0; i <= SIZE; i++)
    {
        dst[i] = src[i];
    }
}

int total;

void
move(int *src, int *dst)
{
    total++;
    push(dst, pop(src));
}

void
merge(void)
{
    while(1)
    {
        if(count(s1) == 0 && count(s2) == 0)
        {
            break;
        }
        else if(count(s1) == 0 || (count(s2) > 0 && top(s2) < top(s1)))
        {
            move(s2, s0);
        }
        else
        {
            move(s1, s0);
        }
    }
}

void
reverse(void)
{
    while(1)
    {
        while(count(s2) == 0 || top(s0) == top(s2))
        {
            move(s0, s2);
        }
        if(count(s0) == 0 || top(s2) < top(s0))
        {
            while(count(s2) > 0)
            {
                move(s2, s0);
            }
            break;
        }
        while(count(s0) > 0 && (count(s1) == 0 || top(s0) <= top(s1)))
        {
            move(s0, s1);
        }
        while(count(s2) > 0)
        {
            move(s2, s0);
        }
        merge();
    }
}

void
sort(void)
{
    while(1)
    {
        if(count(s0) == 0)
        {
            merge();
            reverse();
            break;
        }
        else if(count(s1) == 0 || top(s1) >= top(s0))
        {
            move(s0, s1);
        }
        else if(count(s2) == 0 || top(s2) >= top(s0))
        {
            move(s0, s2);
        }
        else
        {
            merge();
        }
    }
}

void
helper(void)
{
    copy(s0, save);
    sort();
    copy(save, s0);
}

int
main(void)
{
    permute(1, 6, helper);
    permute(2, 6, helper);
    permute(3, 6, helper);
    permute(4, 6, helper);
    permute(5, 6, helper);
    permute(6, 6, helper);
    printf("%d\n", permutations);
    printf("%d\n", total);
    return 0;
}
laindir
źródło
4

Python, 3983838 3912258 przenosi ponad 55986 danych wejściowych

To jest bardzo nieefektywne.

Dodam całkowitą liczbę ruchów po tym, jak PO wyjaśni, czy dotyczą one wszystkich tych przypadków, czy konkretnych innych przypadków.


from itertools import product       # Required for testing
import time                         # Required if you want to see it in action.

from pycallgraph import PyCallGraph
from pycallgraph.output import GraphvizOutput

def main( l ):
    ################### Data ###################
    global a , b , c , d , copy , total_moves
    total_moves = 0

    a = [ x for x in l ]  # Input stack, order doesn't matter, we'll try to empty this one
    b = []                # Usable stack, restricted by order, we'll try to get the final sorted order here
    c = []                # Usable stack, restricted by order, but we'll try to keep it as empty as possible

    d = { 'a':a , 'b':b , 'c':c }  # Passing the stacks to the nested functions by their names as a string
    copy = [ x for x in a ]        # reference copy, read-only


    ################### Functions ###################
    def is_correct( stack ):
        if d[ stack ] == sorted( copy , reverse = True ):
            return True
        else:
            return False

    def reverse_exchange( source , destination , keep = 0 ):
        #
        # keep is the number of elements to keep at the bottom of the source stack
        # The remaining top elements are moved to the destination stack
        # We first move the top elements to stack a
        # and from a to c so that the order is preserved
        # effectively splitting the source stack into two
        #

        i = 0
        while len( d[ source ] ) > keep :
            move( source , 'a' )
            i += 1
        else:
            while i > 0:
                move( 'a' , destination )
                i -= 1

    # def validate( source , destination ):
    #   # 
    #   # Validates the give move
    #   #
    #   if len( d[ source ] ) == 0:
    #       return False
    #   if destination == 'a' or len( d[ destination ] ) == 0:
    #       return True
    #   else:
    #       if d[ destination ][ len( d[ destination ] ) - 1 ] >= d[ source ][ len( d[ source ] ) - 1 ]:
    #           return True
    #       else:
    #           return False

    def move( source , destination ):
        global total_moves
        # if validate( source , destination ):
        d[ destination ].append( d[ source ].pop() )

        total_moves += 1

            # Uncomment the following to view the workings step-by-step
            # print '\n'
            # print a
            # print b
            # print c
            # print '\n'
            # time.sleep(0.1)

        return True
        # else:
        #   return False


    ################### Actual logic ###################
    while ( not is_correct( 'a' ) ):

        copy_b   = [x for x in b ]                         # Checking where the topmost element of a
        top_of_a = a[ len(a) - 1 ]                         # should be inserted
        copy_b.append( top_of_a )                          #
        sorted_copy_b = sorted( copy_b , reverse = True )  #

        reverse_exchange( 'b' , 'c' , sorted_copy_b.index( top_of_a ) )                                                  # Sandwiching the top-most element
        move( 'a' , 'b' )                                                                                                # to proper position in b
        while (len(b) > 0 and len(c) > 0 and len(a) > 0) and (sorted (b , reverse = True)[0] <= a[len(a) - 1] <= c[0]):  #  # Optimization
            move( 'a' , 'b' )                                                                                            #  #
        reverse_exchange( 'c' , 'b' )                                                                                    # b is always sorted, c is always empty

        if is_correct( 'b' ):                     # Just moving b to a
            while ( not is_correct( 'a' ) ):      # The entire program focuses on "insertion sorting"
                reverse_exchange( 'b' , 'c' , 1 ) # elements of a onto b while keeping c empty
                move( 'b' , 'a' )                 # 
                if len(c) > 0 :                       #
                    reverse_exchange( 'c' , 'b' , 1 ) # Slightly more efficient
                    move('c' , 'a' )                  #



    return total_moves


# with PyCallGraph( output= GraphvizOutput() ):


    ################### Test cases #############
i = 0
for elements in xrange( 1 , 7 ):
    for cartesian_product in product( [ 1 , 2 , 3 , 4 , 5 , 6 ] , repeat = elements ):
        input_list = [ int( y ) for y in cartesian_product ]
        i += main(input_list)
        print i
print i

Wyjaśnienie

Co, komentarze nie są dla ciebie wystarczająco dobre?


Uwaga do OP: Dziękujemy za nie zrobienie tego golfa.

użytkownik80551
źródło
Uważam, że jeśli istnieje bardziej interesujący sposób wykonania wyzwania inny niż [golf-golf], należy to zrobić w ten sposób.
Justin
Ok, to się nie udaje dla [1,1,2]. W pythonie rozważenie listy [2,1,1]jest sposobem na uzyskanie [2,1,1].index(1)2, tzn. Zaczynając od wyższej klasy?
user80551
@Quincunx Nie, [2,1,1,3,5].index(1)==2zamiast1
user80551
Er, list.index(data)zwraca indeks elementu dataw list. I nie wiem, indeks datatj1
user80551
Hack byłbylen(list)-(list[::-1].index(1))
user80551
2

Python: 1668293 1579182 1,5244,04 1450842 1093195 ruchów

Główną metodą jest main_to_help_bestprzenoszenie wybranych elementów ze stosu głównego na stos pomocniczy. Ma flagę, everythingktóra określa, czy chcemy przenieść wszystko do określonego destination, czy też chcemy zachować tylko największydestination podczas gdy reszta w drugim pomocniku.

Załóżmy, że przechodzimy do dstużywania pomocnikahelper , funkcję można z grubsza opisać następująco:

  1. Znajdź pozycje największych elementów
  2. Przenieś wszystko na najwyżej umieszczony element helper rekurencyjnie
  3. Przenieś największy element do dst
  4. Wciśnij z powrotem helperdo głównego
  5. Powtarzaj 2-4, aż pojawią się największe elementy dst
  6. za. Jeśli everythingjest ustawiony, rekurencyjnie przenieś elementy w main do dst
    b. W przeciwnym razie rekurencyjnie przenieś elementy w głównym dohelper

Algorytm sortowania głównego ( sort2w moim kodzie) wywoła następnie za main_to_help_bestpomocą everythingset to False, a następnie przeniesie największy element z powrotem do main, a następnie przeniesie wszystko z pomocnika z powrotem do main, utrzymując go posortowane.

Więcej objaśnień zawartych w komentarzach w kodzie.

Zasadniczo zastosowałem następujące zasady:

  1. Zachowaj jednego pomocnika, aby zawierał maksymalną liczbę elementów
  2. Zachowaj innego pomocnika, aby zawierał inne elementy
  3. Nie rób niepotrzebnych ruchów tak często, jak to możliwe

Zasada 3 jest realizowana przez nie liczenie ruchu, jeśli źródłem jest poprzedni cel (tj. Właśnie przenieśliśmy główny do help1, a następnie chcemy przejść z help1 do help2), a ponadto zmniejszamy liczbę ruchów o 1, jeśli przesuwają go z powrotem do pierwotnej pozycji (tj. główny do help1, a następnie help1 do main). Ponadto, jeśli wszystkie poprzednie nruchy przenoszą tę samą liczbę całkowitą, możemy faktycznie zmienić ich kolejność n. Wykorzystujemy to również w celu dalszego zmniejszenia liczby ruchów.

Jest to ważne, ponieważ znamy wszystkie elementy na głównym stosie, więc można to interpretować jako widzenie w przyszłości, że cofniemy element z powrotem, nie powinniśmy wykonywać tego ruchu.

Przykładowy przebieg (stosy są wyświetlane od dołu do góry - więc pierwszy element jest na dole):

Długość 1
Przenosi: 0
Zadania: 6
Maks .: 0 ([1])
Średnia: 0,000

Długość 2
Przenosi: 60
Zadania: 36
Maks .: 4 ([1, 2])
Średnia: 1,667

Długość 3
Przenosi: 1030
Zadania: 216
Maks .: 9 ([2, 3, 1])
Średnia: 4,769

Długość 4
Przenosi: 11765
Zadania: 1296
Maks .: 19 ([3, 4, 2, 1])
Średnia: 9,078

Długość 5
Przenosi: 112325
Zadania: 7776
Maks .: 33 ([4, 5, 3, 2, 1])
Średnia: 14,445

Długość 6
Przenosi: 968015
Zadania: 46656
Maks .: 51 ([5, 6, 4, 3, 2, 1])
Średnia: 20,748

--------------
Ogólny
Przenosi: 1093195
Zadania: 55986
Średnia: 19,526

Widzimy, że najgorszym przypadkiem jest umieszczenie największego elementu na drugim dnie, podczas gdy pozostałe są sortowane. W najgorszym przypadku możemy zobaczyć, że algorytmem jest O (n ^ 2).

Liczba ruchów jest oczywiście minimalna n=1i n=2jak widać z wyniku, i uważam, że jest to również minimalna wartość dla większych wartości n, chociaż nie mogę tego udowodnić.

Więcej wyjaśnień znajduje się w kodzie.

from itertools import product

DEBUG = False

def sort_better(main, help1, help2):
    # Offset denotes the bottom-most position which is incorrect
    offset = len(main)
    ref = list(reversed(sorted(main)))
    for idx, ref_el, real_el in zip(range(len(main)), ref, main):
        if ref_el != real_el:
            offset = idx
            break

    num_moves = 0
    # Move the largest to help1, the rest to help2
    num_moves += main_to_help_best(main, help1, help2, offset, False)
    # Move the largest back to main
    num_moves += push_to_main(help1, main)
    # Move everything (sorted in help2) back to main, keep it sorted
    num_moves += move_to_main(help2, main, help1)
    return num_moves

def main_to_help_best(main, dst, helper, offset, everything=True):
    """
    Moves everything to dst if everything is true,
    otherwise move only the largest to dst, and the rest to helper
    """
    if offset >= len(main):
        return 0
    max_el = -10**10
    max_idx = -1
    # Find the location of the top-most largest element
    for idx, el in enumerate(main[offset:]):
        if el >= max_el:
            max_idx = idx+offset
            max_el = el
    num_moves = 0
    # Loop from that position downwards
    for max_idx in range(max_idx, offset-1, -1):
        # Processing only at positions with largest element
        if main[max_idx] < max_el:
            continue
        # The number of elements above this largest element
        top_count = len(main)-max_idx-1
        # Move everything above this largest element to helper
        num_moves += main_to_help_best(main, helper, dst, max_idx+1)
        # Move the largest to dst
        num_moves += move(main, dst)
        # Move back the top elements
        num_moves += push_to_main(helper, main, top_count)
    # Here, the largest elements are in dst, the rest are in main, not sorted
    if everything:
        # Move everything to dst on top of the largest
        num_moves += main_to_help_best(main, dst, helper, offset)
    else:
        # Move everything to helper, not with the largest
        num_moves += main_to_help_best(main, helper, dst, offset)
    return num_moves

def verify(lst, moves):
    if len(moves) == 1:
        return True
    moves[1][0][:] = lst
    for src, dst, el in moves[1:]:
        move(src, dst)
    return True

def equal(*args):
    return len(set(str(arg.__init__) for arg in args))==1

def move(src, dst):
    dst.append(src.pop())
    el = dst[-1]
    if not equal(dst, sort.lst) and list(reversed(sorted(dst))) != dst:
        raise Exception('HELPER NOT SORTED: %s, %s' % (src, dst))

    cur_len = len(move.history)
    check_idx = -1
    matched = False
    prev_src, prev_dst, prev_el = move.history[check_idx]
    # As long as the element is the same as previous elements,
    # we can reorder the moves
    while el == prev_el:
        if equal(src, prev_dst) and equal(dst, prev_src):
            del(move.history[check_idx])
            matched = True
            break
        elif equal(src, prev_dst):
            move.history[check_idx][1] = dst
            matched = True
            break
        elif equal(dst, prev_src):
            move.history[check_idx][0] = src
            matched = True
            break
        check_idx -= 1
        prev_src, prev_dst, prev_el = move.history[check_idx]
    if not matched:
        move.history.append([src, dst, el])
    return len(move.history)-cur_len

def push_to_main(src, main, amount=-1):
    num_moves = 0
    if amount == -1:
        amount = len(src)
    if amount == 0:
        return 0
    for i in range(amount):
        num_moves += move(src, main)
    return num_moves

def push_to_help(main, dst, amount=-1):
    num_moves = 0
    if amount == -1:
        amount = len(main)
    if amount == 0:
        return 0
    for i in range(amount):
        num_moves += move(main, dst)
    return num_moves

def help_to_help(src, dst, main, amount=-1):
    num_moves = 0
    if amount == -1:
        amount = len(src)
    if amount == 0:
        return 0
    # Count the number of largest elements
    src_len = len(src)
    base_el = src[src_len-amount]
    base_idx = src_len-amount+1
    while base_idx < src_len and base_el == src[base_idx]:
        base_idx += 1

    # Move elements which are not the largest to main
    num_moves += push_to_main(src, main, src_len-base_idx)
    # Move the largest to destination
    num_moves += push_to_help(src, dst, base_idx+amount-src_len)
    # Move back from main
    num_moves += push_to_help(main, dst, src_len-base_idx)
    return num_moves

def move_to_main(src, main, helper, amount=-1):
    num_moves = 0
    if amount == -1:
        amount = len(src)
    if amount == 0:
        return 0
    # Count the number of largest elements
    src_len = len(src)
    base_el = src[src_len-amount]
    base_idx = src_len-amount+1
    while base_idx < src_len and base_el == src[base_idx]:
        base_idx += 1

    # Move elements which are not the largest to helper
    num_moves += help_to_help(src, helper, main, src_len-base_idx)
    # Move the largest to main
    num_moves += push_to_main(src, main, base_idx+amount-src_len)
    # Repeat for the rest of the elements now in the other helper
    num_moves += move_to_main(helper, main, src, src_len-base_idx)
    return num_moves

def main():
    num_tasks = 0
    num_moves = 0
    for n in range(1, 7):
        start_moves = num_moves
        start_tasks = num_tasks
        max_move = -1
        max_main = []
        for lst in map(list,product(*[[1,2,3,4,5,6]]*n)):
            num_tasks += 1
            if DEBUG: print lst, [], []
            sort.lst = lst
            cur_lst = lst[:]
            move.history = [(None, None, None)]
            help1 = []
            help2 = []
            moves = sort_better(lst, help1, help2)
            if moves > max_move:
                max_move = moves
                max_main = cur_lst
            num_moves += moves

            if DEBUG: print '%s, %s, %s (moves: %d)' % (cur_lst, [], [], moves)
            if list(reversed(sorted(lst))) != lst:
                print 'NOT SORTED: %s' % lst
                return
            if DEBUG: print

            # Verify that the modified list of moves is still valid
            verify(cur_lst, move.history)
        end_moves = num_moves - start_moves
        end_tasks = num_tasks - start_tasks
        print 'Length %d\nMoves: %d\nTasks: %d\nMax: %d (%s)\nAverage: %.3f\n' % (n, end_moves, end_tasks, max_move, max_main, 1.0*end_moves/end_tasks)
    print '--------------'
    print 'Overall\nMoves: %d\nTasks: %d\nAverage: %.3f' % (num_moves, num_tasks, 1.0*num_moves/num_tasks)

# Old sort method, which assumes we can only see the top of the stack
def sort(main, max_stack, a_stack):
    height = len(main)
    largest = -1
    num_moves = 0
    a_stack_second_el = 10**10
    for i in range(height):
        if len(main)==0:
            break
        el = main[-1]
        if el > largest: # We found a new maximum element
            if i < height-1: # Process only if it is not at the bottom of main stack
                largest = el
                if len(a_stack)>0 and a_stack[-1] < max_stack[-1] < a_stack_second_el:
                    a_stack_second_el = max_stack[-1]
                # Move aux stack to max stack then reverse the role
                num_moves += help_to_help(a_stack, max_stack, main)
                max_stack, a_stack = a_stack, max_stack
                if DEBUG: print 'Moved max_stack to a_stack: %s, %s, %s (moves: %d)' % (main, max_stack, a_stack, num_moves)
                num_moves += move(main, max_stack)
                if DEBUG: print 'Moved el to max_stack: %s, %s, %s (moves: %d)' % (main, max_stack, a_stack, num_moves)
        elif el == largest:
            # The maximum element is the same as in max stack, append
            if i < height-1: # Only if the maximum element is not at the bottom
                num_moves += move(main, max_stack)
        elif len(a_stack)==0 or el <= a_stack[-1]:
            # Current element is the same as in aux stack, append
            if len(a_stack)>0 and el < a_stack[-1]:
                a_stack_second_el = a_stack[-1]
            num_moves += move(main, a_stack)
        elif a_stack[-1] < el <= a_stack_second_el:
            # Current element is larger, but smaller than the next largest element
            # Step 1
            # Move the smallest element(s) in aux stack into max stack
            amount = 0
            while len(a_stack)>0 and a_stack[-1] != a_stack_second_el:
                num_moves += move(a_stack, max_stack)
                amount += 1

            # Step 2
            # Move all elements in main stack that is between the smallest
            # element in aux stack and current element
            while len(main)>0 and max_stack[-1] <= main[-1] <= el:
                if max_stack[-1] < main[-1] < a_stack_second_el:
                    a_stack_second_el = main[-1]
                num_moves += move(main, a_stack)
                el = a_stack[-1]

            # Step 3
            # Put the smallest element(s) back
            for i in range(amount):
                num_moves += move(max_stack, a_stack)
        else: # Find a location in aux stack to put current element
            # Step 1
            # Move all elements into max stack as long as it will still
            # fulfill the Hanoi condition on max stack, AND
            # it should be greater than the smallest element in aux stack
            # So that we won't duplicate work, because in Step 2 we want
            # the main stack to contain the minimum element
            while len(main)>0 and a_stack[-1] < main[-1] <= max_stack[-1]:
                num_moves += move(main, max_stack)

            # Step 2
            # Pick the minimum between max stack and aux stack, move to main
            # This will essentially sort (in reverse) the elements into main
            # Don't move to main the element(s) found before Step 1, because
            # we want to move them to aux stack
            while True:
                if len(a_stack)>0 and a_stack[-1] < max_stack[-1]:
                    num_moves += move(a_stack, main)
                elif max_stack[-1] < el:
                    num_moves += move(max_stack, main)
                else:
                    break

            # Step 3
            # Move all elements in main into aux stack, as long as it
            # satisfies the Hanoi condition on aux stack
            while max_stack[-1] == el:
                num_moves += move(max_stack, a_stack)
            while len(main)>0 and main[-1] <= a_stack[-1]:
                if main[-1] < a_stack[-1] < a_stack_second_el:
                    a_stack_second_el = a_stack[-1]
                num_moves += move(main, a_stack)
        if DEBUG: print main, max_stack, a_stack
    # Now max stack contains largest element(s), aux stack the rest
    num_moves += push_to_main(max_stack, main)
    num_moves += move_to_main(a_stack, main, max_stack)
    return num_moves

if __name__ == '__main__':
    main()
justhalf
źródło
Nie dostaję twojego pytania 4. Co to jest przechowywanie drugiego co do wielkości elementu drugiego pomocnika? Co to znaczy?
Justin
Zasadniczo wystarczy śledzić drugi co do wielkości element w zmiennej. Chyba przesadziłem. Myślę, że jest w porządku, haha
tylko
„Twoja funkcja / podprogram może w dowolnym momencie sprawdzić dowolny stos”. Jeśli więc to, co robisz, można łatwo zrobić, przeglądając stosy i znajdując lokalizację drugiego co do wielkości elementu, jest w porządku.
Justin
1
Czy przez „sprawdzanie dowolnego stosu w dowolnym momencie” mogę odczytać stos jak tablicę, nie zużywając żadnego ruchu? To wszystko zmienia. Jeśli chodzi o wyjaśnienie, wciąż jestem w trakcie aktualizacji algorytmu (mam go jeszcze niżej), więc zaktualizuję, gdy skończę.
pół
1
Widzę. To otwiera zupełnie nowe możliwości i zdecydowanie możemy jeszcze bardziej zmniejszyć liczbę ruchów. Sprawi to, że problem będzie trudniejszy, haha, ponieważ zadanie jest zasadniczo „podane szeregowi liczb całkowitych, znajdź minimalną liczbę ruchów, aby posortować je jako Wieża Hanoi”. Jeśli możemy zobaczyć tylko górę stosu, mój algorytm jest bliski optymalnemu (jeśli nie optymalnemu)
pół
1

Java - 2129090 2083142 porusza się po 55986 tablicach

Link ideone .

Struktura zapewniająca poprawność algorytmu:

private final Deque<Integer> main = new ArrayDeque<Integer>();
private final Deque<Integer> help1 = new ArrayDeque<Integer>();
private final Deque<Integer> help2 = new ArrayDeque<Integer>();

private int taskCount = 0;
private int opCount = 0;

private void sort(List<Integer> input) {
    taskCount++;
    main.addAll(input);
    sortMain();
    verify();
    main.clear();
}

private void verify() {
    if (!help1.isEmpty()) {
        throw new IllegalStateException("non-empty help1\n" + state());
    }
    if (!help2.isEmpty()) {
        throw new IllegalStateException("non-empty help2\n" + state());
    }
    int last = 0;
    for (int i: main) {
        if (last > i) {
            throw new IllegalStateException("unsorted main: " + main);
        }
        last = i;
    }
}

private void done() {
    System.out.println();
    System.out.print(opCount + "/" + taskCount);
}

private void move(Deque<Integer> from, Deque<Integer> to) {
    if (from == to) throw new IllegalArgumentException("moving from/to " + from);
    Integer i = from.pop();
    if (to != main && !to.isEmpty() && i > to.peek()) {
        throw new IllegalStateException(
                from + " " + i + " -> " + to);
    }
    to.push(i);
    opCount++;
}

private String name(Deque<Integer> stack) {
    return stack == help1 ? "help1" :
           stack == help2 ? "help2" :
           "main";
}

private String state() {
    return "main:  " + main + 
            "\nhelp1: " + help1 +
            "\nhelp2: " + help2;
}

Rzeczywisty algorytm:

private void ensureMain(Deque<Integer> stack) {
    if (stack != main) {
        throw new IllegalArgumentException("Expected main, got " + name(stack) + "\n" + state());
    }
}

private void ensureHelp(Deque<Integer> stack) {
    if (stack == main) {
        throw new IllegalArgumentException("Expected help, got main\n" + state());
    }
}

private void ensureHelpers(Deque<Integer> stack1, Deque<Integer> stack2) {
    ensureHelp(stack1);
    ensureHelp(stack2);
}

private void sortMain() {
    int height = main.size();
    int topIndex = height;
    while (topIndex == height && height > 1) {
        topIndex = lastIndexOfLargest(height, main);
        height--;
    }
    if (topIndex == height) { 
        // is already sorted
        return;
    }
    // split stack at largest element
    int part1Count = topIndex;
    int part2Count = height - topIndex;
    // move largest and first part to help 1
    moveFromMain(part1Count+1, help1, help2);
    // merge both parts to help 2, leaving largest on 1
    mergeToHelp(part2Count, main, part1Count, help1, help2);
    // move largest to main
    move(help1, main);
    // move remaining to main
    moveToMain(height, help2, help1);
}

/** Moves elements from main to helper, sorting them */
private void moveFromMain(int amount, Deque<Integer> target, Deque<Integer> help) {
    if (amount < 1) return;
    ensureHelpers(target, help);
    int topIndex = lastIndexOfLargest(amount, main);
    int part1Count = topIndex;
    int part2Count = amount - topIndex - 1;
    // move first part to help
    moveFromMain(part1Count, help, target);
    // move largest to target
    move(main, target);
    // merge both parts to target
    mergeToHelp(part2Count, main, part1Count, help, target);
}

/** Moves elements from helper to main, keeping them sorted */
private void moveToMain(int amount, Deque<Integer> source, Deque<Integer> help) {
    if (amount < 1) return;
    ensureHelpers(source, help);
    moveHelper(amount-1, source, help);
    move(source, main);
    moveToMain(amount-1, help, source);
}

/** Moves elements between helpers */
private void moveHelper(int amount, Deque<Integer> source, Deque<Integer> target) {
    pushToMain(amount, source);
    popFromMain(amount, target);
}

/** Merges top of main and helper to other helper */
private void mergeToHelp(int mainAmount, Deque<Integer> main, int helpAmount, Deque<Integer> help, Deque<Integer> target) {
    ensureMain(main);
    ensureHelpers(help, target);
    if (mainAmount < 0) mainAmount = 0;
    if (helpAmount < 0) helpAmount = 0;
    while (mainAmount > 0 || helpAmount > 0) {
        // while there is something to merge
        int largestMain = valueOfLargest(mainAmount, main);
        int largestHelp = valueOfLargest(helpAmount, help);
        if (largestMain > largestHelp) {
            // largest is in main
            int index = firstIndexOfLargest(mainAmount, main);
            if (index > 0) {
                // move excess to help:
                int mainTop = index;
                int helpTop = elementsSmallerThan(help, largestMain);
                if (helpTop > 0) {
                    // 1. move top of help to target
                    moveHelper(helpTop, help, target);
                    // 2. merge old top with excess from main
                    mergeToHelp(mainTop, main, helpTop, target, help);
                } else {
                    moveFromMain(mainTop, help, target);
                }
                mainAmount -= mainTop;
                helpAmount += mainTop;
            }
            move(main, target);
            mainAmount--;
        } else {
            // largest is at bottom of help
            int helpTop = helpAmount - 1; // largest is at bottom
            // move top to main
            pushToMain(helpTop, help);
            mainAmount += helpTop;
            // move largest to target
            move(help, target);
            helpAmount = 0;
        }
    }
}

private void pushToMain(int amount, Deque<Integer> from) {
    for (; amount > 0; amount--) move(from, main);
}

private void popFromMain(int amount, Deque<Integer> to) {
    for (; amount > 0; amount--) move(main, to);
}

private int firstIndexOfLargest(int height, Deque<Integer> stack) {
    if (height == 0) throw new IllegalArgumentException("height == 0");
    int topValue = 0;
    int topIndex = 0;
    int i = 0;
    for (Integer e: stack) {
        if (e > topValue) {
            topValue = e;
            topIndex = i;
        }
        if (++i == height) break;
    }
    return topIndex;
}

private int lastIndexOfLargest(int height, Deque<Integer> stack) {
    if (height == 0) throw new IllegalArgumentException("height == 0");
    int topValue = 0;
    int topIndex = 0;
    int i = 0;
    for (Integer e: stack) {
        if (e >= topValue) {
            topValue = e;
            topIndex = i;
        }
        if (++i == height) break;
    }
    return topIndex;
}

private int valueOfLargest(int height, Deque<Integer> stack) {
    int v = Integer.MIN_VALUE;
    for (Integer e: stack) {
        if (height-- == 0) break;
        if (e > v) v = e;
    }
    return v;
}

private int elementsSmallerThan(Deque<Integer> stack, int value) {
    int i = 0;
    for (Integer e: stack) {
        if (e >= value) return i;
        i++;
    }
    return i;
}

Testcase:

public static void main(String[] args) throws Exception {
    HanoiSort hanoi = new HanoiSort();
    int N = 6;
    for (int len = 1; len <= N; len++) {
        Integer[] input = new Integer[len];
        List<Integer> inputList = Arrays.asList(input);
        int max = N;
        for (int i = 1; i < len; i++) max *= N;
        for (int run = 0; run < max; run++) {
            int n = run;
            for (int i = 0; i < len; n /= N, i++) {
                input[i] = (n % N)+1;
            }
            try {
                hanoi.sort(inputList);
            } catch (Exception e) {
                System.out.println(inputList);
                e.printStackTrace(System.out);
                return;
            }
        }
    }
    hanoi.done();
}
Głowonóg
źródło
-1

C / C ++ Nie mierzyłem ruchów (kołki: p1, p2, p3) Nie wiem, jak dodać kod C ++, który używa STL (problem z formatowaniem). Utrata części kodu z powodu formatów tagów HTML w kodzie.

  1. Uzyskaj n: liczbę najlepiej posortowanych elementów w p1
  2. Wykonaj ruch hanoi (n) do p3 za pomocą p2 (zachowuje posortowaną właściwość)
  3. Przenieś kolejne elementy (przynajmniej 1) w odwrotnej kolejności od p1 do p2.
  4. Scal dane przenoszenia (n + m) w p2 i p3 do p1.

         4.1 merge sort p2 & P3 (reverse) to p1
         4.2 move (n+m) to p3
         4.3 hanoi p3 to p1 using p2
    
  5. Wywołaj hanoi sortuj rekurencyjnie, co teraz posortuje co najmniej n + m + 1 elementów.
  6. Zatrzymaj, gdy n = rozmiar p1.
#zawierać 
#zawierać 
using namespace std;
/ ************************************************* ***************************** 
   Pokaż wektor (musiał przypadkiem wykrztusić
************************************************** ***************************** /    

void show (wektor p, ciąg msg)
{
   wektor :: iterator it;
   printf ("% s \ n", msg.c_str ());
   for (it = p.begin (); it & fr, vector & inter, vector & to, int n) {
   int d1;
   jeśli (n & p1, wektor i p2, wektor i p3) {
   int d3, d2, d1;
   liczba całkowita, n;
   bool d2_added;

   d2 = p2.back (); p2.pop_back ();
   // dane będą malejące w p1
   d2_added = false;
   while (p3.size ()> 0) {
      d3 = p3.back (); p3.pop_back ();
      if (d2_added == false && d2 0) {
      d1 = p1.back (); p1.pop_back ();
      p3.push_back (d1);
      --liczyć;
   }
   // Wróć z hanoi z p3 na p1
   // użyj p2 jako inter
   hanoi (p3, p2, p1, n);
}
/ ************************************************* ******************************
   Pobiera liczbę pierwszych posortowanych elementów
************************************************** ***************************** /    
int get_top_sorted_count (vector & p1) {
   wektor :: iterator it;
   int prev = 0;
   int n = 1;

   for (it = --p1.end (); it> = p1.begin (); --it) {
     if (it == --p1.end ()) {
    prev = * it;
        dalej;
     }
     if (* it i p1, wektor i p2, wektor i p3) {
    int n, d1;
    n = get_top_sorted_count (p1);
    if (n == p1.size ()) {
       powrót;
    }
    hanoi (p1, p2, p3, n);
    p2.push_back (p1.back ());
    p1.pop_back ();
    merge_move (p1, p2, p3);
    hanoi_sort (p1, p2, p3);
}
/ ************************************************* ******************************    
    Trochę baed na hanoi
************************************************** ***************************** /    
int main (void)
{
  wektor p1, p2, p3;
  p1.push_back (5);
  p1.push_back (4);
  p1.push_back (7);
  p1.push_back (3);
  p1.push_back (8);
  p1.push_back (2);
  show (p1, „... Bef Sort ...”);
  hanoi_sort (p1, p2, p3);
  show (p1, „... Aft Sort ...”);
}
Joji
źródło
Czy możesz napisać do tego kod? W przeciwnym razie nie jest to odpowiedź.
Justin
Nie widzę hanoi(...)funkcji. Ponadto masz 2 #includes, które się nie kompilują. Proszę zamieścić pełny kod.
Hosch250