Skip to content

Commit f014a53

Browse files
dcoudertdimpase
authored andcommitted
improve find_hamiltonian
1 parent 543f8d6 commit f014a53

File tree

1 file changed

+95
-42
lines changed

1 file changed

+95
-42
lines changed

src/sage/graphs/generic_graph_pyx.pyx

Lines changed: 95 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ from sage.libs.gmp.mpz cimport *
3535
from sage.misc.prandom import random
3636
from sage.graphs.base.static_sparse_graph cimport short_digraph
3737
from sage.graphs.base.static_sparse_graph cimport init_short_digraph
38+
from sage.graphs.base.static_sparse_graph cimport init_reverse
3839
from sage.graphs.base.static_sparse_graph cimport free_short_digraph
3940
from sage.graphs.base.static_sparse_graph cimport out_degree, has_edge
4041

@@ -1237,11 +1238,34 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
12371238
Finally, an example on a graph which does not have a Hamiltonian
12381239
path::
12391240
1240-
sage: G=graphs.HyperStarGraph(5,2)
1241+
sage: G = graphs.HyperStarGraph(5, 2)
12411242
sage: fh(G,find_path=False)
1242-
(False, ['00110', '10100', '01100', '11000', '01010', '10010', '00011', '10001', '00101'])
1243+
(False, ['01100', '10100', '00110', '10010', '00011', '10001', '01001', '11000', '01010'])
12431244
sage: fh(G,find_path=True)
1244-
(False, ['01001', '10001', '00101', '10100', '00110', '10010', '01010', '11000', '01100'])
1245+
(False, ['00110', '10010', '01010', '11000', '01100', '10100', '00101', '10001', '00011'])
1246+
1247+
The method can also be used for directed graphs::
1248+
1249+
sage: G = DiGraph()
1250+
sage: G.add_path('abcd')
1251+
sage: G.add_path('xyd')
1252+
sage: fh(G)
1253+
(False, ['a', 'b', 'c', 'd'])
1254+
sage: G = G.reverse()
1255+
sage: fh(G)
1256+
(False, ['d', 'c', 'b', 'a'])
1257+
sage: H = DiGraph()
1258+
sage: H.add_cycle('abcdefgh')
1259+
sage: fh(H)
1260+
(True, ['b', 'c', 'd', 'e', 'f', 'g', 'h', 'a'])
1261+
sage: H.delete_edge('ab')
1262+
sage: fh(H, find_path=False)
1263+
(False, ['b', 'c', 'd', 'e', 'f', 'g', 'h', 'a'])
1264+
sage: fh(H, find_path=True)
1265+
(True, ['b', 'c', 'd', 'e', 'f', 'g', 'h', 'a'])
1266+
sage: H.delete_edge('gh')
1267+
sage: fh(H, find_path=True)
1268+
(False, ['b', 'c', 'd', 'e', 'f', 'g'])
12451269
12461270
TESTS:
12471271
@@ -1288,31 +1312,32 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
12881312
(False, [0, 1, 2, 3])
12891313
sage: fh(G, find_path=True)
12901314
(False, [0, 1, 2, 3])
1291-
12921315
"""
1316+
G._scream_if_not_simple()
1317+
12931318
from sage.misc.prandom import randint
12941319
cdef int n = G.order()
12951320

12961321
# Easy cases
1297-
if not n:
1298-
return False, []
1299-
if n == 1:
1300-
return False, G.vertices(sort=False)
1322+
if n < 2:
1323+
return False, list(G)
13011324

13021325
# To clean the output when find_path is None or a number
13031326
find_path = (find_path > 0)
13041327

13051328
if G.is_clique(induced=False):
13061329
# We have an hamiltonian path since n >= 2, but we have an hamiltonian
13071330
# cycle only if n >= 3
1308-
return find_path or n >= 3, G.vertices(sort=True)
1331+
return find_path or n >= 3, list(G)
13091332

13101333
cdef list best_path, p
13111334
if not G.is_connected():
13121335
# The (Di)Graph has no hamiltonian path or cycle. We search for the
13131336
# longest path in its connected components.
13141337
best_path = []
13151338
for H in G.connected_components_subgraphs():
1339+
if H.order() <= len(best_path):
1340+
continue
13161341
_, p = find_hamiltonian(H, max_iter=max_iter, reset_bound=reset_bound,
13171342
backtrack_bound=backtrack_bound, find_path=True)
13181343
if len(p) > len(best_path):
@@ -1321,20 +1346,24 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
13211346

13221347
# Misc variables used below
13231348
cdef int i, j
1324-
cdef int n_available
1349+
cdef bint directed = G.is_directed()
13251350

13261351
# Initialize the path.
13271352
cdef MemoryAllocator mem = MemoryAllocator()
13281353
cdef int *path = <int *>mem.allocarray(n, sizeof(int))
1329-
memset(path, -1, n * sizeof(int))
13301354

13311355
# Initialize the membership array
13321356
cdef bint *member = <bint *>mem.allocarray(n, sizeof(int))
13331357
memset(member, 0, n * sizeof(int))
13341358

13351359
# static copy of the graph for more efficient operations
1360+
cdef list int_to_vertex = list(G)
13361361
cdef short_digraph sd
1337-
init_short_digraph(sd, G)
1362+
init_short_digraph(sd, G, edge_labelled=False, vertex_list=int_to_vertex)
1363+
cdef short_digraph rev_sd
1364+
cdef bint reverse = False
1365+
if directed:
1366+
init_reverse(rev_sd, sd)
13381367

13391368
# A list to store the available vertices at each step
13401369
cdef list available_vertices = []
@@ -1344,7 +1373,7 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
13441373
cdef int u = randint(0, n - 1)
13451374
while not out_degree(sd, u):
13461375
u = randint(0, n - 1)
1347-
# Then we pick at random a neighbor of u
1376+
# Then we pick at random a neighbor of u
13481377
cdef int x = randint(0, out_degree(sd, u) - 1)
13491378
cdef int v = sd.neighbors[u][x]
13501379
# This will be the first edge in the path
@@ -1362,34 +1391,35 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
13621391

13631392
# Initialize a path to contain the longest path
13641393
cdef int *longest_path = <int *>mem.allocarray(n, sizeof(int))
1365-
memset(longest_path, -1, n * sizeof(int))
13661394
for i in range(length):
13671395
longest_path[i] = path[i]
13681396

1369-
# Initialize a temporary path for flipping
1370-
cdef int *temp_path = <int *>mem.allocarray(n, sizeof(int))
1371-
memset(temp_path, -1, n * sizeof(int))
1372-
13731397
cdef bint longer = False
1374-
cdef bint good = True
1398+
cdef bint longest_reversed = False
13751399
cdef bint flag
13761400

13771401
while not done:
13781402
counter = counter + 1
13791403
if counter % 10 == 0:
13801404
# Reverse the path
1381-
13821405
for i in range(length//2):
13831406
t = path[i]
13841407
path[i] = path[length - i - 1]
13851408
path[length - i - 1] = t
13861409

1410+
if directed:
1411+
# We now work on the reverse graph
1412+
reverse = not reverse
1413+
13871414
if counter > reset_bound:
13881415
bigcount = bigcount + 1
13891416
counter = 1
13901417

13911418
# Time to reset the procedure
13921419
memset(member, 0, n * sizeof(int))
1420+
if directed and reverse:
1421+
# We restore the original orientation
1422+
reverse = False
13931423

13941424
# First we pick a random vertex u of (out-)degree at least one
13951425
u = randint(0, n - 1)
@@ -1405,37 +1435,44 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
14051435
member[u] = True
14061436
member[v] = True
14071437

1408-
if counter % backtrack_bound == 0:
1438+
if length > 5 and counter % backtrack_bound == 0:
14091439
for i in range(5):
14101440
member[path[length - i - 1]] = False
14111441
length = length - 5
14121442
longer = False
14131443

1444+
# We search for a possible extension of the path
14141445
available_vertices = []
14151446
u = path[length - 1]
1416-
for i in range(out_degree(sd, u)):
1417-
v = sd.neighbors[u][i]
1418-
if not member[v]:
1419-
available_vertices.append(v)
1447+
if directed and reverse:
1448+
for i in range(out_degree(rev_sd, u)):
1449+
v = rev_sd.neighbors[u][i]
1450+
if not member[v]:
1451+
available_vertices.append(v)
1452+
else:
1453+
for i in range(out_degree(sd, u)):
1454+
v = sd.neighbors[u][i]
1455+
if not member[v]:
1456+
available_vertices.append(v)
14201457

1421-
n_available = len(available_vertices)
1422-
if n_available > 0:
1458+
if available_vertices:
14231459
longer = True
1424-
x = randint(0, n_available - 1)
1425-
path[length] = available_vertices[x]
1460+
x = randint(0, len(available_vertices) - 1)
1461+
v = available_vertices[x]
1462+
path[length] = v
14261463
length = length + 1
1427-
member[available_vertices[x]] = True
1464+
member[v] = True
14281465

14291466
if not longer and length > longest:
1430-
1467+
# Store the current best solution
14311468
for i in range(length):
14321469
longest_path[i] = path[i]
14331470

14341471
longest = length
1472+
longest_reversed = reverse
14351473

1436-
if not longer:
1437-
1438-
memset(temp_path, -1, n * sizeof(int))
1474+
if not directed and not longer:
1475+
# We revert a cycle to change the extremity of the path
14391476
degree = out_degree(sd, path[length - 1])
14401477
while True:
14411478
x = randint(0, degree - 1)
@@ -1455,37 +1492,53 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
14551492
j += 1
14561493
if path[i] == u:
14571494
flag = True
1495+
14581496
if length == n:
14591497
if find_path:
14601498
done = True
1499+
elif directed and reverse:
1500+
done = has_edge(rev_sd, path[0], path[n - 1]) != NULL
14611501
else:
14621502
done = has_edge(sd, path[n - 1], path[0]) != NULL
14631503

14641504
if bigcount * reset_bound > max_iter:
1465-
verts = G.vertices(sort=True)
1466-
output = [verts[longest_path[i]] for i in range(longest)]
1505+
output = [int_to_vertex[longest_path[i]] for i in range(longest)]
14671506
free_short_digraph(sd)
1507+
if directed:
1508+
free_short_digraph(rev_sd)
1509+
if longest_reversed:
1510+
return (False, output[::-1])
14681511
return (False, output)
14691512
# #
14701513
# # Output test
14711514
# #
14721515

1516+
if directed and reverse:
1517+
# We revert the path to work on sd
1518+
for i in range(length//2):
1519+
t = path[i]
1520+
path[i] = path[length - i - 1]
1521+
path[length - i - 1] = t
1522+
14731523
# Test adjacencies
1524+
cdef bint good = True
14741525
for i in range(n - 1):
14751526
u = path[i]
14761527
v = path[i + 1]
1477-
# Graph is simple, so both arcs are present
14781528
if has_edge(sd, u, v) == NULL:
14791529
good = False
14801530
break
14811531
if good is False:
1482-
raise RuntimeError('vertices %d and %d are consecutive in the cycle but are not adjacent' % (u, v))
1483-
if not find_path and has_edge(sd, path[0], path[n - 1]) == NULL:
1484-
raise RuntimeError('vertices %d and %d are not adjacent' % (path[0], path[n - 1]))
1532+
raise RuntimeError(f"vertices {int_to_vertex[u]} and {int_to_vertex[v]}"
1533+
" are consecutive in the cycle but are not adjacent")
1534+
if not find_path and has_edge(sd, path[n - 1], path[0]) == NULL:
1535+
raise RuntimeError(f"vertices {int_to_vertex[path[n - 1]]} and "
1536+
f"{int_to_vertex[path[0]]} are not adjacent")
14851537

1486-
verts = G.vertices(sort=True)
1487-
output = [verts[path[i]] for i in range(length)]
1538+
output = [int_to_vertex[path[i]] for i in range(length)]
14881539
free_short_digraph(sd)
1540+
if directed:
1541+
free_short_digraph(rev_sd)
14891542

14901543
return (True, output)
14911544

0 commit comments

Comments
 (0)