-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhyper_karger.py
202 lines (137 loc) · 5.16 KB
/
hyper_karger.py
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#! /usr/bin/python
import random
from math import sqrt
from math import floor
from math import ceil
from math import log
SHOW_CUTS = False
def get_input():
raw_nm = input()
n, m = raw_nm.split(' ')
m = int(m)
n = int(n)
hyperedges = []
for _ in range(m):
raw_elements = input()
endpoints = raw_elements.split(' ')
endpoints = [int(x) for x in endpoints]
hyperedges.append(endpoints)
return n, hyperedges
def replace_endpoints(hyperedge, e, history):
new_vertice = hyperedge[0]
new_edge = []
new_history = history
## replace the merged vertices by the new one
for i in range(3):
elt = e[i]
if elt in hyperedge:
new_edge.append(new_vertice)
## transfer history to new node
if elt != new_vertice:
for _, h in enumerate(new_history[elt-1]):
new_history[new_vertice-1].append(h)
new_history[elt-1] = []
else:
new_edge.append(elt)
return new_edge, new_history
def contract_hyperedge(hyperedge, graph, n, history):
contracted_graph = []
## If all the endpoints of the edge are different, 2 will disappear into the one remaining
## If only 2 different endpoints, 1 will disappear
new_n = n
new_history = [[i for i in group] for group in history]
if (hyperedge[0] != hyperedge[1]) and (hyperedge[1] != hyperedge[2]) and (hyperedge[0] != hyperedge[2]):
new_n -= 2
else:
new_n -= 1
for _, e in enumerate(graph):
contracted_edge, new_history = replace_endpoints(hyperedge, e, new_history)
if not (contracted_edge[0] == contracted_edge[1] == contracted_edge[2]): # ignore if it's a point (lööp)
contracted_graph.append(contracted_edge)
return new_n, contracted_graph, new_history
## This is not a magic number
current_min = 40000000
## each cell will contain [cut_size, [[hash0, [set0]], [hash1, set1], ...]]
## ordered by hash value for later comparison
## A cut is represented only once
## only cut with a <= cut size will be added
global_history = []
def new_min(x):
global current_min
current_min = min(x, current_min)
def insert_cut(cut_size, history):
global global_history
if cut_size > current_min:
return
new_min(cut_size)
## Prepare candidate
## Sort the sets inside
prep0 = [sorted(cut_set) for cut_set in history if len(cut_set) > 0]
if SHOW_CUTS:
## Compute hash for each set
prep1 = [[hash(tuple(cut_set)), cut_set] for cut_set in prep0]
## Sort sets according to hash value
prep2 = sorted(prep1, key=lambda k: k[0])
## Extract list of hash list of current cuts
current_hash_list = [[e[0] for e in cut[1]] for cut in global_history]
## Extract hash list for comparison
candidate_hash_list = [h[0] for h in prep2]
## Insert if it's anew cut
if candidate_hash_list not in current_hash_list:
global_history.append([cut_size, prep2])
else:
## Compute hash for each set
prep1 = [hash(tuple(cut_set)) for cut_set in prep0]
## Sort sets according to hash value
prep2 = sorted(prep1)
## Extract list of hash list of current cuts
current_hash_list = [x[1] for x in global_history]
## Insert if it's anew cut
if prep2 not in current_hash_list:
global_history.append([cut_size, prep2])
return
## Karger-Stein
def min_cut(n, graph, history):
if n <= 3:
cut_size = 0
if n == 2:
cut_size = len(graph)
insert_cut(cut_size, history)
return
## Edge cases when n == 3
## try one last contraction
to_contract = graph[random.randint(0, len(graph)-1)]
new_n, last_try, new_history = contract_hyperedge(to_contract, graph, n, history)
if new_n == 1: ## oopsi should not have contracted
cut_size = len(graph)
insert_cut(cut_size, history)
else:
cut_size = len(last_try)
insert_cut(cut_size, new_history)
return
precontracted = graph
new_n = n
## Deep copy needed
new_history = [[i for i in group] for group in history]
for _ in range(n - ceil(n/sqrt(2))):
to_contract = precontracted[random.randint(0, len(precontracted)-1)]
new_n, precontracted, new_history = contract_hyperedge(to_contract, precontracted, new_n, new_history)
for _ in range(2):
min_cut(new_n, precontracted, new_history)
return
def yeet():
n, graph = get_input()
## at first, each element represents itself
history = [[x] for x in range(1, n+1)]
for _ in range(4*ceil((log(n, 2)**2))):
min_cut(n, graph, history)
global global_history
global_history = [x[1] for x in global_history if x[0] == current_min]
print(current_min, len(global_history))
if SHOW_CUTS:
cuts = [[hashed_tuple[1] for hashed_tuple in complete] for complete in global_history]
for _, e in enumerate(cuts):
print()
print(e)
yeet()
## It was a magic number