Torre de Hanói Sort


21

Escreva uma função / sub-rotina para classificar uma lista de números inteiros, estilo Tower of Hanoi .

Você receberá uma pilha de números inteiros. Esta é a pilha principal.

Você também recebe mais duas pilhas auxiliares. Porém, essas pilhas auxiliares têm uma propriedade exclusiva: cada elemento deve ser menor ou do mesmo tamanho que o elemento abaixo dele. A pilha principal não tem essa restrição.

Você tem a tarefa de classificar a pilha principal no lugar, colocando os maiores números inteiros por baixo. Sua função / sub-rotina retornará (ou equivalente) o número de movimentos realizados na classificação da pilha.
Nota: você deve classificar a pilha principal no lugar , sem classificar em outra pilha e chamar isso de resposta. No entanto, se por algum motivo não puder fazê-lo, você pode simular as pilhas mutáveis, mas lembre-se de que esse é o tipo da Torre de Hanói; existem apenas 3 pinos e apenas 1 pode ser desordenado.

Sua função / sub-rotina pode inspecionar qualquer pilha a qualquer momento, mas só pode fazer um movimento ao estourar e empurrar. Um único movimento é um pop de uma pilha que é empurrada para outra.

Teste sua função / sub-rotina para cada permutação dos 6 primeiros números naturais. Em outras palavras, teste sua função / sub-rotina {1},{2},...,{6},{1,1},{1,2},...,{1,6},{2,1},...(deve ser um total de ou possibilidades (obrigado a Howard por corrigir minha matemática)). A função / sub-rotina que move elementos pelo menor número de vezes ganha.61+62+...+6655986


@JanDvorak Essa foi a idéia dos testes. Se o programador precisar executar a função 46656 vezes, por que ele / ela esperaria tanto tempo pela saída? Ou existe outra boa maneira de restringir esse tipo de coisa?
— Justin

De alguma forma, eu gosto do desafio do blind-zap: você só pode dizer "mover da pilha x para a pilha y" e ver se sua jogada é bem-sucedida e, se ocorrer, você será cobrado por isso; pontos de bônus é falha no movimento é indicado lançando uma exceção / retornando corretamente.
— John Dvorak

3
A lista de "permutações" que você forneceu contém 6**1+6**2+...+6**6=55986elementos.
— Howard

11
@ m.buettner A distinção é que esses são os elementos do produto cartesiano s 1 a 6 vezes . Eu provavelmente chamaria isso de "o conjunto de permutações de cada elemento do conjunto de potências dos 6 primeiros números naturais, exceto o conjunto nulo".
— Justin

11
@Quincunx, exceto que o conjunto de alimentação não contém conjuntos com números repetidos. ;) ... mas não acho que devamos levar isso muito a sério, desde que seja claro sobre os elementos do conjunto.
— Martin Ender

Respostas:


4

Java - solução ideal (1080544 movimentos)

Esta solução cria uma árvore de caminho mais curto a partir do destino e para trás e, em seguida, percorre o caminho do estado inicial para o destino. Muito espaço para melhorar a velocidade, mas ainda completa todos os 55986 problemas em cerca de um minuto.

Assumindo que o algoritmo foi implementado corretamente, essa deve ser a melhor solução teoricamente.

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

}

Gostaria de explicar mais sobre o que você quer dizer com "árvore do caminho mais curto"?
— justhalf

De qualquer forma obrigado pela sua resposta, que me faz feliz que eu posso conseguir solução ideal perto usando apenas heurística :)
— justhalf

Uma árvore de caminho mais curto é uma árvore na qual cada nó / estado tem uma borda "próxima", levando ao nó / estado que, por sua vez, tem a menor distância do estado raiz / destino (= pilha principal classificada). Pode haver outros candidatos a nós próximos que tenham a mesma distância ou mais, mas nenhum que esteja mais próximo da raiz. Para esse problema, todas as arestas na árvore de caminho mais curto têm uma distância de uma, pois é necessário um movimento para passar de um estado para outro. Basicamente, uma árvore completa de caminho mais curto contém a solução para todos os estados que têm o mesmo estado de destino.
— MrBackend

Esqueceu de mencionar você no meu comentário. BTW, veja também en.wikipedia.org/wiki/Retrograde_analysis
— MrBackend

5

C - 2547172 para 55986 entradas

Há muito espaço para melhorias aqui. Para minha própria sanidade, simplifiquei isso para que só fosse possível inspecionar o elemento superior de cada pilha. O levantamento dessa restrição autoimposta permitiria otimizações, como determinar a ordem final antecipadamente e tentar minimizar o número de movimentos necessários para alcançá-la. Um exemplo atraente é que minha implementação tem um comportamento de pior caso se a pilha principal já estiver classificada.

Algoritmo:

  1. Preencha as duas pilhas auxiliares (espaço para otimização aqui, possivelmente atribuindo a qual pilha com base em algum tipo de pivô).
  2. Mesclar ordenar as pilhas auxiliares de volta na pilha principal.
  3. Repita 1-2 até a pilha principal ser classificada (mas ao contrário).
  4. Inverta a pilha principal (mais espaço para otimização, embaralhando muitos dos mesmos elementos repetidamente).

Análise:

  • A complexidade adicional do espaço é O (n) (para as duas pilhas auxiliares), o que é bom, pois esse era um requisito do problema.
  • A complexidade do tempo é O (n ^ 2) pela minha contagem. Correções são bem-vindas.

#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 move sobre 55986 entradas

Isso é muito ineficiente.

Acrescentarei o número total de movimentos após o OP esclarecer se são para todos esses casos ou para outros 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

Explicação

O que, comentários não são bons o suficiente para você?


Nota para OP: Obrigado por não fazer este código-golfe.


Acredito que, se houver uma maneira mais interessante de fazer o desafio que não seja [code-golf], isso deve ser feito dessa maneira.
— Justin

Ok, isso falha para [1,1,2]. Em python, considerando uma lista, [2,1,1]existe uma maneira de obter [2,1,1].index(1)2, ou seja, a partir do final superior?
— user80551

@Quincunx Não, em [2,1,1,3,5].index(1)==2vez de1
— user80551

Er, list.index(data)retorna o índice do item dataem list. Eu não sei o índice de dataie1
— user80551 01/04

O hack serialen(list)-(list[::-1].index(1))
— user80551

2

Python: 1.688.293 1.579.182 1.524.054 1.450.842 1.093.195 movimentos

O método principal é main_to_help_bestmover alguns elementos selecionados da pilha principal para a pilha auxiliar. Ele possui um sinalizador everythingque define se queremos mover tudo para o especificado destinationou se queremos manter apenas o maior destinationenquanto o restante no outro auxiliar.

Supondo que estamos dstusando o helper helper, a função pode ser descrita a seguir:

  1. Encontre as posições dos maiores elementos
  2. Mova tudo sobre o elemento mais alto para o topo helper recursivamente
  3. Mova o maior elemento para dst
  4. Empurre de volta helperpara a principal
  5. Repita 2-4 até que os maiores elementos estejam dst
  6. uma. Se everythingestiver definido, mova recursivamente os elementos em main para dst
    b. Caso contrário, mova recursivamente os elementos principais parahelper

O algoritmo de classificação principal ( sort2no meu código) irá chamar main_to_help_bestcom everythingset para Falsee, em seguida, moverá o elemento maior de volta para main, depois moverá tudo do auxiliar de volta para main, mantendo-o classificado.

Mais explicações incorporadas como comentários no código.

Basicamente, os princípios que eu usei são:

  1. Mantenha um ajudante para conter o (s) elemento (s) máximo (s)
  2. Mantenha outro ajudante para conter outros elementos
  3. Não faça movimentos desnecessários o máximo possível

O princípio 3 é implementado não contando a movimentação se a origem for o destino anterior (ou seja, nós apenas mudamos main para help1, então queremos passar de help1 para help2) e, além disso, reduzimos o número de movimentos em 1 se estão movendo-o de volta à posição original (ou seja, principal para ajudar1 e depois ajudar1 para principal). Além disso, se os nmovimentos anteriores estiverem todos movendo o mesmo número inteiro, podemos reordenar osn . Portanto, também aproveitamos isso para reduzir ainda mais o número de movimentos.

Isso é válido, pois conhecemos todos os elementos da pilha principal; portanto, isso pode ser interpretado como se você visse no futuro que vamos mover o elemento de volta, não devemos fazer isso.

Amostra de execução (as pilhas são exibidas de baixo para cima - portanto, o primeiro elemento é o fundo):

Comprimento 1
Movimentos: 0
Tarefas: 6
Máx: 0 ([1])
Médio: 0,000

Comprimento 2
Movimentos: 60
Tarefas: 36
Máx: 4 ([1, 2])
Média: 1.667

Comprimento 3
Movimentos: 1030
Tarefas: 216
Máx: 9 ([2, 3, 1])
Média: 4.769

Comprimento 4
Movimentos: 11765
Tarefas: 1296
Máx: 19 ([3, 4, 2, 1])
Média: 9,078

Comprimento 5
Movimentos: 112325
Tarefas: 7776
Máx: 33 ([4, 5, 3, 2, 1])
Médio: 14,445

Comprimento 6
Move: 968015
Tarefas: 46656
Máx: 51 ([5, 6, 4, 3, 2, 1])
Média: 20.748

--------------
No geral
Movimentos: 1093195
Tarefas: 55986
Média: 19,526

Podemos ver que o pior caso é quando o maior elemento é colocado na segunda parte inferior, enquanto o restante é classificado. Do pior caso, podemos ver que o algoritmo é O (n ^ 2).

O número de movimentos é obviamente mínimo para n=1e, n=2como podemos ver no resultado, e acredito que isso também seja mínimo para valores maiores de n, embora eu não possa provar isso.

Mais explicações estão no 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()

Eu não entendi sua pergunta 4. O que é isso armazenando o segundo maior elemento no segundo auxiliar? O que isso significa?
— 23714 Justin

Basicamente, basta acompanhar o segundo maior elemento de uma variável. Eu acho que estava pensando demais. Eu acho que está perfeitamente bem, haha
— justhalf

"Sua função / sub-rotina pode inspecionar qualquer pilha a qualquer momento". Portanto, se o que você está fazendo pode ser feito facilmente, percorrendo as pilhas e localizando o segundo maior elemento, tudo bem.
— 236 Justin

11
Ao "inspecionar qualquer pilha a qualquer momento", significa que posso ler a pilha como uma matriz sem consumir nenhuma movimentação? Isso muda tudo. Com relação à explicação, eu ainda estou no processo de atualização do algoritmo (eu o reduzi ainda mais), então atualizarei assim que terminar.
— justhalf

11
Entendo. Isso abre novas possibilidades e, definitivamente, podemos reduzir ainda mais o número de movimentos. Isso dificultará ainda mais o problema, haha, já que a tarefa é essencialmente "dada uma matriz de números inteiros, encontre o número mínimo de movimentos para classificá-lo na Torre de Hanói". Se nos for permitido ver apenas o topo da pilha, meu algoritmo está próximo do ideal (se não o ideal)
— justhalf

1

Java - 2129090 2083142 move-se em 55986 matrizes

O link ideone .

A estrutura para garantir que o algoritmo esteja correto:

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

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

A caixa de teste:

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 ++ não mediu movimentos (pegs: p1, p2, p3) Não sabe como adicionar código C ++ que usa STL (problema de formatação). Perder partes do código devido aos formatos de tag html no código.

  1. Obter n: contagem dos principais elementos classificados na p1
  2. Faça um hanoi deles (n) para p3 usando p2 (mantém a propriedade classificada)
  3. Mova os próximos itens (pelo menos 1) na ordem inversa de p1 para p2.
  4. Mesclar dados de movimentação (n + m) em p2 e p3 para 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. Chame a classificação hanoi recursivamente, que classificará pelo menos n + m + 1 elementos agora.
  6. Pare quando n = tamanho de p1.
#incluir 
#incluir 
usando espaço para nome std;
/ ************************************************* ***************************** 
   Mostrar o vetor (tive que arriscar
**************************************************** ***************************** /    

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

   d2 = p2.back (); p2.pop_back ();
   // os dados serão decrescentes na 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);
      --contagem;
   }
   // Retroceda com hanoi de p3 para p1
   // use p2 como inter
   hanoi (p3, p2, p1, n);
}
/ ************************************************* ******************************
   Obtém a contagem dos primeiros elementos classificados
**************************************************** ***************************** /    
int get_top_sorted_count (vetor e p1) {
   vector :: iterador;
   int anterior = 0;
   int n = 1;

   Para (it = --p1.end (); it> = p1.begin (); --it) {
     if (it == --p1.end ()) {
    prev = * it;
        continuar;
     }
     if (* it & p1, vetor & p2, vetor & p3) {
    int n, d1;
    n = get_top_sorted_count (p1);
    if (n == p1.size ()) {
       Retorna;
    }
    hanoi (p1, p2, p3, n);
    p2.push_back (p1.back ());
    p1.pop_back ();
    merge_move (p1, p2, p3);
    hanoi_sort (p1, p2, p3);
}
/ ************************************************* ******************************    
    Tipo baed em hanoi
**************************************************** ***************************** /    
int main (vazio)
{
  vetor 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, "... Classificação Bef ...");
  hanoi_sort (p1, p2, p3);
  show (p1, "... Classificação posterior ...");
}

Você pode escrever o código para isso? Caso contrário, isso não é uma resposta.
— 237 Justin

Não vejo a hanoi(...)função. Além disso, você tem 2 #includes que não compilam. Por favor, poste o código completo.
— Hosch250
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.