Torre di Hanoi Ordinamento


21

Scrivi una funzione / subroutine per ordinare un elenco di numeri interi, Torre di Hanoi stile .

Ti verrà data una pila di numeri interi. Questo è lo stack principale.

Ti vengono anche dati altri due stack di aiuto. Queste pile di helper hanno comunque una proprietà unica: ogni elemento deve essere più piccolo o uguale alle dimensioni dell'elemento sottostante. Lo stack principale non ha questa limitazione.

Hai il compito di ordinare lo stack principale in posizione, mettendo sotto i numeri interi più grandi. La tua funzione / subroutine restituirà (o equivalente) il numero di mosse effettuate nell'ordinamento della pila.
Nota: è necessario ordinare lo stack principale in posizione , non ordinare su un altro stack e chiamare quella risposta. Tuttavia, se per qualche motivo non puoi farlo, puoi simulare le pile mutabili, ma ricorda che questo è un tipo di Torre di Hanoi; ci sono solo 3 pioli e solo 1 piolo può essere non ordinato.

La tua funzione / subroutine può ispezionare qualsiasi stack in qualsiasi momento, ma può fare una mossa solo saltando e spingendo. Una singola mossa è un pop da una pila che viene spinta in un'altra.

Metti alla prova la tua funzione / subroutine per ogni permutazione dei primi 6 numeri naturali. In altre parole, prova la tua funzione / subroutine per {1},{2},...,{6},{1,1},{1,2},...,{1,6},{2,1},...(questo dovrebbe essere un totale di o possibilità (grazie a Howard per aver corretto la mia matematica)). La funzione / subroutine che sposta gli elementi il ​​minor numero di volte vince.61+62+...+6655986


@JanDvorak Quella era una specie di idea sui test. Se il programmatore deve eseguire la funzione 46656 volte, perché dovrebbe voler aspettare così a lungo per l'output? O c'è un altro buon modo per limitare questo genere di cose?
Justin

In qualche modo mi piace la sfida del blind-zap: puoi solo dire "sposta dallo stack x allo stack y" e vedi se la tua mossa ha successo, e se lo fa, ti viene addebitato; i punti bonus indicano che il fallimento della mossa è indicato lanciando un'eccezione / ritornando correttamente.
John Dvorak,

3
L'elenco di "permutazioni" che hai fornito contiene 6**1+6**2+...+6**6=55986elementi.
Howard,

1
@ m.buettner La distinzione è che questi sono gli elementi del prodotto cartesiano da 1 a 6 volte . Probabilmente chiamerei questo "l'insieme delle permutazioni di ciascun elemento dell'insieme di potenze dei primi 6 numeri naturali tranne l'insieme nullo".
Giustino,

1
@Quincunx ad eccezione del gruppo di alimentazione non contiene set con numeri ripetuti. ;) ... ma non penso che dovremmo prenderlo troppo sul serio comunque, purché siamo tutti chiari sugli elementi del set.
Martin Ender,

Risposte:


4

Java - soluzione ottimale (mosse 1080544)

Questa soluzione crea un albero percorso più breve dalla destinazione e viceversa, quindi attraversa il percorso dallo stato iniziale alla destinazione. Un sacco di spazio per il miglioramento della velocità, ma completa ancora tutti i 55986 problemi in circa un minuto.

Supponendo che l'algoritmo sia implementato correttamente, questa dovrebbe essere la soluzione teoricamente migliore.

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;
    }

}

Vuoi spiegare di più su cosa intendi per "albero del sentiero più breve"?
solo il

Comunque grazie per la tua risposta, mi fa piacere che posso raggiungere una soluzione quasi ottimale usando solo l'euristica :)
solo il

Un albero percorso più breve è un albero in cui ogni nodo / stato ha un bordo "successivo", che porta al nodo / stato che, a sua volta, ha la distanza più breve rispetto allo stato radice / destinazione (= pila principale ordinata). Potrebbero esserci altri nodi candidati successivi che hanno la stessa distanza o più, ma nessuno che è più vicino alla radice. Per questo problema, tutti i bordi dell'albero del percorso più breve hanno una distanza di uno, poiché sono necessari uno spostamento per passare da uno stato a un altro. Fondamentalmente, un albero percorso completo più breve contiene la soluzione per tutti gli stati che hanno lo stesso stato target.
MrBackend,

@justhalf Hai dimenticato di menzionarti nel mio commento. A proposito, vedi anche en.wikipedia.org/wiki/Retrograde_analysis
MrBackend,

5

C - 2547172 per 55986 ingressi

C'è molto margine di miglioramento qui. Per la mia sanità mentale, l'ho semplificato in modo che fosse possibile ispezionare solo l'elemento superiore di ogni pila. L'eliminazione di questa limitazione autoimposta consentirebbe ottimizzazioni come la determinazione anticipata dell'ordine finale e il tentativo di ridurre al minimo il numero di mosse necessarie per raggiungerlo. Un esempio convincente è che la mia implementazione presenta comportamenti peggiori se lo stack principale è già ordinato.

Algoritmo:

  1. Riempi entrambe le pile ausiliarie (spazio per l'ottimizzazione qui, possibilmente assegnando a quale pila in base a un qualche tipo di perno).
  2. Unisci riordinare le pile ausiliarie sulla pila principale.
  3. Ripeti 1-2 fino a quando lo stack principale non viene ordinato (ma al contrario).
  4. Invertire la pila principale (più spazio per l'ottimizzazione, mescolando ripetutamente molti degli stessi elementi).

Analisi:

  • Ulteriore complessità dello spazio è O (n) (per le due pile ausiliarie), il che è buono, poiché era un requisito del problema.
  • La complessità temporale è O (n ^ 2) secondo il mio conteggio. Le correzioni sono benvenute.

#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;
}

4

Python, 3983838 3912258 si sposta su 55986 input

Questo è molto inefficiente.

Aggiungerò il numero totale di mosse dopo che il PO ha chiarito se sono per tutti questi casi o per altri casi specifici.


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

Spiegazione

Cosa, i commenti non sono abbastanza buoni per te?


Nota per OP: Grazie per non aver creato questo code-golf.


Credo che se esiste un modo più interessante di affrontare la sfida oltre a [code-golf], dovrebbe essere fatto in questo modo.
Justin

Ok, questo fallisce per [1,1,2]. In Python, considerando un elenco [2,1,1]c'è un modo per ottenere [2,1,1].index(1)come 2 cioè a partire dalla fascia più alta?
user80551

@Quincunx No, [2,1,1,3,5].index(1)==2invece di1
user80551

Ehm, list.index(data)restituisce l'indice dell'elemento datain list. Io non conosco l'indice dataie1
user80551

L'hack sarebbelen(list)-(list[::-1].index(1))
user80551

2

Python: 1.688.293 1.579.182 1.524.054 1.450.842 1.093.195 mosse

Il metodo principale main_to_help_bestè quello di spostare alcuni elementi selezionati dallo stack principale allo stack di supporto. Ha una bandiera everythingche definisce se vogliamo che tutto si sposti nello specifico destination, o se vogliamo mantenere solo il più grande destinationmentre il resto nell'altro aiutante.

Supponendo che stiamo passando dstall'utilizzo dell'helper helper, la funzione può essere approssimativamente descritta come segue:

  1. Trova le posizioni degli elementi più grandi
  2. Sposta tutto in cima all'elemento più grande in helpermodo ricorsivo
  3. Sposta l'elemento più grande in dst
  4. Spingere indietro da helper principale
  5. Ripeti 2-4 fino a quando non si trovano gli elementi più grandi dst
  6. un. Se everythingimpostato, sposta ricorsivamente gli elementi in main in dst
    b. Altrimenti, sposta ricorsivamente gli elementi in main ahelper

L'algoritmo di ordinamento principale ( sort2nel mio codice) chiamerà quindi main_to_help_bestcon everythingset suFalse , quindi sposta l'elemento più grande su main, quindi sposta tutto dall'helper su main, mantenendolo ordinato.

Altre spiegazioni integrate come commenti nel codice.

Fondamentalmente i principi che ho usato sono:

  1. Mantieni un aiutante per contenere gli elementi massimi
  2. Mantieni un altro aiutante per contenere altri elementi
  3. Non fare mosse non necessarie il più possibile

Il principio 3 viene implementato non contando lo spostamento se l'origine è la destinazione precedente (ovvero, abbiamo appena spostato main in help1, quindi vogliamo passare da help1 in help2) e, inoltre, ridurre il numero di movimenti di 1 se lo stanno riportando nella posizione originale (ovvero main to help1 quindi help1 su main). Inoltre, se le nmosse precedenti muovono tutte lo stesso numero intero, possiamo effettivamente riordinarle n. Quindi ne approfittiamo anche per ridurre ulteriormente il numero di mosse.

Questo è valido poiché conosciamo tutti gli elementi nello stack principale, quindi questo può essere interpretato nel vedere in futuro che stiamo per spostare indietro l'elemento, non dovremmo fare questa mossa.

Esecuzione del campione (le pile sono visualizzate dal basso verso l'alto, quindi il primo elemento è il fondo):

Lunghezza 1
Mosse: 0
Compiti: 6
Max: 0 ([1])
Media: 0.000

Lunghezza 2
Mosse: 60
Compiti: 36
Max: 4 ([1, 2])
Media: 1.667

Lunghezza 3
Mosse: 1030
Compiti: 216
Max: 9 ([2, 3, 1])
Media: 4.769

Lunghezza 4
Mosse: 11765
Compiti: 1296
Max: 19 ([3, 4, 2, 1])
Media: 9.078

Lunghezza 5
Mosse: 112325
Compiti: 7776
Max: 33 ([4, 5, 3, 2, 1])
Media: 14.445

Lunghezza 6
Mosse: 968015
Compiti: 46656
Max: 51 ([5, 6, 4, 3, 2, 1])
Media: 20.748

--------------
Complessivamente
Mosse: 1093195
Compiti: 55986
Media: 19.526

Possiamo vedere che il caso peggiore è quando l'elemento più grande è posizionato sul secondo fondo, mentre il resto è ordinato. Dal caso peggiore possiamo vedere che l'algoritmo è O (n ^ 2).

Il numero di mosse è ovviamente minimo per n=1e n=2come possiamo vedere dal risultato, e credo che questo sia anche minimo per valori più grandi di n, anche se non posso provarlo.

Altre spiegazioni sono nel codice.

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()

Non capisco la tua domanda 4. Qual è la memorizzazione del secondo elemento più grande sul secondo helper? Cosa significa?
Giustino,

Fondamentalmente basta tenere traccia del secondo elemento più grande in una variabile. Immagino che ci stavo pensando troppo. Penso che
vada

"La tua funzione / subroutine può ispezionare qualsiasi stack in qualsiasi momento". Quindi, se quello che stai facendo può essere facilmente eseguito scorrendo le pile e trovando la posizione del secondo elemento più grande, va bene.
Giustino,

1
"Ispezionare qualsiasi stack in qualsiasi momento" significa che posso leggere lo stack come un array senza consumare alcuna mossa? Questo cambia tutto. Per quanto riguarda la spiegazione, sto ancora aggiornando l'algoritmo (l'ho ancora più in basso), quindi aggiornerò una volta terminato.
solo

1
Vedo. Ciò apre nuove possibilità e sicuramente possiamo ridurre ulteriormente il numero di mosse. Renderà il problema più difficile, ahah, dato che il compito è essenzialmente "dato un array di numeri interi, trova il numero minimo di mosse per ordinarlo alla Torre di Hanoi". Se ci è permesso vedere solo la parte superiore dello stack, allora il mio algoritmo è vicino all'ottimale (se non all'ottimale)
solo

1

Java - 2129090 2083142 si sposta su 55986 array

Il collegamento ideone .

Il framework per garantire che l'algoritmo sia corretto:

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;
}

L'attuale algoritmo:

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;
}

Il 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();
}

-1

C / C ++ Non ha misurato le mosse (pegg: p1, p2, p3) Non so come aggiungere il codice C ++ che utilizza STL (formating issue). Perdere parti di codice a causa dei formati di tag html nel codice.

  1. Ottieni n: conteggio degli elementi più ordinati in p1
  2. Esegui uno spostamento di hanoi (p) su p3 usando p2 (mantiene la proprietà ordinata)
  3. Spostare i memem successivi (almeno 1) in ordine inverso da p1 a p2.
  4. Unisci i dati di spostamento (n + m) in p2 e p3 a 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. Chiama ricorsi in modo ricorsivo hanoi che ordinerà ora almeno n + m + 1 elementi.
  6. Arresta quando n = dimensione di p1.
#includere 
#includere 
usando lo spazio dei nomi std;
/ ************************************************* ***************************** 
   Mostra il vettore (dovuto al caso cout
************************************************** ***************************** /    

void show (vettore p, stringa msg)
{
   vettore :: iteratore esso;
   printf ("% s \ n", msg.c_str ());
   for (it = p.begin (); it & fr, vector & inter, vector & to, int n) {
   int d1;
   if (n & p1, vector & p2, vector & p3) {
   int d3, d2, d1;
   int count, n;
   bool d2_added;

   d2 = p2.back (); p2.pop_back ();
   // i dati scenderanno in 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);
      --contare;
   }
   // Torna indietro con hanoi da p3 a p1
   // usa p2 come inter
   hanoi (p3, p2, p1, n);
}
/ ************************************************* ******************************
   Ottiene il conteggio dei primi elementi ordinati
************************************************** ***************************** /    
int get_top_sorted_count (vector & p1) {
   vettore :: iteratore esso;
   int prev = 0;
   int n = 1;

   for (it = --p1.end (); it> = p1.begin (); --it) {
     if (it == --p1.end ()) {
    prev = * esso;
        Continua;
     }
     if (* it & p1, vector & p2, vector & p3) {
    int n, d1;
    n = get_top_sorted_count (p1);
    if (n == p1.size ()) {
       ritorno;
    }
    hanoi (p1, p2, p3, n);
    p2.push_back (p1.back ());
    p1.pop_back ();
    merge_move (p1, p2, p3);
    hanoi_sort (p1, p2, p3);
}
/ ************************************************* ******************************    
    Sorta su hanoi
************************************************** ***************************** /    
int main (vuoto)
{
  vettore 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);
  mostra (p1, "... Bef Sort ...");
  hanoi_sort (p1, p2, p3);
  mostra (p1, "... Aft Sort ...");
}

Puoi scrivere il codice per questo? Altrimenti, questa non è una risposta.
Giustino

Non vedo la hanoi(...)funzione. Inoltre, hai 2 #includesecondi che non vengono compilati. Si prega di inserire il codice completo.
Hosch250,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.