Башта сорту Ханой


21

Напишіть функцію / підпрограму для сортування списку цілих чисел, стилю Вежі Ханої .

Вам буде наданий стек цілих чисел. Це основний стек.

Вам також надають ще два помічники. Ці допоміжні стеки мають унікальну властивість: кожен елемент повинен бути меншим або такого ж розміру, як елемент під ним. Основний стек не має такого обмеження.

Вам доручено сортувати основний стек на місці, підклавши під нього найбільші цілі числа. Ваша функція / підпрограма поверне (або еквівалент) кількість ходів, зроблених під час сортування стека.
Примітка. Ви повинні сортувати основний стек за місцем , не сортуючи інший стек і не називаючи його відповіддю. Однак якщо ви з якихось причин цього не можете зробити, ви можете імітувати змінні стеки, але пам’ятайте, що це сорт Тауер Ханой; є лише 3 кілочки, і лише 1 кілочок може бути не упорядкованим.

Ваша функція / підпрограма може перевірити будь-який стек у будь-який час, але він може зробити крок лише натисканням та натисканням. Один хід - це поп з однієї стеки, який висувається на інший.

Перевірте свою функцію / підпрограму для кожної перестановки перших 6 натуральних чисел. Іншими словами, протестувати функцію / підпрограму для {1},{2},...,{6},{1,1},{1,2},...,{1,6},{2,1},...(це повинно бути в цілому або можливості (спасибі Говарду за виправлення моєї математики)). Функція / підпрограма, яка переміщує елементи, виграє найменшу кількість разів.61+62+...+6655986


@JanDvorak Це була така ідея на тестах. Якщо програмісту потрібно запустити функцію 46656 разів, чому б він / вона хотіла так довго чекати на вихід? Або є ще один хороший спосіб обмеження подібних речей?
Джастін

Якимось чином мені подобається виклик сліпого заходу: ви можете сказати лише "перейти зі стека х до стека у" і побачити, чи ваш крок вдалий, і якщо він станеться, вам за це стягується плата; бонусні бали - невдача в переміщенні позначається викиданням виключення / правильним поверненням.
Джон Дворак

3
Список наданих вами перестановок містить 6**1+6**2+...+6**6=55986елементи.
Говард

1
@ m.buettner Відмінністю є те, що це елементи декартового продукту s від 1 до 6 разів . Я, мабуть, назвав би це "набором перестановок кожного елемента набору потужностей перших 6 натуральних чисел, крім нульового набору".
Джастін

1
@Quincunx, за винятком комплекту живлення, не містить наборів з повторними числами. ;) ... але я не думаю, що ми все-таки повинні занадто серйозно ставитися до цього, доки нам все зрозуміло щодо елементів набору.
Мартін Ендер

Відповіді:


4

Java - оптимальне рішення (1080544 ходи)

Це рішення будує найкоротше дерево шляху від цілі та назад, а потім проходить шлях від початкового стану до цілі. Багато місця для покращення швидкості, але воно все-таки завершує всі 55986 проблем за хвилину.

Якщо припустити, що алгоритм правильно виконаний, це має бути теоретично найкращим рішенням.

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

}

Хочете більше пояснити, що ви маєте на увазі під «деревом найкоротшого шляху»?
justhalf

У будь-якому випадку, дякую за вашу відповідь, мене радіє, що я можу досягти майже оптимального рішення, використовуючи лише евристику :)
justhalf

Найкоротше дерево шляху - це дерево, у якому кожен вузол / стан має один "наступний" край, що веде до вузла / стану, який, у свою чергу, має найкоротший відстанню до кореневого / цільового стану (= відсортований основний стек). Можуть бути й інші наступні вузли-кандидати, які мають однакову відстань або більше, але жоден, який ближче до кореня. Для цієї проблеми всі ребра в найкоротшому дереві шляху мають відстань в один, оскільки для переходу з одного стану в інший потрібен один хід. В основному, повне дерево найкоротшого шляху містить рішення для всіх станів, які мають однаковий цільовий стан.
MrBackend

@justhalf Забув згадати вас у своєму коментарі. BTW, див. Також en.wikipedia.org/wiki/Retrograde_analysis
MrBackend

5

C - 2547172 для 55986 входів

Тут є багато можливостей для вдосконалення. З міркувань я спростив це так, щоб можна було лише оглянути верхній елемент кожного стека. Зняття цього самостійного обмеження дозволило б зробити оптимізацію, як-от визначити остаточне замовлення заздалегідь, і намагатися мінімізувати кількість кроків, необхідних для його досягнення. Переконливим прикладом є те, що моя реалізація має найгірший випадок, якщо основний стек вже відсортований.

Алгоритм:

  1. Заповніть обидва допоміжні стеки (тут є місце для оптимізації, можливо, призначаючи до цього стека на основі певного зведення).
  2. Об'єднайте сортування допоміжних стеків на основний стек.
  3. Повторюйте 1-2, поки основний стек не буде відсортований (але зворотно).
  4. Переверніть основний стек (більше місця для оптимізації, повторно перемішуючи багато одних і тих же елементів).

Аналіз:

  • Додаткова складність простору становить O (n) (для двох допоміжних стеків), що добре, оскільки це було вимогою проблеми.
  • За моєю кількістю, складність у часі становить O (n ^ 2). Виправлення вітаються.

#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 переміщує понад 55986 входів

Це дуже неефективно.

Я додаю загальну кількість рухів після того, як ОР роз'ясне, чи є вони для всіх цих випадків або для конкретних інших випадків.


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

Пояснення

Що, коментарі недостатньо для вас?


Примітка до ОП: Дякуємо, що не зробили цей код-гольф.


Я вважаю, що якщо є більш цікавий спосіб вирішити завдання, відмінний від [code-golf], це слід зробити так.
Джастін

Гаразд, це не вдається для [1,1,2]. У python, розглядаючи список, [2,1,1]є спосіб отримати [2,1,1].index(1)як 2, тобто починаючи з вищого кінця?
користувач80551

@Quincunx Ні, [2,1,1,3,5].index(1)==2замість1
user80551

Er, list.index(data)повертає індекс елемента dataв list. Я не знаю індекс, dataтобто1
user80551,

len(list)-(list[::-1].index(1))
Злом

2

Пітон: 1,688,293 1,579,182 1,524,054 1,450,842 1,093,195 ходи

Основний метод полягає в main_to_help_bestтому, щоб перенести деякі вибрані елементи з основного стека в хелперний стек. У ньому є прапор, everythingякий визначає, чи хочемо ми перемістити все у вказане destination, чи ми хочемо зберегти лише найбільший, destinationа решта - в іншому помічнику.

Припустимо, що ми переходимо до dstвикористання помічника helper, функцію можна приблизно описати так:

  1. Знайдіть позиції найбільших елементів
  2. Перемістіть усе на вершині найбільшого елемента helperрекурсивно
  3. Перемістіть найбільший елемент до dst
  4. Відсуньтесь від helperосновної
  5. Повторюйте 2-4, поки не з’являться найбільші елементи dst
  6. а. Якщо everythingвстановлено, рекурсивно переміщуйте елементи в основному на dst
    b. В іншому випадку рекурсивно переміщуйте елементи в основному доhelper

Алгоритм основного сортування ( sort2в моєму коді) зателефонує main_to_help_bestіз everythingвстановленим на False, а потім перемістить найбільший елемент назад до основного, а потім перемістить все від помічника назад до основного, зберігаючи його впорядкованому.

Більше пояснення внесено як коментарі до коду.

В основному принципи, якими я користувався:

  1. Тримайте одного помічника, щоб містити максимальний елемент (и)
  2. Тримайте іншого помічника, щоб містити будь-які інші елементи
  3. Не робіть зайвих рухів якомога більше

Принцип 3 реалізується не рахуючи переміщення, якщо джерело є попереднім пунктом призначення (тобто ми просто перейшли головним до help1, тоді ми хочемо перейти з help1 до help2), і далі, ми зменшимо кількість руху на 1, якщо ми повертаємо його у вихідне положення (тобто головне до help1, а потім help1 - main). Крім того, якщо всі попередні nрухи рухаються одним і тим же цілим числом, ми можемо фактично змінити їх n. Тож ми також цим скористаємось, щоб зменшити кількість рухів далі.

Це справедливо, оскільки ми знаємо всі елементи в основному стеку, тому це можна трактувати як бачення в майбутньому, що ми збираємося перемістити елемент назад, ми не повинні робити цей крок.

Виконання зразка (стеки відображаються знизу вгору - значить, перший елемент - знизу):

Довжина 1
Рухи: 0
Завдання: 6
Макс: 0 ([1])
Середнє значення: 0,000

Довжина 2
Рухи: 60
Завдання: 36
Макс: 4 ([1, 2])
Середній показник: 1.667

Довжина 3
Переїзди: 1030
Завдання: 216
Макс: 9 ([2, 3, 1])
Середній показник: 4.769

Довжина 4
Переїзди: 11765
Завдання: 1296
Макс: 19 ([3, 4, 2, 1])
Середній показник: 9.078

Довжина 5
Переїзди: 112325
Завдання: 7776
Макс: 33 ([4, 5, 3, 2, 1])
Середній показник: 14.445

Довжина 6
Переїзди: 968015
Завдання: 46656
Макс: 51 ([5, 6, 4, 3, 2, 1])
Середня оцінка: 20,748

--------------
Загалом
Переїзди: 1093195
Завдання: 55986
Середня оцінка: 19.526

Ми можемо бачити, що найгірший випадок, коли найбільший елемент розміщується на другому дні, а решта сортується. З найгіршого випадку ми бачимо, що алгоритм є O (n ^ 2).

Кількість рухів очевидно мінімальна для, n=1і n=2як ми бачимо з результату, і я вважаю, що це також мінімально для більших значень n, хоча я не можу цього довести.

Більше пояснень - у коді.

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

Я не розумію вашого питання 4. Що це за зберігання другого за величиною елемента на другому помічнику? Що це означає?
Джастін

В основному просто слідкуйте за другим за величиною елементом змінної. Я здогадуюсь, що я це переосмислив. Я думаю, що це прекрасно, ха-ха
половина

"Ваша функція / підпрограма може перевірити будь-який стек у будь-який час". Тож якщо те, що ви робите, можна легко зробити, пробігаючи стеки та знаходячи місце другого за величиною елемента, це добре.
Джастін

1
Якщо "перевірити будь-який стек у будь-який час", чи означає це, що я можу читати стек як масив, не витрачаючи жодного переміщення? Це все змінює. Щодо пояснення, я все ще перебуваю в процесі оновлення алгоритму (я отримав його ще нижче), тому я оновлю, як тільки я закінчу.
справедлива половина

1
Розумію. Це відкриває цілком нові можливості, і, безумовно, ми можемо зменшити кількість кроків далі. Однак це ускладнить проблему, ха-ха, оскільки завдання, по суті, є "заданим масивом цілих чисел, знайдіть мінімальну кількість рухів, щоб сортувати його в Ханойській вежі". Якщо нам дозволяють бачити лише вершину стека, то мій алгоритм близький до оптимального (якщо не оптимального)
justhalf

1

Java - 2129090 2083142 рухається на 55986 масивах

Посилання ideone .

Рамка для забезпечення правильності алгоритму:

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

Фактичний алгоритм:

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

Тестова шафа:

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 ++ Не вимірював ходи (прив'язки: p1, p2, p3) Не знаю, як додати код C ++, який використовує STL (проблема форматування). Втрата частин коду через формати html-тегів у коді.

  1. Отримаємо n: кількість найкраще відсортованих елементів у p1
  2. Зробіть hanoi переміщення їх (n) до p3, використовуючи p2 (зберігає відсортовану властивість)
  3. Перемістіть наступні m елем (принаймні 1) у зворотному порядку від p1 до p2.
  4. Об'єднайте дані переміщення (n + m) у p2 & p3 до 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. Викликайте сортування hanoi рекурсивно, який зараз буде сортувати принаймні n + m + 1 елементів.
  6. Зупинка, коли n = розмір p1.
#включати 
#включати 
використання простору імен std;
/ *************************************************** ***************************** 
   Показати вектор (Довелося до випадкового кут
**************************************************** ***************************** /    

недійсне шоу (вектор p, рядок msg)
{
   вектор :: ітератор це;
   printf ("% s \ n", msg.c_str ());
   for (it = p.begin (); it & fr, vector & inter, vector & to, int n) {
   int d1;
   якщо (n & p1, вектор & p2, вектор & p3) {
   int d3, d2, d1;
   int count, n;
   bool d2_added;

   d2 = p2.back (); p2.pop_back ();
   // дані будуть зменшуватися в p1
   d2_added = помилково;
   while (p3.size ()> 0) {
      d3 = p3.back (); p3.pop_back ();
      якщо (d2_added == false && d2 0) {
      d1 = p1.back (); p1.pop_back ();
      p3.push_back (d1);
      --рахувати;
   }
   // Переміщення назад з ханой від p3 до p1
   // використовувати p2 як inter
   ханой (p3, p2, p1, n);
}
/ *************************************************** ******************************
   Отримує кількість перших відсортованих елементів
**************************************************** ***************************** /    
int get_top_sorted_count (vector & p1) {
   вектор :: ітератор це;
   int prev = 0;
   int n = 1;

   for (it = --p1.end (); it> = p1.begin (); --it) {
     якщо (це == --p1.end ()) {
    prev = * це;
        продовжувати;
     }
     if (* it & p1, vector & p2, vector & p3) {
    int n, d1;
    n = get_top_sorted_count (p1);
    якщо (n == p1.size ()) {
       повернення;
    }
    ханой (p1, p2, p3, n);
    p2.push_back (p1.back ());
    p1.pop_back ();
    merge_move (p1, p2, p3);
    hanoi_sort (p1, p2, p3);
}
/ *************************************************** ******************************    
    Сорти базуються на ханої
**************************************************** ***************************** /    
int main (пустота)
{
  вектор 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);
  показати (p1, "... Сортування Bef ...");
  hanoi_sort (p1, p2, p3);
  показати (p1, "... Aft Sort ...");
}

Чи можете ви написати код для цього? Інакше це не відповідь.
Джастін

Я не бачу hanoi(...)функції. Також у вас є 2 #includeс, які не компілюються. Будь ласка, опублікуйте повний код.
Hosch250
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.