@@ -35,6 +35,7 @@ from sage.libs.gmp.mpz cimport *
35
35
from sage.misc.prandom import random
36
36
from sage.graphs.base.static_sparse_graph cimport short_digraph
37
37
from sage.graphs.base.static_sparse_graph cimport init_short_digraph
38
+ from sage.graphs.base.static_sparse_graph cimport init_reverse
38
39
from sage.graphs.base.static_sparse_graph cimport free_short_digraph
39
40
from sage.graphs.base.static_sparse_graph cimport out_degree, has_edge
40
41
@@ -1237,11 +1238,34 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1237
1238
Finally, an example on a graph which does not have a Hamiltonian
1238
1239
path::
1239
1240
1240
- sage: G= graphs. HyperStarGraph( 5,2)
1241
+ sage: G = graphs. HyperStarGraph( 5, 2)
1241
1242
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 ' ])
1243
1244
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' ])
1245
1269
1246
1270
TESTS:
1247
1271
@@ -1288,31 +1312,32 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1288
1312
( False, [0, 1, 2, 3 ])
1289
1313
sage: fh( G, find_path=True)
1290
1314
( False, [0, 1, 2, 3 ])
1291
-
1292
1315
"""
1316
+ G._scream_if_not_simple()
1317
+
1293
1318
from sage.misc.prandom import randint
1294
1319
cdef int n = G.order()
1295
1320
1296
1321
# 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)
1301
1324
1302
1325
# To clean the output when find_path is None or a number
1303
1326
find_path = (find_path > 0 )
1304
1327
1305
1328
if G.is_clique(induced = False ):
1306
1329
# We have an hamiltonian path since n >= 2, but we have an hamiltonian
1307
1330
# 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 )
1309
1332
1310
1333
cdef list best_path, p
1311
1334
if not G.is_connected():
1312
1335
# The (Di)Graph has no hamiltonian path or cycle. We search for the
1313
1336
# longest path in its connected components.
1314
1337
best_path = []
1315
1338
for H in G.connected_components_subgraphs():
1339
+ if H.order() <= len (best_path):
1340
+ continue
1316
1341
_, p = find_hamiltonian(H, max_iter = max_iter, reset_bound = reset_bound,
1317
1342
backtrack_bound = backtrack_bound, find_path = True )
1318
1343
if len (p) > len (best_path):
@@ -1321,20 +1346,24 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1321
1346
1322
1347
# Misc variables used below
1323
1348
cdef int i, j
1324
- cdef int n_available
1349
+ cdef bint directed = G.is_directed()
1325
1350
1326
1351
# Initialize the path.
1327
1352
cdef MemoryAllocator mem = MemoryAllocator()
1328
1353
cdef int * path = < int * > mem.allocarray(n, sizeof(int ))
1329
- memset(path, - 1 , n * sizeof(int ))
1330
1354
1331
1355
# Initialize the membership array
1332
1356
cdef bint * member = < bint * > mem.allocarray(n, sizeof(int ))
1333
1357
memset(member, 0 , n * sizeof(int ))
1334
1358
1335
1359
# static copy of the graph for more efficient operations
1360
+ cdef list int_to_vertex = list (G)
1336
1361
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)
1338
1367
1339
1368
# A list to store the available vertices at each step
1340
1369
cdef list available_vertices = []
@@ -1344,7 +1373,7 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1344
1373
cdef int u = randint(0 , n - 1 )
1345
1374
while not out_degree(sd, u):
1346
1375
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
1348
1377
cdef int x = randint(0 , out_degree(sd, u) - 1 )
1349
1378
cdef int v = sd.neighbors[u][x]
1350
1379
# 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,
1362
1391
1363
1392
# Initialize a path to contain the longest path
1364
1393
cdef int * longest_path = < int * > mem.allocarray(n, sizeof(int ))
1365
- memset(longest_path, - 1 , n * sizeof(int ))
1366
1394
for i in range (length):
1367
1395
longest_path[i] = path[i]
1368
1396
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
-
1373
1397
cdef bint longer = False
1374
- cdef bint good = True
1398
+ cdef bint longest_reversed = False
1375
1399
cdef bint flag
1376
1400
1377
1401
while not done:
1378
1402
counter = counter + 1
1379
1403
if counter % 10 == 0 :
1380
1404
# Reverse the path
1381
-
1382
1405
for i in range (length// 2 ):
1383
1406
t = path[i]
1384
1407
path[i] = path[length - i - 1 ]
1385
1408
path[length - i - 1 ] = t
1386
1409
1410
+ if directed:
1411
+ # We now work on the reverse graph
1412
+ reverse = not reverse
1413
+
1387
1414
if counter > reset_bound:
1388
1415
bigcount = bigcount + 1
1389
1416
counter = 1
1390
1417
1391
1418
# Time to reset the procedure
1392
1419
memset(member, 0 , n * sizeof(int ))
1420
+ if directed and reverse:
1421
+ # We restore the original orientation
1422
+ reverse = False
1393
1423
1394
1424
# First we pick a random vertex u of (out-)degree at least one
1395
1425
u = randint(0 , n - 1 )
@@ -1405,37 +1435,44 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1405
1435
member[u] = True
1406
1436
member[v] = True
1407
1437
1408
- if counter % backtrack_bound == 0 :
1438
+ if length > 5 and counter % backtrack_bound == 0 :
1409
1439
for i in range (5 ):
1410
1440
member[path[length - i - 1 ]] = False
1411
1441
length = length - 5
1412
1442
longer = False
1413
1443
1444
+ # We search for a possible extension of the path
1414
1445
available_vertices = []
1415
1446
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)
1420
1457
1421
- n_available = len (available_vertices)
1422
- if n_available > 0 :
1458
+ if available_vertices:
1423
1459
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
1426
1463
length = length + 1
1427
- member[available_vertices[x] ] = True
1464
+ member[v ] = True
1428
1465
1429
1466
if not longer and length > longest:
1430
-
1467
+ # Store the current best solution
1431
1468
for i in range (length):
1432
1469
longest_path[i] = path[i]
1433
1470
1434
1471
longest = length
1472
+ longest_reversed = reverse
1435
1473
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
1439
1476
degree = out_degree(sd, path[length - 1 ])
1440
1477
while True :
1441
1478
x = randint(0 , degree - 1 )
@@ -1455,37 +1492,53 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1455
1492
j += 1
1456
1493
if path[i] == u:
1457
1494
flag = True
1495
+
1458
1496
if length == n:
1459
1497
if find_path:
1460
1498
done = True
1499
+ elif directed and reverse:
1500
+ done = has_edge(rev_sd, path[0 ], path[n - 1 ]) != NULL
1461
1501
else :
1462
1502
done = has_edge(sd, path[n - 1 ], path[0 ]) != NULL
1463
1503
1464
1504
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)]
1467
1506
free_short_digraph(sd)
1507
+ if directed:
1508
+ free_short_digraph(rev_sd)
1509
+ if longest_reversed:
1510
+ return (False , output[::- 1 ])
1468
1511
return (False , output)
1469
1512
# #
1470
1513
# # Output test
1471
1514
# #
1472
1515
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
+
1473
1523
# Test adjacencies
1524
+ cdef bint good = True
1474
1525
for i in range (n - 1 ):
1475
1526
u = path[i]
1476
1527
v = path[i + 1 ]
1477
- # Graph is simple, so both arcs are present
1478
1528
if has_edge(sd, u, v) == NULL :
1479
1529
good = False
1480
1530
break
1481
1531
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" )
1485
1537
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)]
1488
1539
free_short_digraph(sd)
1540
+ if directed:
1541
+ free_short_digraph(rev_sd)
1489
1542
1490
1543
return (True , output)
1491
1544
0 commit comments