forked from TheAlgorithms/Python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdancing_links.py
More file actions
165 lines (135 loc) · 4.47 KB
/
Copy pathdancing_links.py
File metadata and controls
165 lines (135 loc) · 4.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""
Implementation of the Dancing Links algorithm (Algorithm X) by Donald Knuth.
https://en.wikipedia.org/wiki/Knuth's_Algorithm_X
https://en.wikipedia.org/wiki/Dancing_links
>>> universe = [1, 2, 3, 4, 5, 6, 7]
>>> subsets = [
... [1, 4, 7],
... [1, 4],
... [4, 5, 7],
... [3, 5, 6],
... [2, 3, 6, 7],
... ]
>>> dlx = DancingLinks(universe, subsets)
>>> sols = dlx.solve()
>>> len(sols) == 0
True
"""
class DLXNode:
"""Represents a node in the Dancing Links structure."""
def __init__(self) -> None:
self.left = self.right = self.up = self.down = self
self.column = None
class ColumnNode(DLXNode):
"""Represents a column header node, keeping track of its column size."""
def __init__(self, name: str) -> None:
super().__init__()
self.name = name
self.size = 0
class DancingLinks:
"""Dancing Links structure for solving the Exact Cover problem."""
def __init__(self, universe: list[int], subsets: list[list[int]]) -> None:
self.header = ColumnNode("header")
self.columns = {}
self.solution = []
self.solutions = []
# Create column headers for each element in the universe
prev = self.header
for u in universe:
col = ColumnNode(u)
self.columns[u] = col
col.left, col.right = prev, self.header
prev.right = col
self.header.left = col
prev = col
# Add rows (subsets)
for subset in subsets:
first_node = None
for item in subset:
col = self.columns[item]
node = DLXNode()
node.column = col
# Insert node into column
node.down = col
node.up = col.up
col.up.down = node
col.up = node
col.size += 1
# Link nodes in the same row
if first_node is None:
first_node = node
else:
node.left = first_node.left
node.right = first_node
first_node.left.right = node
first_node.left = node
def _cover(self, col: ColumnNode) -> None:
"""Covers a column (removes it from the matrix)."""
col.right.left = col.left
col.left.right = col.right
row = col.down
while row != col:
node = row.right
while node != row:
node.down.up = node.up
node.up.down = node.down
node.column.size -= 1
node = node.right
row = row.down
def _uncover(self, col: ColumnNode):
"""Uncovers a column (reverses _cover)."""
row = col.up
while row != col:
node = row.left
while node != row:
node.column.size += 1
node.down.up = node
node.up.down = node
node = node.left
row = row.up
col.right.left = col
col.left.right = col
def _choose_column(self) -> ColumnNode:
"""Select the column with the smallest size (heuristic)."""
min_size = float("inf")
chosen = None
col = self.header.right
while col != self.header:
if col.size < min_size:
min_size = col.size
chosen = col
col = col.right
return chosen
def _search(self) -> None:
"""Recursive Algorithm X search."""
if self.header.right == self.header:
# All columns covered -> valid solution
self.solutions.append([node.column.name for node in self.solution])
return
col = self._choose_column()
if col is None:
return
self._cover(col)
row = col.down
while row != col:
self.solution.append(row)
node = row.right
while node != row:
self._cover(node.column)
node = node.right
self._search()
# Backtrack
self.solution.pop()
node = row.left
while node != row:
self._uncover(node.column)
node = node.left
row = row.down
self._uncover(col)
def solve(self) -> list:
"""Find all exact cover solutions."""
self._search()
return self.solutions
if __name__ == "__main__":
import doctest
doctest.testmod()