N-Queens เป็นความท้าทายในการเขียนโค้ดที่น่าสนใจ โดยคุณจะต้องวาง N Queens ไว้บน N * N board .
ดูเหมือนว่านี้:
ราชินีสามารถเคลื่อนที่ได้ทุกทิศทาง:
- แนวตั้ง
- แนวนอน
- แนวทแยง
วิธีแก้ปัญหา (มีได้หลายอย่าง) ต้อง วางควีนทั้งหมดไว้บนกระดาน &ราชินีทุกตัวต้องอยู่ห่างจากราชินีทุกตัว
ในบทความนี้ คุณจะได้เรียนรู้วิธีที่ฉันคิดวิธีแก้ปัญหา
แผน
เมื่อแก้ปัญหาเหล่านี้ จุดเริ่มต้นที่ดีคือการเขียนแผนเป็นภาษาอังกฤษง่ายๆ
วิธีนี้จะช่วยให้คุณเข้าใจอย่างชัดเจนว่าปัญหาคืออะไรและขั้นตอนในการแก้ไข
หากคุณมีปัญหาในการเขียนแผน โปรดเข้าใจปัญหา 100% .
แผนของฉันสำหรับโซลูชัน N-Queens:
- เริ่มต้น @ ตำแหน่ง 0,0
- ถ้าตำแหน่งถูกต้อง:ใส่ queen, Advance column (+ 1), set row to 0
- ตรวจสอบ บน ล่าง ซ้าย ขวา เส้นทแยงมุม
- อย่างอื่น:เลื่อนไป 1 ตาราง
- ขึ้นไป (แถว + 1) เว้นแต่ตำแหน่งปัจจุบัน ==n
- ย้อนรอยเมื่อไม่สามารถวางราชินีในคอลัมน์ปัจจุบันได้
- เอาราชินีองค์สุดท้ายออก
- กำหนดแถวและคอลัมน์เป็นตำแหน่งของควีนสุดท้ายด้วยแถว + 1
นี่เป็นเวอร์ชันที่สะอาดกว่าของสิ่งที่ฉันจดบันทึกไว้ในตอนแรก
ฉันเจาะลึกขั้นตอนที่ต้องใช้รายละเอียดเพิ่มเติมก่อนจึงจะสามารถใช้งานได้
ตอนนี้ :
แผนของคุณจะไม่สมบูรณ์แบบ (ของฉันไม่สมบูรณ์แบบ) แต่จะช่วยให้คุณทราบว่าควรดำเนินการอย่างไร
หากคุณไม่สามารถเขียนแผนงานที่มั่นคง การค้นหาวิธีแก้ปัญหาก็ไม่ใช่เรื่องผิด …
…เข้าใจว่าวิธีแก้ปัญหาทำงานอย่างไร จากนั้นเขียนของคุณเอง
ค้นหาการเคลื่อนไหวที่ถูกต้อง
ในการตรวจสอบว่าตำแหน่งนั้นถูกต้องหรือไม่ เราต้องดูจากหลายๆ ทิศทาง
แทนที่จะยุ่งกับบอร์ด 2 มิติ ฉันตัดสินใจมี ราชินีมากมาย ในกระดานที่มีตำแหน่งของตน
จากนั้นเราจะตรวจสอบราชินีแถวนี้กับตำแหน่งที่เราต้องการตรวจสอบ
ตัวอย่างเช่น สำหรับการตรวจสอบแถว:
def queen_in_row(row) @queens_in_board.find { |r, c| r == row } end
ค่านี้จะคืนค่าเป็นราชินีถ้ามีคนแถวนั้นหรือไม่มีเลย
คุณไม่จำเป็นต้องตรวจสอบว่าคอลัมน์ว่างหรือไม่เพราะ เราจะย้ายไปยังคอลัมน์ถัดไปหลังจากวางราชินี .
สำหรับเส้นทแยงมุม ต้องใช้ความพยายามเป็นพิเศษเนื่องจากมี 4 เส้น
นี่คือรหัสสำหรับเส้นทแยงมุมบนขวา:
def right_upper_diagonal_for(row, column, n) diagonals = [] until row == n || column == n diagonals << [row += 1, column += 1] end diagonals end
เส้นทแยงมุมอื่นๆ เหมือนกัน ความแตกต่างเพียงอย่างเดียวอยู่ในเงื่อนไขของลูป &ทิศทาง (แถว + 1 / แถว - 1)
ต้องใช้การทดลองและข้อผิดพลาดเล็กน้อยจึงจะถูกต้อง แต่ก็ไม่เป็นไร
สิ่งสำคัญคือต้องทดสอบวิธีการเหล่านี้แยกกันเพื่อให้แน่ใจว่าได้ผล . เมื่อคุณมีชุดวิธีการทำงานแล้ว คุณก็นำมารวมกันเพื่อสร้างโซลูชันที่สมบูรณ์ได้
นี่คือวิธีที่ดึงเส้นทแยงมุมทั้งหมดมารวมกันและตรวจสอบกับราชินีทุกตัวในกระดาน:
def queen_in_diagonal(row, column, n) diagonals = right_upper_diagonal_for(row, column, n) + left_upper_diagonal_for(row, column, n) + left_lower_diagonal_for(row, column, n) + right_lower_diagonal_for(row, column, n) diagonals.any? { |r, c| r == row && c == column } || diagonals.any? { |r, c| @queens_in_board.any? { |qr, qc| r == qr && c == qc } } end
วิธีการใช้ BackTracking
การแก้ปัญหาที่ไม่สำคัญเหล่านี้จะทำให้คุณต้องรู้ข้อมูลเชิงลึก เทคนิค หรืออัลกอริทึมที่สำคัญ
ในกรณีของ N-Queens เทคนิคคือการย้อนรอย .
การย้อนรอยเป็นการยกเลิกการกระทำบางอย่างก่อนหน้านี้ (เช่น การวางราชินีบนกระดาน) และลองอีกครั้งด้วยการกำหนดค่าอื่น
ฉันคิดว่านี่จะเป็นส่วนที่ยากที่สุด แต่กลับกลายเป็นว่าง่ายทีเดียว
เพื่อหาสิ่งนี้ ฉันใช้การจำลองเล็กน้อย
ฉันวาดกระดานและกล่องเพื่อประคับประคองราชินี :
จากนั้นฉันก็ย้ายกล่องไปรอบๆ กระดาน (ใช้เมาส์โดยตรง) เพื่อจำลองอัลกอริทึม
นี่คือรหัส:
while row >= n row = @queens_in_board[-1][0] + 1 column = @queens_in_board[-1][1] puts "Backtracking, deleted: #{@queens_in_board.pop}" end
คุณยังสามารถทำเช่นนี้สำหรับปัญหาอื่นๆ เมื่อคุณติดขัด วาดในโปรแกรมวาดภาพ หรือแม้แต่บนกระดาษและลองเล่นดู
นี่คือสาระสำคัญของวิธีการทำงาน:
- เราเดินขึ้นเรื่อยๆ หากเราไปถึงจุดสูงสุดของกระดาน แสดงว่าเราไม่สามารถใส่ราชินีในคอลัมน์นี้ได้
- เราย้อนรอยโดยกำหนดตำแหน่งปัจจุบันเป็นราชินีตัวสุดท้ายและนำออกจากกระดาน
- ถ้าเราไม่สามารถวางราชินีจากตำแหน่งนี้ เราจะย้อนรอยอีกครั้ง
+1 บนตำแหน่งแถวคือวิธีที่ เลื่อนราชินีตัวสุดท้ายเพื่อปรับตำแหน่ง &เปิดการกำหนดค่าบอร์ดใหม่
เมื่อรันโค้ดนี้สำหรับ n =4 (ไม่มีวิธีแก้ปัญหาสำหรับ n =2 &n =3):
"placing at 0 0" "placing at 2 1" Backtracking, deleted: [2, 1] "placing at 3 1" "placing at 1 2" Backtracking, deleted: [1, 2] Backtracking, deleted: [3, 1] Backtracking, deleted: [0, 0] "placing at 1 0" "placing at 3 1" "placing at 0 2" "placing at 2 3"
gif นี้เป็นตัวอย่างภาพของอัลกอริทึม:
โค้ดเต็ม
def solve_n_queens(n) @queens_in_board = [] row = 0 column = 0 until @queens_in_board.size == n if queen_in_row(row) || queen_in_diagonal(row, column, n) row += 1 while row >= n row = @queens_in_board[-1][0] + 1 column = @queens_in_board[-1][1] puts "Backtracking, deleted: #{@queens_in_board.pop}" end else place_queen(row, column) p "placing at #{row} #{column}" row = 0 column += 1 end end @queens_in_board end def queen_in_row(row) @queens_in_board.find { |r, c| r == row } end def queen_in_diagonal(row, column, n) diagonals = right_upper_diagonal_for(row, column, n) + left_upper_diagonal_for(row, column, n) + left_lower_diagonal_for(row, column, n) + right_lower_diagonal_for(row, column, n) diagonals.any? { |r, c| r == row && c == column } || diagonals.any? { |r, c| @queens_in_board.any? { |qr, qc| r == qr && c == qc } } end def top_row?(row, n) row == n end def place_queen(row, column) @queens_in_board << [row, column] end def right_upper_diagonal_for(row, column, n) diagonals = [] until row == n || column == n diagonals << [row += 1, column += 1] end diagonals end def left_upper_diagonal_for(row, column, n) diagonals = [] until row == n || column == 0 diagonals << [row += 1, column -= 1] end diagonals end def right_lower_diagonal_for(row, column, n) diagonals = [] until row == 0 || column == n diagonals << [row -= 1, column += 1] end diagonals end def left_lower_diagonal_for(row, column, n) diagonals = [] until row == 0 || column == 0 diagonals << [row -= 1, column -= 1] end diagonals end def print_board(n) board = Array.new(n) { Array.new(n) { "." } } @queens_in_board.each { |queen| board[queen[0]][queen[1]] = "Q" } board.map { |n| n.join("|") }.reverse end p solve_n_queens(4) p solve_n_queens(5) puts print_board(5)
เวอร์ชันเรียกซ้ำ
นี่เป็นเวอร์ชันทางเลือกที่ค้นหาวิธีแก้ปัญหาที่เป็นไปได้ทั้งหมด
def solve_n_queens(n, column = 0, queens_in_board = []) @queens_in_board = queens_in_board n.times do |row| unless queen_in_row(row) || queen_in_diagonal(row, column, n) place_queen(row, column) solve_n_queens(n, column + 1, @queens_in_board) remove_last_queen end end puts print_board(n) if @queens_in_board.size == n end
สิ่งเดียวที่เปลี่ยนแปลงคือ solve_n_queens
วิธีการ
เวอร์ชันนี้ใช้การเรียกซ้ำ (เมธอดที่เรียกตัวเอง) เพื่อสำรวจโซลูชันบางส่วนทั้งหมด
เมื่อพบวิธีแก้ปัญหาที่สมบูรณ์ เราจะพิมพ์โดยใช้ print_board
วิธีการ
สรุป
คุณได้เรียนรู้เกี่ยวกับความท้าทายในการเขียนโค้ดของ N-Queens และวิธีแก้ปัญหาใน Ruby แล้ว คุณยังได้เรียนรู้วิธีพัฒนาทักษะการแก้ปัญหาของคุณอีกด้วย
หากคุณชอบบทความนี้ โปรดแบ่งปันกับทุกคนที่ได้รับประโยชน์จากบทความนี้
ขอบคุณสำหรับการอ่าน!