N-Queens II
يهاجم الوزير كل مربع في صفه وعموده وقطريه كليهما، مهما كانت المسافة. معطى عدد صحيح n، أعد عدد الطرق لوضع n من الوزير على لوحة n × n بحيث لا يهاجم أي وزيرين أحدهما الآخر.
تُحسب اللوحات التي تختلف في أي مربع على حدة، بما في ذلك الصور المرآتية.
الدالة
- ninteger
- حجم اللوحة وعدد الملكات
- تُرجعinteger
- عدد الطرق لوضع الملكات بحيث لا تهاجم أيٌّ منها الأخرى
القيود
1 ≤ n ≤ 12- الإجابة عن
n = 12هي 14,200، لذا فهي تناسب عددًا صحيحًا ذا 32 بت.
أمثلة
- المدخلات
- n = 4
- المخرجات
- 2
- الشرح
- بكتابة عمود الملكة في كل صف من الأعلى إلى الأسفل، يكون التمثيلان هما
1, 3, 0, 2و2, 0, 3, 1: صورتان متطابقتان في المرآة، وتُحسبان حلّين. كل اختيار آخر يضع ملكتين في العمود نفسه أو على القطر نفسه.
- المدخلات
- n = 3
- المخرجات
- 0
- الشرح
- يترك وضع ملكة في أحد الركنين العلويين مربعًا آمنًا واحدًا في الصف الأوسط، ثم لا يبقى أي مربع آمن في الصف السفلي. تهاجم الملكة في منتصف الصف العلوي الصف الأوسط بأكمله.
سؤال إضافي
هل يمكنك عدّ اللوحات المختلفة فقط بعد تدوير اللوحة وعكسها؟ عندما تكون n = 8، تنقسم اللوحات الـ92 إلى 12 مجموعة من هذا النوع.
تلميحات
افتحها واحدًا تلو الآخر. كل تلميح يكشف أكثر قليلًا.
يهاجم الوزيران بعضهما بعضًا إذا كانا في الصف نفسه، لذا يحتوي كل صف على وزير واحد بالضبط. ما الذي يبقى لاختياره بعد أن تعرف ذلك؟
املأ اللوحة صفًا تلو الآخر، بدءًا من الأعلى. ما إن تصبح الملكة الجديدة مهددة، فتخلَّ عن اللوحة الجزئية هذه، إذ لا شيء مما تضيفه في الأسفل يمكنه إصلاحها. لاختبار مربع دون النظر إلى اللوحة كلها، تذكّر الأعمدة والأقطار التي توجد فيها ملكة بالفعل. في أحد اتجاهَي القطر، تكون قيمة
row + colمتساوية في كل مربع، وفي الاتجاه الآخر تكون قيمةrow - colمتساوية.اكتب
place(row)، التي تُرجع عدد اللوحات الكاملة التي يمكن إتمامها انطلاقًا من هنا. تُرجع 1 عندما يكونrow == n. وإلا، فإنها تجرّب كل عمودcيكون فيه العمود والقطرrow + cوالقطرrow - cجميعها خالية: علِّم المواضع الثلاثة، وأضفplace(row + 1)إلى المجموع الجاري، ثم أزل العلامات عنها. الإجابة هيplace(0).
الحل
يتسبب وجود ملكتين في صف واحد دائمًا في الهجوم، لذا تتكوّن اللوحة من عمود واحد لكل صف: n^n قائمة، أي نحو 8.9 × 10^12 عندما تكون n = 12. بدلًا من ذلك، أنشئها صفًا تلو الآخر، وتخلّص من أي لوحة جزئية فور تعرّض ملكة للهجوم، وحدّد الأعمدة والقطرين المشغولين، بحيث لا يتطلب اختبار مربع سوى ثلاث عمليات بحث.
جرّب كل موضع مع ملكة واحدة في كل صف
صحيحة، لكنها لا تنتهي في أكبر الاختبارات
الفكرة
اكتب الترتيب على هيئة قائمة cols، حيث يمثّل كل عنصر فيها عمود الملكة في صفّ، ثم مرّ على جميع القوائم n^n كما يدور عدّاد المسافة. تهاجم الملكات في الصفوف i < j بعضها بعضًا عندما يكون cols[i] == cols[j] أو |cols[i] - cols[j]| == j - i، لأن الحركة قطريًا تعني الانتقال عمودًا واحدًا في كل صف. تُفحَص كل قائمة مرة واحدة، لذلك لا تُفوَّت أي رقعة ولا تُعَدّ مرتين.
لا شيء يوقف التنفيذ مبكرًا: حتى إن حدث تصادم في أول صفّين، تبقى هناك n^(n-2) قائمة. عند n = 8، يعني ذلك 16,777,216 قائمة للوصول إلى 92 رقعة؛ وعند n = 12، 8.9 × 10^12 قائمة، أي نحو 2.5 ساعة إذا استغرق فحص كل قائمة نانوثانية واحدة.
الخوارزمية
- ابدأ بـ
colsوكل قيمها أصفار. - أضف 1 إلى العدد إذا لم يوجد أي زوج من الصفوف
i < jبحيث يكونcols[i] == cols[j]أو|cols[i] - cols[j]| == j - i. - تقدّم كما في عدّاد المسافة: بدءًا من الصف الأخير صعودًا، أعد ضبط كل مدخل قيمته
n-1إلى 0، ثم أضف 1 إلى أول مدخل تقل قيمته عنn-1. - توقّف وأعِد العدد عندما تكون قيمة كل مدخل
n-1.
def totalNQueens(n):
def is_valid(cols):
# cols[r] is the column of the queen in row r, so rows never clash.
for i in range(n):
for j in range(i + 1, n):
if cols[i] == cols[j] or abs(cols[i] - cols[j]) == j - i:
return False
return True
cols = [0] * n
count = 0
while True:
if is_valid(cols):
count += 1
# Move to the next placement, like an odometer with n digits in base n.
row = n - 1
while row >= 0 and cols[row] == n - 1:
cols[row] = 0
row -= 1
if row < 0:
return count
cols[row] += 1التراجع باستخدام مجموعات الأعمدة والأقطار
الفكرة
ضع الملكات صفًا تلو الآخر واختبر كل واحدة عند وضعها. لا يمكن لملء الصفوف أدناه أن ينقذ ملكةً مُهدَّدة، لذا تخطَّ ذلك المربع. تنتقل الملكة الآمنة إلى الصف التالي عبر استدعاء递ي، وتُزال عند عودة الاستدعاء؛ والوصول إلى الصف n يُحتسب لوحةً واحدة.
لا يمكن مهاجمة المربع (row, c) إلا عبر أحد ثلاثة خطوط: عموده، وقطره / حيث تكون row + c ثابتة، وقطره \ حيث تكون row - c ثابتة (أضف n-1 لفهرسته بدءًا من 0). تحوّل مصفوفات الأعلام cols وdiag وanti اختبار الأمان إلى ثلاث عمليات بحث. يحتوي كل خط على ملكة واحدة كحد أقصى، لذا فإن مسح الأعلام عند العودة يعيد المصفوفات كما كانت تمامًا.
يوفر الصف k ما يصل إلى n - k أعمدة، لذا فإن البحث محدود بـ O(n!)، مع استخدام مساحة O(n).
الخوارزمية
- أنشئ مصفوفات الأعلام
colsبالحجمn، وdiagوantiبالحجم2n-1، واجعلها جميعًا مطفأة. place(row): إذا كانrow == n، فأعِد 1.- لكل عمود
c، تخطَّه إذا كانcols[c]أوdiag[row + c]أوanti[row - c + n - 1]مشغّلًا. - وإلا، شغّل الأعلام الثلاثة، وأضف
place(row + 1)إلى المجموع، ثم أطفئها. - أعِد المجموع. الإجابة هي
place(0).
def totalNQueens(n):
cols = [False] * n # cols[c]: column c holds a queen
diag = [False] * (2 * n - 1) # diag[r + c]: that / diagonal holds a queen
anti = [False] * (2 * n - 1) # anti[r - c + n - 1]: that \ diagonal holds a queen
def place(row):
if row == n:
return 1 # a queen in every row: one complete board
count = 0
for c in range(n):
if cols[c] or diag[row + c] or anti[row - c + n - 1]:
continue # attacked: three lookups, no scan of the board
cols[c] = diag[row + c] = anti[row - c + n - 1] = True
count += place(row + 1)
cols[c] = diag[row + c] = anti[row - c + n - 1] = False # take it back
return count
return place(0)التراجع باستخدام أقنعة البتات
الفكرة
يختبر البحث باستخدام الأعلام جميع الأعمدة n في كل صف؛ أما أقنعة البتات فتنتقل مباشرةً إلى الأعمدة الخالية. البت c هو العمود c من الصف الجاري ملؤه: يحتوي cols على الأعمدة المشغولة، بينما يحتوي left وright على المربعات التي يهددها كل قطر. ثم free = ~(cols | left | right) & full، حيث full هي أقل n بتات، وfree & -free هو أدنى مربع خالٍ. في الصف التالي، يظل العمود مشغولًا، وينتقل التهديد على كل قطر عمودًا واحدًا.
عندما تكون n = 4 وتكون الملكة الأولى عند bit = 0010، يحصل الصف 1 على free = 1000: العمود 3، الذي عُثر عليه من دون اختبار الأعمدة الأخرى. عندما تكون n = 12، تحلّ 856,188 خطوة تكرار محل 10,103,868 اختبارًا للمربعات. ينشئ كود R صفًا كاملًا من اللوحات الجزئية دفعةً واحدة بدلًا من الاستدعاء التكراري.
الخوارزمية
- عيّن
full = (1 << n) - 1. count(cols, left, right): إذا كانcols == full، فأعد 1.- احسب
free = ~(cols | left | right) & full. - ما دام
freeلا يساوي 0، خذbit = free & -free، وأزِله منfree، وأضِفcount(cols | bit, ((left | bit) << 1) & full, (right | bit) >> 1)إلى المجموع. - أعِد المجموع. الإجابة هي
count(0, 0, 0).
def totalNQueens(n):
full = (1 << n) - 1 # bit c stands for column c of the current row
def count(cols, left, right):
# cols: columns taken. left, right: squares of this row hit along a diagonal.
if cols == full:
return 1 # every column used: n queens placed
total = 0
free = full & ~(cols | left | right)
while free:
bit = free & -free # the lowest free square
free -= bit
# Moving down a row shifts each diagonal attack one column over.
total += count(cols | bit, ((left | bit) << 1) & full, (right | bit) >> 1)
return total
return count(0, 0, 0)
أخطاء شائعة وحالات حدّية
- فهرسة
antiباستخدامrow - cمن دون إضافةn-1. يرمي Java استثناءً، ويقرأ C خارج المصفوفة، بينما تقرأanti[-2]في Python بهدوء عَلَمًا يخص قطرًا آخر. - تحديد حجم مصفوفات الأقطار ليكون
nبدلًا من2n-1. تصلrow + cإلى2n-2. - عدم تصفير الأعلام بعد الاستدعاء التكراري. ترى الفروع اللاحقة ملكات لم تعد موجودة، فينخفض العدد.
- إغفال
& fullمنfree. تضبط~xالبتات الواقعة فوق العمودn-1، فتختار الحلقة مربعات خارج اللوحة؛ وفي Python أو Ruby تصبحfreeسالبة ولا تنتهي الحلقة أبدًا.
أسئلة شائعة3
ما هو التعقيد الزمني لمسألة الملكات الثماني II؟
يُحدَّد البحث بالتراجع بحد أعلى قدره O(n!): يحتوي الصف الأول على n خيارات، والصف التالي على n-1 خيارًا على الأكثر، وهكذا. يقلّل التقليم العمل الفعلي كثيرًا، إلى 856,189 لوحة جزئية عند n = 12. المساحة هي O(n).
ما الفرق بين مسألة الملكات N والملكات N II؟
تعيد N-Queens جميع اللوحات، مرسومةً على هيئة صفوف من النص؛ أما N-Queens II فتعيد عددها فقط. البحث هو نفسه، لكن العدّ لا يخزّن أي لوحات، وهذا ما يناسب إصدار قناع البتات.
هل يمكنك استخدام التناظر لتسريع حل مسألة الملكات N-Queens II؟
يعطي انعكاس اللوحة من اليسار إلى اليمين لوحةً صالحة أخرى، لذا احسب اللوحات التي يكون فيها الوزير الأول في الأعمدة من 0 إلى n/2 - 1 وضاعف العدد. إذا كان n فرديًا، فأضف مرة واحدة اللوحات التي يكون فيها الوزير الأول في العمود الأوسط.
مسائل مشابهة
مسائل تعتمد على الأفكار نفسها. حلّ اثنتين أو ثلاث منها يثبّت النمط.
def totalNQueens(n):
# اكتب الكود هنااختبار #1
اختبار #2
وسيط
n = 4
الإخراج المتوقع
2