Рубік сортування матриці (він же головоломка тора)


16

Ідея цього проста: маючи матрицю цілих чисел, давайте сортуємо її, застосовуючи рухи в стилі Рубіка. Це означає, що ви можете вибрати один рядок або стовпець і обертати його елементи в будь-якому напрямку:

[1, 3, 2, 4]  => [3, 2, 4, 1] (rotate left for rows/up for columns)
[1, 3, 2, 4]  => [4, 1, 3, 2] (rotate right for rows/down for columns)

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

[а11а12а13а14а21а22а23а24а31а32а33а34]

буде вважатися відсортованим, якщо його елементи відповідають наступному обмеженню:

a11a12a13a14a21a22a23a24a31a32a33a34

I / O

  • Введенням буде матриця натуральних чисел без повторних значень.
  • Виходом будуть рухи, необхідні для їх сортування. Оскільки це не код гольф виклику і вам не потрібно турбуватися про його довжині, пропонований формат для кожного руху , #[UDLR]де #це число рядків або стовпців для переміщення (0 індексованих) і [UDLR]являє собою один символ в тому , що діапазон, який визначає, чи є рух вгору / вниз (для стовпців) або вліво / вправо (для рядків). Так 1Uби означало "переміщення 1-го стовпчика вгору", але 1Rбуло б "переміщення 1-го ряду вправо". Рух буде відокремлено коми, тому рішення буде виражатися наступним чином: 1R,1U,0L,2D.

Оцінка балів

Спроба сортування матриці таким чином може бути дорогою, оскільки існує маса можливих комбінацій ходів, а також існує безліч можливих списків ходів, які можуть її сортувати, тому мета полягає в тому, щоб написати якийсь код, який сортує N * N матриць нижче. Оцінка буде найбільшим розміром N, який ви зможете вирішити за розумну кількість часу 1 без помилок (чим більше розмір вирішеної матриці, тим краще). У випадку зрізання краваткою краватки буде кількість рухів у знайденому шляху (чим коротший шлях, тим краще).

Приклад: якщо користувач A знаходить рішення для N = 5 і B знаходить рішення для N = 6, B виграє незалежно від довжини обох шляхів. Якщо вони обидва знаходять рішення для N = 6, але рішення, знайдене A, має 50 кроків, а рішення B - 60 кроків, A виграє.

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

Тестові справи

Наступні матриці ( посилання Pastebin для більш копіюваної версії) були створені, починаючи з вже відсортованих матриць, розшифровуючи їх до 10К випадкових рухів у стилі Рубіка:

[8561110131513]
[211012161762214851926132431]
[11381659402126221124143928321937310301736734]
[34214022354118333130124319113924282344136538451417916132683476254]
[20361711550187267341032355424396306139284154272357048132512465863523784533146859655673606422]
[85565275894441682715879132373973676419997846164221631004172131197309328403365070258058960845496172943342335776182482943866]
[567990617112211031551144284851306188443386611324962010275685888098351007713216410810601144023472731068232120263653936910454191111176217278873349155811695112571189151426545]

Plaintext Test Cases:

[[8, 5, 6], [11, 10, 1], [3, 15, 13]]

[[21, 10, 12, 16], [17, 6, 22, 14], [8, 5, 19, 26], [13, 24, 3, 1]]

[[1, 13, 8, 16, 5], [9, 40, 21, 26, 22], [11, 24, 14, 39, 28], [32, 19, 37, 3, 10], [30, 17, 36, 7, 34]]

[[34, 21, 40, 22, 35, 41], [18, 33, 31, 30, 12, 43], [19, 11, 39, 24, 28, 23], [44, 1, 36, 5, 38, 45], [14, 17, 9, 16, 13, 26], [8, 3, 47, 6, 25, 4]]

[[20, 36, 17, 1, 15, 50, 18], [72, 67, 34, 10, 32, 3, 55], [42, 43, 9, 6, 30, 61, 39], [28, 41, 54, 27, 23, 5, 70], [48, 13, 25, 12, 46, 58, 63], [52, 37, 8, 45, 33, 14, 68], [59, 65, 56, 73, 60, 64, 22]]

[[85, 56, 52, 75, 89, 44, 41, 68], [27, 15, 87, 91, 32, 37, 39, 73], [6, 7, 64, 19, 99, 78, 46, 16], [42, 21, 63, 100, 4, 1, 72, 13], [11, 97, 30, 93, 28, 40, 3, 36], [50, 70, 25, 80, 58, 9, 60, 84], [54, 96, 17, 29, 43, 34, 23, 35], [77, 61, 82, 48, 2, 94, 38, 66]]

[[56, 79, 90, 61, 71, 122, 110, 31, 55], [11, 44, 28, 4, 85, 1, 30, 6, 18], [84, 43, 38, 66, 113, 24, 96, 20, 102], [75, 68, 5, 88, 80, 98, 35, 100, 77], [13, 21, 64, 108, 10, 60, 114, 40, 23], [47, 2, 73, 106, 82, 32, 120, 26, 36], [53, 93, 69, 104, 54, 19, 111, 117, 62], [17, 27, 8, 87, 33, 49, 15, 58, 116], [95, 112, 57, 118, 91, 51, 42, 65, 45]]

Please ask for more if you solve them all. :-) And many thanks to the people who helped me refine this challenge while in the sandbox.


1 Доцільна кількість часу: будь-яка кількість часу, яка не підриває наш терпіння під час тестування вашого рішення. Зауважте, що TIO запускає код лише 60 секунд, будь-яка кількість часу, що перевищує цю межу, змусить нас перевірити код на наших машинах. Приклад: мій досить неефективний алгоритм потребує декількох мілісекунд, щоб вирішити матриці порядку 3х3 та 4х4, але я лише перевірив його матрицею 5х5 і на її вирішення знадобилось 317 секунд (більш ніж 5 мільйонів рухів, дуже смішно, якщо вважати, що матриця для розв’язання була скремблірована лише в 10 К разів). Я спробував зменшити кількість рухів до менше 10 К, але я здався через 30 хвилин, виконуючи код.


1
Приємний виклик! Однак у мене є кілька запитів / запитань: 1) Чи можете ви надати тестові приклади у більш зручному для копіювання форматі? (наприклад, пастбін) 2) Чи можете ви надати більш точне визначення порядку обмеження часу? 3) Чи гарантована матриця квадратною? (Тестові випадки говорять про це, але визначення цього немає.)
Арнольд,

@Arnauld 1) Я на цьому. 2) Я не хотів встановлювати обмеження часу, і ніхто не пропонував жодного обмеження, поки виклик був у пісочниці. Якщо вам потрібен, чи вважаєте ви 30 хвилин розумним обмеженням? 3) Так, показані тестові матриці, і всі вони будуть квадратними, якщо потрібно більше.
Чарлі

Існує (порівняно простий у реалізації) алгоритм O (розмір вводу) для цього завдання, тому це не так цікаво, як може здатися спочатку.
користувач202729

@ user202729 Яким буде розмір вводу у вашому O(input size)тоді? Для матриці 5x5 це було б O(25)? Це здається надзвичайно швидким, тому мені було б дуже цікаво побачити цей алгоритм чи його реалізацію. EDIT: Ви розумієте, що ми вводимо «скремблированную» матрицю і виводимо рухи, правда? Не навпаки.
Kevin Cruijssen

4
Я думаю, це щось на зразок цього алгоритму
Кирилл Л.

Відповіді:


8

Нім

import algorithm, math, sequtils, strutils

let l = split(stdin.readLine())
var m = map(l, parseInt)
let n = int(sqrt(float(len(m))))
let o = sorted(m, system.cmp[int])

proc rotations(P, Q: int): tuple[D, L, R, U: string, P, Q: int]=
  result = (D: "D", L: "L", R: "R", U: "U", P: P, Q: Q)
  if P > n - P:
    result.D = "U"
    result.U = "D"
    result.P = n - P
  if Q > n - Q:
    result.L = "R"
    result.R = "L"
    result.Q = n - Q

proc triangle(r: int): string=
  let p = r div n
  let q = r mod n
  let i = find(m, o[r])
  let s = i div n
  let t = i mod n
  var u = s
  var v = q
  if s == p and t == q:
    return ""
  var patt = 0
  if p == s: 
    u = s + 1
    patt = 4
  elif q == t:
    if q == n - 1:
      v = t - 1
      patt = 8
    else:
      u = p
      v = t + 1
      patt = 3
  elif t > q:
    patt = 2
  else:
    patt = 7
  var Q = abs(max([q, t, v]) - min([q, t, v]))
  var P = abs(max([p, s, u]) - min([p, s, u]))
  let x = p*n + q
  let y = s*n + t
  let z = u*n + v
  let w = m[x]
  m[x] = m[y]
  m[y] = m[z]
  m[z] = w
  let R = rotations(P, Q)

  result = case patt:
    of 2:
      repeat("$#$#," % [$v, R.D], R.P) & 
        repeat("$#$#," % [$u, R.L], R.Q) &
        repeat("$#$#," % [$v, R.U], R.P) & 
        repeat("$#$#," % [$u, R.R], R.Q)
    of 3:
      repeat("$#$#," % [$q, R.U], R.P) & 
        repeat("$#$#," % [$p, R.L], R.Q) &
        repeat("$#$#," % [$q, R.D], R.P) & 
        repeat("$#$#," % [$p, R.R], R.Q)
    of 4:
      repeat("$#$#," % [$p, R.L], R.Q) & 
        repeat("$#$#," % [$q, R.U], R.P) &
        repeat("$#$#," % [$p, R.R], R.Q) & 
        repeat("$#$#," % [$q, R.D], R.P)
    of 7:
      repeat("$#$#," % [$v, R.D], R.P) & 
        repeat("$#$#," % [$u, R.R], R.Q) &
        repeat("$#$#," % [$v, R.U], R.P) & 
        repeat("$#$#," % [$u, R.L], R.Q)
    of 8:
      repeat("$#$#," % [$s, R.R], R.Q) & 
        repeat("$#$#," % [$t, R.D], R.P) &
        repeat("$#$#," % [$s, R.L], R.Q) & 
        repeat("$#$#," % [$t, R.U], R.P)
    else: ""

proc Tw(p, t, P, Q: int): string =
  let S = P + Q
  result = "$#D,$#$#U,$#$#D,$#$#U," % [
    $t, if P > n - P: repeat("$#L," % $p, n - P) else: repeat("$#R," % $p, P),
    $t, if S > n - S: repeat("$#R," % $p, n - S) else: repeat("$#L," % $p, S), 
    $t, if Q > n - Q: repeat("$#L," % $p, n - Q) else: repeat("$#R," % $p, Q), 
    $t]

proc line(r: int): string=
  let p = n - 1
  let q = r mod n
  let i = find(m, o[r])
  var t = i mod n
  if t == q: 
    return ""
  let j = t == n - 1
  var P = t - q
  let x = p*n + q
  let y = x + P
  let z = y + (if j: -1 else: 1)
  let w = m[x]
  m[x] = m[y]
  m[y] = m[z]
  m[z] = w
  if j:
    let R = rotations(1, P)
    result = "$#D,$#$#U,$#$#R,$#D,$#L,$#U," % [
      $t, repeat("$#$#," % [$p, R.R], R.Q), 
      $t, repeat("$#$#," % [$p, R.L], R.Q), 
      $p, $t, $p, $t]
  else:
    result = Tw(p, t, P, 1)  
  
proc swap: string=
  result = ""
  if m[^1] != o[^1]:
    m = o
    for i in 0..(n div 2-1):
      result &= Tw(n - 1, n - 2*i - 1, 1, 1)
    result &= "$#R," % $(n - 1)
  
var moves = ""
for r in 0..(n^2 - n - 1):
  moves &= triangle(r)
if n == 2 and m[^1] != o[^1]:
  m = o
  moves &= "1R"
else:
  for r in (n^2 - n)..(n^2 - 3):
    moves &= line(r)
  if n mod 2 == 0:
    moves &= swap()
  if len(moves) > 0:
    moves = moves[0..^2]
  
echo moves

Спробуйте в Інтернеті!

Швидка спроба впровадження алгоритму рішення головоломки Торус із статті, опублікованої в Algorithms 2012, 5, 18-29, про яку я згадував у коментарях.

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

Тут також є валідатор в Python 2 . Як вхідні дані приймаються два рядки: оригінальна скремблирована матриця в тому ж вигляді, що і основний код, і запропонована послідовність рухів. Виведенням валідатора є матриця, отримана в результаті застосування цих ходів.

Пояснення

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

Ми робимо це, виконуючи ряд "обертів трикутника" ( proc triangle) - послідовності рухів, в результаті яких місцями змінюються лише три комірки, а всі інші залишаються незмінними. Беремо кожну поспіль «робочу» комірку з координатами[p,q], то знайдіть клітинку [с,т] який наразі містить число, на яке слід перейти [p,q]та завершіть правильний трикутник, вибравши третю точку [у,v] згідно з деякою схемою, як показано на фіг. 4 пов'язаної статті.

На рис. 2 автори представляють 8 можливих шаблонів та відповідних послідовностей рухів, але в моєму коді всі випадки фактично охоплені лише 5 шаблонами, так що ні. 1, 5 і 6 не використовуються.

У другій частині останній ряд, крім двох останніх елементів, упорядковується, виконуючи "обертання трьох елементів" на лінії ( proc line), яка складається з двох обертів трикутника кожен (див. Рис. 3 статті).

Вибираємо поточну робочу клітинку [p,q] як ліва точка, комірка, що містить цільове значення [с,т] як центральний пункт, і [с,т+1]як правильна точка. Цей рух на захід названийТWв статті, і так це мій рядок формування проц для цього. Якщот - це вже остання колонка, так що т+1 не існує, ми беремо [с,т-1] як третій пункт, і відповідно модифікуйте дію: два обертання трикутника виконуються шаблонами 7 і 8 (замість 7 і 1 в оригіналі ТW послідовність).

Нарешті, якщо нЯк це не дивно, два інші елементи повинні бути вже в наявності, оскільки ми гарантуємо, що рішення існує. Якщон є рівним, а два решти елемента не стоять на місці, то відповідно до леми 1 (стор. 22) вони можуть бути замінені рядом ТW рухається з наступною зміною на схід (=R). Оскільки наведений приклад здійснює заміну перших двох записів, і нам потрібно поміняти два останніх, наші proc swapвиконуютьсяТW рухається в зворотному порядку.

У крайовому випадку о н=2 нам взагалі не потрібні всі ці складні процедури - якщо елементи останнього рядка не знаходяться після частини 1, єдиного 1R ходу достатньо, щоб матриця була повністю впорядкованою.

Оновлення: Додано нове, proc rotationsщо змінює напрямок рухів, якщо це призведе до менших кроків.


Вражає! Я тоді створять ще кілька тестових випадків. Тим часом, я впевнений, що це рішення можна оптимізувати, оскільки є фрагменти на зразок 7L,7L,7L,7L,7D,7D,7D,7Dтого, що можна зменшити і 8R,8R,8R,8R,8R,8R,8Rніж можна перетворити на 8L,8Lматрицю 9x9.
Чарлі

Я спробував ваш алгоритм з матрицею 100x100, і він вирішує його менше ніж за 4 секунди. Я дійсно не очікував, що ця проблема матиме лінійний час. Я спробую написати кращі виклики в майбутньому!
Чарлі

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

@Charlie Хоча я все ще намагаюся трохи вдосконалити поточне рішення, я справді не маю уявлення, як вирішити загальну проблему оптимізації шляху ...
Кирилл Л.

5

Python 2 , розмір 100 за <30 секунд на TIO

import random
def f(a):
 d = len(a)
 r = []
 def V(j, b = -1):
  b %= d
  if d - b < b:
   for k in range(d - b):
    if r and r[-1] == "U%d" % j:r.pop()
    else:r.append("D%d" % j)
    b = a[-1][j]
    for i in range(len(a) - 1):
     a[-1 - i][j] = a[-2 - i][j]
    a[0][j] = b
  else:
   for k in range(b):
    if r and r[-1] == "D%d" % j:r.pop()
    else:r.append("U%d" % j)
    b = a[0][j]
    for i in range(len(a) - 1):
     a[i][j] = a[i + 1][j]
    a[-1][j] = b
 def H(i, b = -1):
  b %= d
  if d - b < b:
   for k in range(d - b):
    if r and r[-1] == "L%d" % i:r.pop()
    else:r.append("R%d" % i)
    a[i] = a[i][-1:] + a[i][:-1]
  else:
   for k in range(b):
    if r and r[-1] == "R%d" % i:r.pop()
    else:r.append("L%d" % i)
    a[i] = a[i][1:] + a[i][:1]
 b = sorted(sum(a, []))
 for i in range(d - 1):
  for j in range(d):
   c = b.pop(0)
   e = sum(a, []).index(c)
   if e / d == i:
    if j == 0:H(i, e - j)
    elif j < e % d:
     if i:
      V(e % d, 1)
      H(i, j - e)
      V(e % d)
      H(i, e - j)
     else:
      V(e)
      H(1, e - j)
      V(j, 1)
   else:
    if j == e % d:
     H(e / d)
     e += 1
     if e % d == 0:e -= d
    if i:
     V(j, i - e / d)
    H(e / d, e - j)
    V(j, e / d - i)
 c = [b.index(e) for e in a[-1]]
 c = [sum(c[(i + j) % d] < c[(i + k) % d] for j in range(d) for k in range(j)) % 2 and d * d or sum(abs(c[(i + j) % d] - j) for j in range(d)) for i in range(d)]
 e = min(~c[::-1].index(min(c)), c.index(min(c)), key = abs)
 H(d - 1, e)
 for j in range(d - 2):
  e = a[-1].index(b[j])
  if e > j:
   c = b.index(a[-1][j])
   if c == e:
    if e - j == 1:c = j + 2
    else:c = j + 1
   V(e)
   H(d - 1, j - e)
   V(e, 1)
   H(d - 1, c - j)
   V(e)
   H(d - 1, e - c)
   V(e, 1)
 return r

Спробуйте в Інтернеті! Посилання включає три невеликі тестові випадки з повним вихідним ходом, а також тихий тест розміром 100x100, щоб показати, що код працює (вихідний хід буде перевищувати межі TIO). Пояснення: Код намагається виконати сортування вставки на масиві, будуючи його у порядку зростання, як він йде. Для всіх рядків, крім останнього ряду, існує ряд випадків:

  • Елемент знаходиться у правильному рядку, але належить до стовпця 0. У цьому випадку він просто обертається, поки не досягне стовпця 0.
  • Елемент знаходиться в правильному місці. У цьому випадку нічого не відбувається. (Це також справедливо, якщо елемент належить до стовпця 0, у цьому випадку відбувається лише 0 обертів.)
  • Елемент знаходиться у верхньому рядку, але в неправильному стовпці. У такому випадку вона повертається вниз, потім горизонтально, поки елемент не стоїть у правильному стовпчику, а потім знову повертається вгору.
  • Елемент знаходиться в правильному рядку, але в неправильному стовпці. У цьому випадку він повертається вгору, потім ряд повертається до своєї колонки, потім повертається вниз, потім ряд повертається назад. (Фактично це поворот наступного випадку.)
  • Елемент знаходиться у правильному стовпці, але в неправильному рядку. У такому випадку рядок повертається вправо, щоб зменшити його до останнього випадку.
  • Елемент знаходиться в неправильному рядку та неправильному стовпчику. У цьому випадку правильний стовпець повертається до неправильного рядка (пропускається для верхнього рядка), після чого цей рядок повертається до правильного стовпця, а стовпець повертається назад.

Вищезазначені обертання виконуються в будь-якому напрямку, що мінімізує кількість кроків; квадрат 2 розміру завжди вирішується за допомогою рухів вліво та вгору, незалежно від описаного вище.

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

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


Дуже дякую за вашу відповідь, Ніл! Але пам’ятайте, це не кодовий гольф. Замість довжини коду слід вказати найбільший розмір N матриці NxN, яку ви вирішили, і довжину списку рухів для вирішення цієї матриці.
Чарлі

@Charlie Ну, це 6, але тільки тому, що я був занадто ледачий, щоб вставити у більшу матрицю. Хоча це груба сила, вона лінійно масштабується з площею, тому має бути здатною до великих матриць.
Ніл

Насправді, найгірший випадок може бути квадратичним із площею.
Ніл

1
Посилання @Charlie TIO тепер вирішує випадкову матрицю 100x100.
Ніл

1
@Charlie Зараз я придумав основну оптимізацію для нижнього ряду, але, думаю, це остання зміна, яку я вноситиму до цієї відповіді.
Ніл
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.