Skip to content

Commit dd78ccd

Browse files
author
Hamid Gasmi
committed
Issue #148 1st version is added
1 parent 8b892d1 commit dd78ccd

File tree

1 file changed

+120
-41
lines changed

1 file changed

+120
-41
lines changed
Lines changed: 120 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,162 @@
11
# python3
22

3-
# Naive Solution:
4-
# Time Complexity: O(2^n)
5-
def isSatisfiable_naive():
6-
for mask in range(1<<n):
7-
result = [ (mask >> i) & 1 for i in range(n) ]
8-
formulaIsSatisfied = True
9-
for clause in clauses:
10-
clauseIsSatisfied = False
11-
if result[abs(clause[0]) - 1] == (clause[0] < 0):
12-
clauseIsSatisfied = True
13-
if result[abs(clause[1]) - 1] == (clause[1] < 0):
14-
clauseIsSatisfied = True
15-
if not clauseIsSatisfied:
16-
formulaIsSatisfied = False
17-
break
18-
if formulaIsSatisfied:
19-
return result
20-
return None
21-
22-
class ImplicationGraph:
3+
class Implication_graph:
234
def __init__(self, n, clauses):
24-
self.adjacency_list = [[] for _ in range(2 * n)]
25-
self.build_adjacency_list(n, clauses)
5+
self.sat_variables_count = n
6+
self.vertices_count = 2 * n
7+
self.build_adjacency_list(clauses)
268
self.build_reversed_adjacency_list()
279

28-
def sat_variables_count(self):
29-
len(self.adjacency_list) // 2
30-
31-
def vertices_count(self):
32-
len(self.adjacency_list)
33-
3410
# Each variable Xi in SAT formula, 2 vertices will be created
3511
# ... Vertice 1 corresponds to Xi and its number is Xi - 1
3612
# ... Vertice 2 corresponds to -Xi and its number is Xi - 1 + Variables count
3713
# E.g. 3 SAT variables: X1, X2, X3: 6 vertices will be created
3814
# Vertices 0 1 2 3 4 5
3915
# Variables X1 X2 X3 -X1 -X2 -X3
40-
def get_vertice_num(self, variable_num):
41-
return variable_num - 1 if variable_num > 0 else (-1) * variable_num - 1 + self.sat_variables_count()
16+
def vertice_num(self, variable_num):
17+
return variable_num - 1 if variable_num > 0 else (-1) * variable_num - 1 + self.sat_variables_count
4218

19+
def non_vertice_num(self, vertice_num):
20+
return vertice_num + self.sat_variables_count if vertice_num < self.sat_variables_count else vertice_num - self.sat_variables_count
21+
4322
# Time Complexity: O(|C|)
44-
def build_adjacency_list(self, n, clauses):
23+
def build_adjacency_list(self, clauses):
24+
25+
self.adjacency_list = [[] for _ in range(2 * self.sat_variables_count)]
4526

4627
for c in clauses:
4728
l = c[0]
48-
v_non_l = self.get_vertice_num((-1) * l)
49-
v_l = self.get_vertice_num(l)
29+
v_l = self.vertice_num(l)
30+
v_non_l = self.vertice_num((-1) * l)
5031
if len(c) == 1:
5132
# (l): to create an edge from -l to l: -l --> l
5233
self.adjacency_list[v_non_l].append(v_l)
5334

5435
else:
5536
k = c[1]
56-
v_k = self.get_vertice_num(k)
57-
v_non_k = self.get_vertice_num((-1) * k)
37+
v_k = self.vertice_num(k)
38+
v_non_k = self.vertice_num((-1) * k)
5839
# (l V k): to create 2 edges from !l to k and !k to l:
5940
# ... !l --> k
6041
# ... !k --> l
61-
self.adjacency_list[v_non_l].append(self.get_vertice_num(v_k))
62-
self.adjacency_list[v_non_k].append(self.get_vertice_num(v_l))
42+
self.adjacency_list[v_non_l].append(v_k)
43+
self.adjacency_list[v_non_k].append(v_l)
44+
45+
#for v in range(self.vertices_count:
46+
# print("Vertice V (" + str(v) + ") Adjacency list: ", self.adjacency_list[v])
6347

6448
# Time Complexity: O(|E|) = O(|Edges|) = O(2 |Clauses|) = O(|C|)
6549
def build_reversed_adjacency_list(self):
6650

67-
self.reversed_adjacency_list = [[] for _ in range(self.vertices_count())]
51+
self.reversed_adjacency_list = [[] for _ in range(self.vertices_count)]
6852

69-
for v in range(self.vertices_count()):
53+
for v in range(self.vertices_count):
7054
for a in self.adjacency_list[v]:
7155
self.reversed_adjacency_list[a].append(v)
56+
57+
#for v in range(self.vertices_count:
58+
# print("Vertice V (" + str(v) + ") Reversed Adjacency list: ", self.reversed_adjacency_list[v])
59+
60+
def explore(self, v, an_adjacency_list, preorder_visits, postorder_visits):
61+
62+
self.visited[v] = True
63+
if preorder_visits != None:
64+
preorder_visits.append(v)
65+
66+
for a in an_adjacency_list[v]:
67+
if not self.visited[a]:
68+
self.explore(a, an_adjacency_list, preorder_visits, postorder_visits)
69+
70+
if postorder_visits != None:
71+
postorder_visits.append(v)
72+
73+
def dfs(self, an_adjacency_list, dfs_preorder, dfs_postorder):
74+
self.visited = [False] * self.vertices_count
75+
76+
for v in range(self.vertices_count):
77+
if not self.visited[v]:
78+
self.explore(v, an_adjacency_list, dfs_preorder, dfs_postorder)
79+
80+
def stronly_connected_components(self):
81+
82+
dfs_postorder = []
83+
84+
# 1. Find postorder in Gr
85+
# ... Source vertices in Gr have the highest postorder
86+
# ... A source vertice in Gr is Sink vertice in G
87+
self.dfs(self.reversed_adjacency_list, None, dfs_postorder)
88+
print("dfs_postorder: ", dfs_postorder)
89+
90+
# 2. Find SCCs in G
91+
sccs = []
92+
self.visited = [False] * self.vertices_count
93+
for i in range(len(dfs_postorder) - 1, -1, -1):
94+
v = dfs_postorder[i]
95+
if not self.visited[v]:
96+
sccs.append([])
97+
self.explore(v, self.adjacency_list, None, sccs[len(sccs) - 1])
98+
99+
return sccs
100+
101+
def solve_2sat(self):
102+
103+
strongly_connected_components = self.stronly_connected_components()
104+
105+
# sccs is ordered in reversed topological order
106+
vertice_assignment = [-1] * self.vertices_count
107+
for scc in strongly_connected_components:
108+
for v in scc:
109+
if vertice_assignment[v] == -1:
110+
vertice_assignment[v] = 1
111+
112+
non_v = self.non_vertice_num(v)
113+
if vertice_assignment[non_v] == -1:
114+
vertice_assignment[non_v] = 0
115+
116+
if vertice_assignment[non_v] == 1:
117+
return None
118+
119+
print("sccs: ", strongly_connected_components)
120+
print("vertice_assignment: ", vertice_assignment)
121+
return vertice_assignment[:self.sat_variables_count]
122+
123+
124+
def isSatisfiable(n, clauses):
125+
126+
g = Implication_graph(n, clauses)
72127

128+
return g.solve_2sat()
129+
130+
# Naive Solution:
131+
# Time Complexity: O(2^n)
132+
def isSatisfiable_naive(n, clauses):
133+
for mask in range(1<<n):
134+
result = [ (mask >> i) & 1 for i in range(n) ]
135+
formulaIsSatisfied = True
136+
for clause in clauses:
137+
clauseIsSatisfied = False
138+
if result[abs(clause[0]) - 1] == (clause[0] < 0):
139+
clauseIsSatisfied = True
140+
if result[abs(clause[1]) - 1] == (clause[1] < 0):
141+
clauseIsSatisfied = True
142+
if not clauseIsSatisfied:
143+
formulaIsSatisfied = False
144+
break
145+
if formulaIsSatisfied:
146+
return result
147+
return None
148+
73149
if __name__ == "__main__":
74150

75151
n, m = map(int, input().split())
76152
clauses = [ list(map(int, input().split())) for i in range(m) ]
77153

78-
result = isSatisfiable_naive()
154+
#result = isSatisfiable_naive(n, clauses)
155+
result = isSatisfiable(n, clauses)
156+
79157
if result is None:
80158
print("UNSATISFIABLE")
81159
else:
82160
print("SATISFIABLE")
83-
print(" ".join(str(-i-1 if result[i] else i+1) for i in range(n)))
161+
print(" ".join(str(i + 1 if result[i] else -i-1) for i in range(n)))
162+
#print(" ".join(str(-i-1 if result[i] else i+1) for i in range(n)))

0 commit comments

Comments
 (0)