Diberikan satu set tumpukan NXP dengan N menjadi jumlah tumpukan, dan P menjadi kapasitas tumpukan, bagaimana saya bisa menghitung jumlah minimum swap yang diperlukan untuk berpindah dari beberapa simpul di lokasi A ke beberapa lokasi acak B? Saya merancang sebuah game, dan tujuan akhirnya adalah menyortir semua tumpukan agar warnanya sama.
# Let "-" represent blank spaces, and assume the stacks are
stacks = [
['R', 'R', 'R', 'R'],
['Y', 'Y', 'Y', 'Y'],
['G', 'G', 'G', 'G'],
['-', '-', '-', 'B'],
['-', 'B', 'B', 'B']
]
Jika saya ingin memasukkan "B" di tempat stacks[1][1]
seperti itu stacks[1] = ["-", "B", "Y", "Y"]
. Bagaimana saya bisa menentukan jumlah minimum gerakan yang diperlukan untuk melakukannya?
Saya telah melihat beberapa pendekatan, saya telah mencoba algoritma genetika yang menghasilkan semua kemungkinan pergerakan dari keadaan, memberi skor, dan kemudian melanjutkan jalur skor terbaik, saya juga mencoba menjalankan algoritma Djikstra untuk merintis masalah. . Tampaknya sangat sederhana, namun saya tidak tahu cara untuk menjalankannya selain dari waktu eksponensial. Apakah ada algoritma yang saya lewatkan yang dapat diterapkan di sini?
Edit
Saya telah menulis fungsi ini untuk menghitung jumlah minimum gerakan yang diperlukan: tumpukan: Daftar Daftar Karakter yang mewakili potongan-potongan dalam tumpukan, tumpukan [0] [0] adalah bagian atas tumpukan [0] stack_ind: Indeks dari tumpukan potongan yang akan ditambahkan ke needs_piece: Potongan yang harus ditambahkan ke tumpukan needs_index: Indeks di mana potongan harus ditempatkan
def calculate_min_moves(stacks, stack_ind, needs_piece, needs_index):
# Minimum moves needed to empty the stack that will receive the piece so that it can hold the piece
num_removals = 0
for s in stacks[stack_ind][:needs_index+1]:
if item != "-":
num_removals += 1
min_to_unlock = 1000
unlock_from = -1
for i, stack in enumerate(stacks):
if i != stack_ind:
for k, piece in enumerate(stack):
if piece == needs_piece:
if k < min_to_unlock:
min_to_unlock = k
unlock_from = i
num_free_spaces = 0
free_space_map = {}
for i, stack in enumerate(stacks):
if i != stack_ind and i != unlock_from:
c = stack.count("-")
num_free_spaces += c
free_space_map[i] = c
if num_removals + min_to_unlock <= num_free_spaces:
print("No shuffling needed, there's enough free space to move all the extra nodes out of the way")
else:
# HERE
print("case 2, things need shuffled")
Sunting: Test Case pada tumpukan:
stacks = [
['R', 'R', 'R', 'R'],
['Y', 'Y', 'Y', 'Y'],
['G', 'G', 'G', 'G'],
['-', '-', '-', 'B'],
['-', 'B', 'B', 'B']
]
Case 1: stacks[4][1] should be 'G'
Move 'B' from stacks[4][1] to stacks[3][2]
Move 'G' from stacks[2][0] to stacks[4][1]
num_removals = 0 # 'G' is directly accessible as the top of stack 2
min_to_unlock = 1 # stack 4 has 1 piece that needs removed
free_spaces = 3 # stack 3 has free spaces and no pieces need moved to or from it
moves = [[4, 3], [2, 4]]
min_moves = 2
# This is easy to calculate
Case 2: stacks[0][3] should be 'B'
Move 'B' from stacks[3][3] to stack[4][0]
Move 'R' from stacks[0][0] to stacks[3][3]
Move 'R' from stacks[0][1] to stacks[3][2]
Move 'R' from stacks[0][2] to stacks[3][1]
Move 'R' from stacks[0][3] to stacks[3][0]
Move 'B' from stacks[4][0] to stacks[0][3]
num_removals = 0 # 'B' is directly accessible
min_to_unlock = 4 # stack 0 has 4 pieces that need removed
free_spaces = 3 # If stack 3 and 4 were switched this would be 1
moves = [[3, 4], [0, 3], [0, 3], [0, 3], [0, 3], [4, 0]]
min_moves = 6
#This is hard to calculate
Implementasi kode yang sebenarnya bukan bagian yang sulit, itu menentukan cara mengimplementasikan algoritma yang memecahkan masalah yang saya perjuangkan.
Sesuai permintaan @ YonIif, saya telah membuat inti untuk masalah ini.
Ketika dijalankan, ini menghasilkan susunan acak tumpukan, dan memilih potongan acak yang perlu dimasukkan ke dalam tumpukan acak di lokasi acak.
Menjalankannya mencetak sesuatu dari format ini ke konsol.
All Stacks: [['-', '-', 'O', 'Y'], ['-', 'P', 'P', 'O'], ['-', 'P', 'O', 'Y'], ['Y', 'Y', 'O', 'P']]
Stack 0 is currently ['-', '-', 'O', 'Y']
Stack 0 should be ['-', '-', '-', 'P']
Pembaruan status
Saya sangat bertekad untuk menyelesaikan masalah ini entah bagaimana .
Perlu diingat bahwa ada cara untuk meminimalkan jumlah kasus, seperti yang @Hans Olsson disebutkan dalam komentar. Pendekatan terbaru saya untuk masalah ini, adalah mengembangkan seperangkat aturan yang mirip dengan yang disebutkan, dan mempekerjakan mereka dalam algoritma generasi.
Aturan seperti:
Jangan pernah membalikkan gerakan. Pergi dari 1-> 0 lalu 0-> 1 (Tidak masuk akal)
Jangan pernah memindahkan sepotong dua kali berturut-turut. Jangan Pindah dari 0 -> 1 lalu 1 -> 3
Diberikan beberapa perpindahan dari tumpukan [X] ke tumpukan [Y], kemudian beberapa perpindahan, kemudian perpindahan dari tumpukan [Y] ke tumpukan [Z], jika tumpukan [Z] berada dalam keadaan yang sama seperti ketika perpindahan dari tumpukan [X] ke tumpukan [Y] terjadi, suatu langkah bisa dihilangkan dengan pindah dari tumpukan [X] langsung ke tumpukan [Z]
Saat ini, saya mendekati masalah ini dengan upaya untuk membuat aturan yang cukup, sehingga meminimalkan jumlah gerakan "valid", cukup sehingga jawaban dapat dihitung menggunakan algoritma generasi. Jika ada yang bisa memikirkan aturan tambahan, saya akan tertarik mendengarnya di komentar.
Memperbarui
Berkat jawabannya oleh @RootTwo, saya sudah memiliki sedikit terobosan, yang akan saya uraikan di sini.
Ke terobosan
Tentukan tinggi gawang sebagai kedalaman potongan gawang harus ditempatkan di tumpukan tujuan.
Setiap kali bagian gawang ditempatkan pada indeks <= stack_height - tinggi gawang, akan selalu ada jalan terpendek menuju kemenangan melalui metode clear_path ().
Let S represent some solid Piece.
YAITU
Stacks = [ [R, R, G], [G, G, R], [-, -, -] ]
Goal = Stacks[0][2] = R
Goal Height = 2.
Stack Height - Goal Height = 0
Diberikan beberapa tumpukan sehingga stack[0] = R
, permainan dimenangkan.
GOAL
[ [ (S | -), (S | -), (S | -) ], [R, S, S], [(S | - ), (S | -), (S | -)] ]
Karena diketahui bahwa mereka selalu setidaknya ruang kosong stack_height tersedia, kasus terburuk yang mungkin terjadi adalah:
[ [ S, S, !Goal ], [R, S, S], [-, -, -]
Karena kita tahu bagian gawang tidak bisa berada di tujuan gawang atau permainan dimenangkan. Dalam hal ini jumlah gerakan minimum yang diperlukan adalah gerakan:
(0, 2), (0, 2), (0, 2), (1, 0)
Stacks = [ [R, G, G], [-, R, R], [-, -, G] ]
Goal = Stack[0][1] = R
Stack Height - Goal Height = 1
Diberikan beberapa tumpukan sehingga stack[1] = R
, permainan dimenangkan.
GOAL
[ [ (S | -), (S | -), S], [ (S | -), R, S], [(S | -), (S | -), (S | -)]
Kami tahu setidaknya ada 3 ruang kosong yang tersedia, jadi kasus terburuk yang mungkin terjadi adalah:
[ [ S, !Goal, S], [S, R, S], [ -, -, - ]
Dalam hal ini jumlah gerakan minimum adalah gerakan:
(1, 2), (0, 2), (0, 2), (1, 0)
Ini akan berlaku untuk semua kasus.
Dengan demikian, masalahnya telah direduksi menjadi masalah menemukan jumlah minimum gerakan yang diperlukan untuk menempatkan potongan gawang pada atau di atas pada ketinggian gawang.
Ini membagi masalah menjadi serangkaian sub-masalah:
Ketika tumpukan tujuan memiliki bagian yang dapat diakses! = Bagian tujuan, menentukan apakah ada lokasi yang valid untuk potongan itu, atau apakah potongan itu harus tetap di sana saat potongan lain ditukar.
Ketika tumpukan tujuan memiliki bagian yang dapat diakses == bagian tujuan, menentukan apakah itu dapat dihapus dan ditempatkan pada ketinggian tujuan yang diperlukan, atau jika potongan harus tetap sementara yang lain ditukar.
Ketika kedua kasus di atas membutuhkan bagian yang lain untuk ditukar, tentukan bagian mana yang akan ditukar untuk meningkatkan agar bagian tujuan dapat mencapai ketinggian tujuan.
Tumpukan tujuan harus selalu dievaluasi kasusnya terlebih dahulu.
YAITU
stacks = [ [-, R, G], [-, R, G], [-, R, G] ]
Goal = stacks[0][1] = G
Memeriksa Goal Stack pertama-tama mengarah ke:
(0, 1), (0, 2), (1, 0), (2, 0) = 4 Moves
Mengabaikan Sasaran Sasaran:
(1, 0), (1, 2), (0, 1), (0, 1), (2, 0) = 5 Moves
Jawaban:
Saya datang dengan dua opsi, tetapi tidak ada yang bisa menyelesaikan kasus 2 secara tepat waktu. Opsi pertama menggunakan A * dengan ukuran jarak string sebagai h (n), opsi kedua adalah IDA *. Saya menguji banyak ukuran kesamaan string, saya menggunakan smith-waterman pada pendekatan saya. Saya telah mengubah notasi Anda untuk menangani masalah lebih cepat. Saya telah menambahkan angka pada akhir setiap digit untuk memeriksa apakah sebuah keping dipindahkan dua kali.
Berikut adalah beberapa kasus yang telah saya uji:
Ini kode A *:
Inilah IDA * Code:
Pemakaian:
sumber
Dalam komentar Anda mengatakan ada N tumpukan dengan kapasitas P, dan selalu ada P ruang kosong. Jika itu masalahnya, sepertinya algoritma ini akan berfungsi pada
else
klausa dalam kode Anda (yaitu kapannum_removals + min_to_unlock > num_free_spaces
):sumber
Meskipun saya belum menemukan waktu untuk membuktikan ini secara matematis, saya memutuskan untuk tetap memposting ini; semoga membantu. Pendekatannya adalah menentukan parameter p yang berkurang dengan gerakan yang baik dan mencapai nol tepat saat game selesai. Dalam program ini hanya mempertimbangkan gerakan yang baik atau gerakan netral (yang membuat p tidak berubah) dan melupakan gerakan buruk (yang meningkatkan p).
Jadi apa itu p? Untuk setiap kolom, tentukan p sebagai jumlah blok yang masih harus dihapus sebelum semua warna dalam kolom itu adalah warna yang diinginkan. Jadi misalkan kita ingin blok merah berakhir di kolom paling kiri (saya akan kembali lagi nanti), dan misalkan ada satu blok merah di bagian bawah, lalu kuning di atasnya, satu blok lagi di atas itu, dan kemudian ruang kosong. Kemudian p = 2 untuk kolom ini (dua blok untuk dihapus sebelum semuanya berwarna merah). Hitung p untuk semua kolom. Untuk kolom yang seharusnya berakhir kosong, p sama dengan jumlah blok yang ada di dalamnya (semuanya harus pergi). P untuk kondisi saat ini adalah jumlah dari semua p untuk semua kolom.
Ketika p = 0, semua kolom memiliki warna yang sama dan satu kolom kosong, sehingga permainan telah selesai.
Dengan memilih gerakan yang mengurangi p (atau setidaknya tidak menambah p) kita bergerak ke arah yang benar, ini menurut saya perbedaan penting dengan algoritma jalur terpendek: Dijkstra tidak tahu apakah dia bergerak ke arah yang benar dengan setiap vertex yang dia selidiki.
Jadi bagaimana kita menentukan di mana masing-masing warna harus berakhir? Pada dasarnya dengan menentukan p untuk setiap kemungkinan. Jadi misalnya mulai dengan merah / kuning / hijau / kosong, hitung p, lalu pergi ke merah / kuning / kosong / hijau, hitung p, dll. Ambil posisi awal dengan p terendah. Ini membutuhkan n! perhitungan. Untuk n = 8 ini adalah 40320, yang bisa dilakukan. Berita buruknya adalah Anda harus memeriksa semua posisi awal dengan p terendah yang sama. Berita baiknya adalah Anda bisa melupakan sisanya.
Ada dua ketidakpastian matematis di sini. Satu: apakah mungkin ada jalan yang lebih pendek yang menggunakan langkah buruk? Tampaknya tidak mungkin, saya belum menemukan contoh tandingan, tapi saya juga belum menemukan bukti. Dua: apakah mungkin ketika memulai dengan posisi awal yang tidak optimal (Yaitu bukan p terendah) akan ada jalan yang lebih pendek daripada dengan semua posisi awal yang optimal. Sekali lagi: tidak ada contoh balik tetapi juga tidak ada bukti.
Beberapa saran implementasi. Melacak p selama eksekusi untuk setiap kolom tidak sulit tetapi tentu saja harus dilakukan. Parameter lain yang harus disimpan untuk setiap kolom adalah jumlah tempat terbuka. Jika 0, maka kolom ini untuk sementara waktu tidak dapat menerima blok apa pun, sehingga dapat diabaikan. Ketika p = 0 untuk kolom, itu tidak memenuhi syarat untuk pop. Untuk setiap kemungkinan pop, periksa apakah ada gerakan yang baik, yaitu yang mengurangi keseluruhan p. Jika ada banyak, periksa semua. Jika tidak ada, pertimbangkan semua gerakan netral.
Semua ini akan sangat mengurangi waktu perhitungan Anda.
sumber