Wikipedia angeboten sowohl eine beschissene Erklärung und einen nicht idealen Algorithmus. Aber arbeiten wir damit als Ausgangspunkt.
Zuerst nehmen wir den Backtracking-Algorithmus. Anstatt die Zellen der Matrix "in eine bestimmte Reihenfolge" zu bringen, gehen wir in die erste Reihe, dann alles in die zweite Reihe, dann alles in die dritte Reihe und so weiter. Das wird klar funktionieren.
Jetzt ändern wir den Backtracking-Algorithmus etwas. Anstatt Zelle für Zelle zu gehen, gehen wir Zeile für Zeile. Also machen wir eine Liste der n choose n/2
möglichen Zeilen, die halb 0 und halb 1 sind.Dann eine rekursive Funktion, die in etwa so aussieht:
def count_0_1_matrices(n, filled_rows=None):
if filled_rows is None:
filled_rows = []
if some_column_exceeds_threshold(n, filled_rows):
# Cannot have more than n/2 0s or 1s in any column
return 0
else:
answer = 0
for row in possible_rows(n):
answer = answer + count_0_1_matrices(n, filled_rows + [row])
return answer
Dies ist ein Backtracking-Algorithmus wie das, was wir vorher hatten. Wir machen nur ganze Reihen auf einmal, keine Zellen.
Aber beachten Sie, wir geben mehr Informationen weiter, als wir brauchen. Es besteht keine Notwendigkeit, die genaue Anordnung der Zeilen zu übergeben. Alles, was wir wissen müssen, ist, wie viele 1s in jeder verbleibenden Spalte benötigt werden. So können wir den Algorithmus so aussehen lassen:
def count_0_1_matrices(n, still_needed=None):
if still_needed is None:
still_needed = [int(n/2) for _ in range(n)]
# Did we overrun any column?
for i in still_needed:
if i < 0:
return 0
# Did we reach the end of our matrix?
if 0 == sum(still_needed):
return 1
# Calculate the answer by recursion.
answer = 0
for row in possible_rows(n):
next_still_needed = [still_needed[i] - row[i] for i in range(n)]
answer = answer + count_0_1_matrices(n, next_still_needed)
return answer
Diese Version ist fast die rekursive Funktion in der Wikipedia-Version. Der Hauptunterschied ist, dass unser Basisfall ist, dass wir nach jeder Zeile nichts brauchen, während Wikipedia uns den Basisfall aufschreiben lässt, um die letzte Zeile nach jedem anderen zu überprüfen.
Um von diesem zu einem Top-Down-DP zu gelangen, müssen Sie nur die Funktion notieren. Was in Python Sie tun können, indem Sie einen @memoize
Dekorateur definieren. Wie folgt:
Aber denken Sie daran, dass ich den Wikipedia-Algorithmus kritisiert habe? Lasst uns anfangen, es zu verbessern! Die erste große Verbesserung ist das. Bemerken Sie, dass die Reihenfolge der Elemente von still_needed
nicht wichtig sein kann, nur ihre Werte? Wenn Sie also nur die Elemente sortieren, können Sie die Berechnung nicht für jede Permutation separat durchführen. (Es kann eine Menge von Permutationen sein!)
@memoize
def count_0_1_matrices(n, still_needed=None):
if still_needed is None:
still_needed = [int(n/2) for _ in range(n)]
# Did we overrun any column?
for i in still_needed:
if i < 0:
return 0
# Did we reach the end of our matrix?
if 0 == sum(still_needed):
return 1
# Calculate the answer by recursion.
answer = 0
for row in possible_rows(n):
next_still_needed = [still_needed[i] - row[i] for i in range(n)]
answer = answer + count_0_1_matrices(n, sorted(next_still_needed))
return answer
Der kleinen harmlos sorted
nicht wichtig sieht, aber es spart eine Menge Arbeit! Und jetzt, wo wir wissen, dass still_needed
immer sortiert ist, können wir unsere Prüfungen vereinfachen, ob wir fertig sind und ob etwas negativ wurde. Außerdem können wir eine einfache Überprüfung hinzufügen, um den Fall herauszufiltern, in dem wir zu viele 0 in einer Spalte haben.
@memoize
def count_0_1_matrices(n, still_needed=None):
if still_needed is None:
still_needed = [int(n/2) for _ in range(n)]
# Did we overrun any column?
if still_needed[-1] < 0:
return 0
total = sum(still_needed)
if 0 == total:
# We reached the end of our matrix.
return 1
elif total*2/n < still_needed[0]:
# We have total*2/n rows left, but won't get enough 1s for a
# column.
return 0
# Calculate the answer by recursion.
answer = 0
for row in possible_rows(n):
next_still_needed = [still_needed[i] - row[i] for i in range(n)]
answer = answer + count_0_1_matrices(n, sorted(next_still_needed))
return answer
Und Sie possible_rows
implementieren, vorausgesetzt, sollte diese beide arbeiten und als wesentlich effizienter sein, was Wikipedia angeboten.
=====
ist die komplette Arbeits Umsetzung. Auf meiner Maschine berechnete sie die 6. Laufzeit in weniger als 4 Sekunden.
#! /usr/bin/env python
from sys import argv
from functools import wraps
def memoize(func):
cache = {}
@wraps(func)
def wrap(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrap
@memoize
def count_0_1_matrices(n, still_needed=None):
if 0 == n:
return 1
if still_needed is None:
still_needed = [int(n/2) for _ in range(n)]
# Did we overrun any column?
if still_needed[0] < 0:
return 0
total = sum(still_needed)
if 0 == total:
# We reached the end of our matrix.
return 1
elif total*2/n < still_needed[-1]:
# We have total*2/n rows left, but won't get enough 1s for a
# column.
return 0
# Calculate the answer by recursion.
answer = 0
for row in possible_rows(n):
next_still_needed = [still_needed[i] - row[i] for i in range(n)]
answer = answer + count_0_1_matrices(n, tuple(sorted(next_still_needed)))
return answer
@memoize
def possible_rows(n):
return [row for row in _possible_rows(n, n/2)]
def _possible_rows(n, k):
if 0 == n:
yield tuple()
else:
if k < n:
for row in _possible_rows(n-1, k):
yield tuple(row + (0,))
if 0 < k:
for row in _possible_rows(n-1, k-1):
yield tuple(row + (1,))
n = 2
if 1 < len(argv):
n = int(argv[1])
print(count_0_1_matrices(2*n)))
Danke für Ihre Antwort. Ok, ich sehe die Logik besteht darin, den Vektor der Paare für jede Zeile zu notieren. Bedeutet dies jedes Mal, dass die Zustände aus den Vektorpaaren erzeugt werden ?. Könntest du bitte den Wikipedia-Artikel auf eine andere Art erklären, ich konnte wirklich nichts in dieser Erklärung nach der ersten Zeile verstehen. Oder können wir die Backtrack-Methode verwenden, die von @btilly vorgeschlagen wird, und sie so ändern, dass jede Zeile anstelle von Zellen rekursiv aufgerufen wird. Hier ist meine Implementierung mit Backtrack [link] (https://github.com/ruthra-kumar/Sieve-of-Eratosthenes---C--/blob/master/Balanced_0-1_matrix_Backtrack.cpp) –
@Ruthrakumar Ja, jeder Der Zustand, der generiert wird, muss mit dem Hash verglichen werden, um zu vermeiden, dass die verbleibende Rekursion für ihn wiederholt wird. Ich habe ein bisschen mehr in der Antwort ausgearbeitet. Bitte lassen Sie mich wissen, wenn ich etwas weiter klären kann. –