Torre de Hanoi Sort

21

Escriba una función / subrutina para ordenar una lista de enteros, estilo Torre de Hanoi .

Se le dará una pila de enteros. Esta es la pila principal.

También te dan dos pilas de ayuda más. Sin embargo, estas pilas auxiliares tienen una propiedad única: cada elemento debe ser más pequeño o del mismo tamaño que el elemento debajo de él. La pila principal no tiene esa restricción.

Tiene la tarea de ordenar la pila principal en su lugar, colocando los enteros más grandes debajo. Su función / subrutina devolverá (o equivalente) el número de movimientos que realizó al ordenar la pila.
Nota: debe ordenar la pila principal en su lugar , sin ordenar en otra pila y llamar a eso la respuesta. Sin embargo, si por alguna razón no puede hacerlo, puede simular las pilas mutables, pero recuerde que esto es del tipo Torre de Hanoi; solo hay 3 clavijas y solo 1 clavija puede estar desordenada.

Su función / subrutina puede inspeccionar cualquier pila en cualquier momento, pero solo puede hacer un movimiento haciendo estallar y empujando. Un solo movimiento es un pop de una pila que se empuja a otra.

Pruebe su función / subrutina para cada permutación de los primeros 6 números naturales. En otras palabras, poner a prueba su función / subrutina para {1},{2},...,{6},{1,1},{1,2},...,{1,6},{2,1},...(este debe ser un total de o posibilidades (gracias a Howard para corregir mis matemáticas)). La función / subrutina que mueve elementos la menor cantidad de veces gana.61+62+...+6655986

Justin
fuente
@ JanDvorak Esa fue la idea de las pruebas. Si el programador necesita ejecutar la función 46656 veces, ¿por qué querría esperar tanto la salida? ¿O hay otra buena manera de restringir este tipo de cosas?
Justin
De alguna manera me gusta el desafío de zap ciego: solo puedes decir "muévete de la pila x a la pila y" y mira si tu movimiento tiene éxito, y si lo hace, te cobran por ello; los puntos de bonificación es que la falla de movimiento se indica lanzando una excepción / regresando correctamente.
John Dvorak
3
La lista de "permutaciones" que proporcionó contiene 6**1+6**2+...+6**6=55986elementos.
Howard
1
@ m.buettner La distinción es que estos son los elementos del producto cartesiano s de 1 a 6 veces . Probablemente llamaría a esto "el conjunto de permutaciones de cada elemento del conjunto de potencia de los primeros 6 números naturales, excepto el conjunto nulo".
Justin
1
@Quincunx, excepto que el conjunto de potencia no contiene conjuntos con números repetidos. ;) ... pero no creo que debamos tomar esto demasiado en serio de todos modos, siempre y cuando todos tengamos claros los elementos del conjunto.
Martin Ender

Respuestas:

4

Java: solución óptima (movimientos 1080544)

Esta solución construye un árbol de ruta más corta desde el objetivo y hacia atrás, luego atraviesa la ruta desde el estado inicial hasta el objetivo. Mucho espacio para mejorar la velocidad, pero aún así completa todos los problemas de 55986 en aproximadamente un minuto.

Suponiendo que el algoritmo se implemente correctamente, esta debería ser la mejor solución teórica.

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
fuente
¿Te gustaría explicar más sobre lo que quieres decir con "árbol de camino más corto"?
justhalf
De todos modos, gracias por su respuesta, me alegra poder lograr una solución casi óptima usando solo heurística :)
solo el
Un árbol de ruta más corta es un árbol en el que cada nodo / estado tiene un borde "siguiente", que conduce al nodo / estado que, a su vez, tiene la distancia más corta al estado raíz / objetivo (= pila principal ordenada). Puede haber otros nodos próximos candidatos que tengan la misma distancia o más, pero ninguno que esté más cerca de la raíz. Para este problema, todos los bordes en el árbol de ruta más corta tienen una distancia de uno, ya que se necesita un movimiento para pasar de un estado a otro. Básicamente, un árbol de ruta más corta completo contiene la solución para todos los estados que tienen el mismo estado de destino.
MrBackend
@justhalf Olvidé mencionarlo en mi comentario. Por cierto, ver también en.wikipedia.org/wiki/Retrograde_analysis
MrBackend
5

C - 2547172 para entradas 55986

Aquí hay mucho margen de mejora. Por mi propia cordura, simplifiqué esto para que solo fuera posible inspeccionar el elemento superior de cada pila. Levantar esta restricción autoimpuesta permitiría optimizaciones como determinar el orden final por adelantado y tratar de minimizar el número de movimientos necesarios para lograrlo. Un ejemplo convincente es que mi implementación tiene el peor comportamiento si la pila principal ya está ordenada.

Algoritmo:

  1. Llene ambas pilas auxiliares (espacio para la optimización aquí, posiblemente asignando a qué pila basado en algún tipo de pivote).
  2. Combinar ordenar las pilas auxiliares de nuevo en la pila principal.
  3. Repita 1-2 hasta que la pila principal esté ordenada (pero a la inversa).
  4. Invierta la pila principal (más espacio para la optimización, barajando muchos de los mismos elementos repetidamente).

Análisis:

  • La complejidad de espacio adicional es O (n) (para las dos pilas auxiliares), lo cual es bueno, ya que ese era un requisito del problema.
  • La complejidad del tiempo es O (n ^ 2) según mi cuenta. Las correcciones son bienvenidas.

#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
fuente
4

Python, 3983838 3912258 mueve más de 55986 entradas

Esto es muy ineficiente.

Agregaré el número total de movimientos después de que el OP aclare si son para todos esos casos o para otros casos específicos.


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

Explicación

¿Qué, los comentarios no son lo suficientemente buenos para ti?


Nota para OP: Gracias por no hacer este código de golf.

usuario80551
fuente
Creo que si hay una forma más interesante de hacer el desafío que no sea [code-golf], debería hacerse de esa manera.
Justin
Ok, esto falla para [1,1,2]. En python, considerando una lista, [2,1,1]¿hay alguna manera de obtener [2,1,1].index(1)2, es decir, comenzar desde el extremo superior?
user80551
@Quincunx No, en [2,1,1,3,5].index(1)==2lugar de1
user80551
Er, list.index(data)devuelve el índice del artículo dataen list. Yo no sé el índice de datadecir1
user80551
El truco seríalen(list)-(list[::-1].index(1))
user80551
2

Python: 1,688,293 1,579,182 1,524,054 1,450,842 1,093,195 movimientos

El método principal es main_to_help_best, que consiste en mover algunos elementos seleccionados de la pila principal a la pila auxiliar. Tiene una bandera everythingque define si queremos que mueva todo dentro de lo especificado destination, o si queremos mantener solo el más grande endestination mientras que el resto en el otro ayudante.

Suponiendo que nos estamos moviendo para dstusar helper helper, la función se puede describir de la siguiente manera:

  1. Encuentra las posiciones de los elementos más grandes
  2. Mueve todo encima del elemento superior más grande a helper recursiva
  3. Mueve el elemento más grande a dst
  4. Empuje hacia atrás de helpera principal
  5. Repita 2-4 hasta que los elementos más grandes estén en dst
  6. a. Si everythingestá configurado, mueva recursivamente elementos en main a dst
    b. De lo contrario, mueva recursivamente elementos en main ahelper

El algoritmo de clasificación principal ( sort2en mi código) luego llamará main_to_help_bestcon everythingset toFalse , y luego moverá el elemento más grande de nuevo a main, luego moverá todo desde el asistente de regreso a main, manteniéndolo ordenado.

Más explicaciones incrustadas como comentarios en el código.

Básicamente los principios que utilicé son:

  1. Mantenga un ayudante para contener el (los) elemento (s) máximo (s)
  2. Mantenga otro ayudante para contener cualquier otro elemento.
  3. No hagas movimientos innecesarios tanto como sea posible

El Principio 3 se implementa al no contar el movimiento si la fuente es el destino anterior (es decir, simplemente cambiamos de principal a help1, luego queremos pasar de help1 a help2) y, además, reducimos el número de movimientos en 1 si lo están moviendo de nuevo a la posición original (es decir, principal para ayudar1 luego ayuda1 a principal). Además, si los nmovimientos anteriores se mueven todos con el mismo número entero, podemos reordenar esos nmovimientos. Entonces también aprovechamos eso para reducir aún más el número de movimientos.

Esto es válido ya que conocemos todos los elementos en la pila principal, por lo que esto puede interpretarse como si veamos en el futuro que vamos a mover el elemento hacia atrás, no debemos hacer este movimiento.

Ejecución de muestra (las pilas se muestran de abajo hacia arriba, por lo que el primer elemento es la parte inferior):

Longitud 1
Movimientos: 0
Tareas: 6
Máx .: 0 ([1])
Promedio: 0.000

Longitud 2
Movimientos: 60
Tareas: 36
Máx .: 4 ([1, 2])
Promedio: 1.667

Longitud 3
Movimientos: 1030
Tareas: 216
Máx .: 9 ([2, 3, 1])
Promedio: 4.769

Longitud 4
Movimientos: 11765
Tareas: 1296
Máx .: 19 ([3, 4, 2, 1])
Promedio: 9.078

Longitud 5
Movimientos: 112325
Tareas: 7776
Máx .: 33 ([4, 5, 3, 2, 1])
Promedio: 14.445

Longitud 6
Movimientos: 968015
Tareas: 46656
Máx .: 51 ([5, 6, 4, 3, 2, 1])
Promedio: 20.748

--------------
En general
Movimientos: 1093195
Tareas: 55986
Promedio: 19.526

Podemos ver que el peor de los casos es cuando el elemento más grande se coloca en el segundo fondo, mientras que el resto se clasifica. Del peor de los casos podemos ver que el algoritmo es O (n ^ 2).

El número de movimientos es obviamente mínimo n=1y, n=2como podemos ver en el resultado, y creo que esto también es mínimo para valores más grandes de n, aunque no puedo probarlo.

Más explicaciones están en el código.

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()
justo
fuente
No entiendo tu pregunta 4. ¿Qué es esto almacenar el segundo elemento más grande en el segundo ayudante? Qué significa eso?
Justin
Básicamente solo realiza un seguimiento del segundo elemento más grande en una variable. Supongo que lo estaba pensando demasiado. Creo que está perfectamente bien, jaja
solo
"Su función / subrutina puede inspeccionar cualquier pila en cualquier momento". Entonces, si lo que está haciendo se puede hacer fácilmente al recorrer las pilas y encontrar la ubicación del segundo elemento más grande, está bien.
Justin
1
¿Por "inspeccionar cualquier pila en cualquier momento" significa que puedo leer la pila como una matriz sin consumir ningún movimiento? Eso lo cambia todo. Con respecto a la explicación, todavía estoy en el proceso de actualizar el algoritmo (lo obtuve aún más bajo), así que lo actualizaré una vez que haya terminado.
justo
1
Veo. Eso abre nuevas posibilidades y definitivamente podemos reducir aún más el número de movimientos. Sin embargo, hará que el problema sea más difícil, jaja, ya que la tarea es esencialmente "dada una serie de enteros, encontrar el número mínimo de movimientos para ordenarla a la Torre de Hanoi". Si solo se nos permite ver la parte superior de la pila, entonces mi algoritmo está cerca del óptimo (si no es el óptimo)
solo
1

Java - 2129090 2083142 se mueve en matrices 55986

El enlace ideone .

El marco para garantizar que el algoritmo sea correcto:

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

El algoritmo real:

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

El caso de prueba:

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();
}
Cefalópodo
fuente
-1

C / C ++ no midió movimientos (clavijas: p1, p2, p3) No sé cómo agregar código C ++ que usa STL (problema de formateo). Perder partes del código debido a los formatos de etiqueta html en el código.

  1. Obtener n: recuento de los principales elementos ordenados en p1
  2. Haz un movimiento hanoi de ellos (n) a p3 usando p2 (mantiene la propiedad ordenada)
  3. Mueva los siguientes elementos (al menos 1) en orden inverso de p1 a p2.
  4. Combinar datos de movimiento (n + m) en p2 y 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. Llame a hanoi sort de forma recursiva, que clasificará al menos n + m + 1 elementos ahora.
  6. Pare cuando n = tamaño de p1.
#incluir 
#incluir 
usando el espacio de nombres estándar;
/ ************************************************* ***************************** 
   Mostrar el vector (tuvo que arriesgarse
************************************************** ***************************** /    

show nulo (vector p, cadena de mensajes)
{
   vector :: iterador;
   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 cuenta, n;
   bool d2_added;

   d2 = p2.back (); p2.pop_back ();
   // los datos descenderán en 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);
      --contar;
   }
   // Retrocede con hanoi de p3 a p1
   // usa p2 como inter
   hanoi (p3, p2, p1, n);
}
/ ************************************************* ******************************
   Obtiene el recuento de los primeros elementos ordenados
************************************************** ***************************** /    
int get_top_sorted_count (vector & p1) {
   vector :: iterador;
   int anterior = 0;
   int n = 1;

   for (it = --p1.end (); it> = p1.begin (); --it) {
     if (it == --p1.end ()) {
    prev = * it;
        Seguir;
     }
     if (* it & p1, vector & p2, vector & p3) {
    int n, d1;
    n = get_top_sorted_count (p1);
    if (n == p1.size ()) {
       regreso;
    }
    hanoi (p1, p2, p3, n);
    p2.push_back (p1.back ());
    p1.pop_back ();
    merge_move (p1, p2, p3);
    hanoi_sort (p1, p2, p3);
}
/ ************************************************* ******************************    
    Clases descubiertas en Hanoi
************************************************** ***************************** /    
int main (nulo)
{
  vector 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, "... Clasificación en popa ...");
}
Joji
fuente
¿Puedes escribir el código para esto? De lo contrario, esto no es una respuesta.
Justin
No veo la hanoi(...)función Además, tienes 2 #includes que no se compilan. Por favor, publique el código completo.
Hosch250