Пітон: 1,688,293 1,579,182 1,524,054 1,450,842 1,093,195 ходи
Основний метод полягає в main_to_help_best
тому, щоб перенести деякі вибрані елементи з основного стека в хелперний стек. У ньому є прапор, everything
який визначає, чи хочемо ми перемістити все у вказане destination
, чи ми хочемо зберегти лише найбільший, destination
а решта - в іншому помічнику.
Припустимо, що ми переходимо до dst
використання помічника helper
, функцію можна приблизно описати так:
- Знайдіть позиції найбільших елементів
- Перемістіть усе на вершині найбільшого елемента
helper
рекурсивно
- Перемістіть найбільший елемент до
dst
- Відсуньтесь від
helper
основної
- Повторюйте 2-4, поки не з’являться найбільші елементи
dst
- а. Якщо
everything
встановлено, рекурсивно переміщуйте елементи в основному на dst
b. В іншому випадку рекурсивно переміщуйте елементи в основному доhelper
Алгоритм основного сортування ( sort2
в моєму коді) зателефонує main_to_help_best
із everything
встановленим на False
, а потім перемістить найбільший елемент назад до основного, а потім перемістить все від помічника назад до основного, зберігаючи його впорядкованому.
Більше пояснення внесено як коментарі до коду.
В основному принципи, якими я користувався:
- Тримайте одного помічника, щоб містити максимальний елемент (и)
- Тримайте іншого помічника, щоб містити будь-які інші елементи
- Не робіть зайвих рухів якомога більше
Принцип 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()