@@ -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
@@ -1257,11 +1258,29 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1257
1258
Finally, an example on a graph which does not have a Hamiltonian
1258
1259
path::
1259
1260
1260
- sage: G=graphs. HyperStarGraph( 5,2)
1261
- sage: fh( G,find_path=False)
1262
- ( False, ['00110', '10100', '01100', '11000', '01010', '10010', '00011', '10001', '00101' ])
1263
- sage: fh( G,find_path=True)
1264
- ( False, ['01001', '10001', '00101', '10100', '00110', '10010', '01010', '11000', '01100' ])
1261
+ sage: G = graphs. HyperStarGraph( 5, 2)
1262
+ sage: G. order( )
1263
+ 10
1264
+ sage: b, P = fh( G,find_path=False)
1265
+ sage: b, len( P)
1266
+ ( False, 9)
1267
+ sage: b, P = fh( G,find_path=True)
1268
+ sage: b, len( P)
1269
+ ( False, 9)
1270
+
1271
+ The method can also be used for directed graphs::
1272
+
1273
+ sage: G = DiGraph( [(0, 1), (1, 2), (2, 3) ])
1274
+ sage: fh( G)
1275
+ ( False, [0, 1, 2, 3 ])
1276
+ sage: G = G. reverse( )
1277
+ sage: fh( G)
1278
+ ( False, [3, 2, 1, 0 ])
1279
+ sage: G = DiGraph( )
1280
+ sage: G. add_cycle( [0, 1, 2, 3, 4, 5 ])
1281
+ sage: b, P = fh( G)
1282
+ sage: b, len( P)
1283
+ ( True, 6)
1265
1284
1266
1285
TESTS:
1267
1286
@@ -1309,30 +1328,38 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1309
1328
sage: fh( G, find_path=True)
1310
1329
( False, [0, 1, 2, 3 ])
1311
1330
1331
+ Check that the method is robust to incomparable vertices::
1332
+
1333
+ sage: G = Graph( [(1, 'a'), ('a', 2), (2, 3), (3, 1) ])
1334
+ sage: b, C = fh( G, find_path=False)
1335
+ sage: b, len( C)
1336
+ ( True, 4)
1312
1337
"""
1338
+ G._scream_if_not_simple()
1339
+
1313
1340
from sage.misc.prandom import randint
1314
1341
cdef int n = G.order()
1315
1342
1316
1343
# Easy cases
1317
- if not n:
1318
- return False , []
1319
- if n == 1 :
1320
- return False , G.vertices(sort = False )
1344
+ if n < 2 :
1345
+ return False , list (G)
1321
1346
1322
1347
# To clean the output when find_path is None or a number
1323
1348
find_path = (find_path > 0 )
1324
1349
1325
1350
if G.is_clique(induced = False ):
1326
1351
# We have an hamiltonian path since n >= 2, but we have an hamiltonian
1327
1352
# cycle only if n >= 3
1328
- return find_path or n >= 3 , G.vertices( sort = True )
1353
+ return find_path or n >= 3 , list (G )
1329
1354
1330
1355
cdef list best_path, p
1331
1356
if not G.is_connected():
1332
1357
# The (Di)Graph has no hamiltonian path or cycle. We search for the
1333
1358
# longest path in its connected components.
1334
1359
best_path = []
1335
1360
for H in G.connected_components_subgraphs():
1361
+ if H.order() <= len (best_path):
1362
+ continue
1336
1363
_, p = find_hamiltonian(H, max_iter = max_iter, reset_bound = reset_bound,
1337
1364
backtrack_bound = backtrack_bound, find_path = True )
1338
1365
if len (p) > len (best_path):
@@ -1341,20 +1368,24 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1341
1368
1342
1369
# Misc variables used below
1343
1370
cdef int i, j
1344
- cdef int n_available
1371
+ cdef bint directed = G.is_directed()
1345
1372
1346
1373
# Initialize the path.
1347
1374
cdef MemoryAllocator mem = MemoryAllocator()
1348
1375
cdef int * path = < int * > mem.allocarray(n, sizeof(int ))
1349
- memset(path, - 1 , n * sizeof(int ))
1350
1376
1351
1377
# Initialize the membership array
1352
1378
cdef bint * member = < bint * > mem.allocarray(n, sizeof(int ))
1353
1379
memset(member, 0 , n * sizeof(int ))
1354
1380
1355
1381
# static copy of the graph for more efficient operations
1382
+ cdef list int_to_vertex = list (G)
1356
1383
cdef short_digraph sd
1357
- init_short_digraph(sd, G)
1384
+ init_short_digraph(sd, G, edge_labelled = False , vertex_list = int_to_vertex)
1385
+ cdef short_digraph rev_sd
1386
+ cdef bint reverse = False
1387
+ if directed:
1388
+ init_reverse(rev_sd, sd)
1358
1389
1359
1390
# A list to store the available vertices at each step
1360
1391
cdef list available_vertices = []
@@ -1364,7 +1395,7 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1364
1395
cdef int u = randint(0 , n - 1 )
1365
1396
while not out_degree(sd, u):
1366
1397
u = randint(0 , n - 1 )
1367
- # Then we pick at random a neighbor of u
1398
+ # Then we pick at random a neighbor of u
1368
1399
cdef int x = randint(0 , out_degree(sd, u) - 1 )
1369
1400
cdef int v = sd.neighbors[u][x]
1370
1401
# This will be the first edge in the path
@@ -1382,34 +1413,35 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1382
1413
1383
1414
# Initialize a path to contain the longest path
1384
1415
cdef int * longest_path = < int * > mem.allocarray(n, sizeof(int ))
1385
- memset(longest_path, - 1 , n * sizeof(int ))
1386
1416
for i in range (length):
1387
1417
longest_path[i] = path[i]
1388
1418
1389
- # Initialize a temporary path for flipping
1390
- cdef int * temp_path = < int * > mem.allocarray(n, sizeof(int ))
1391
- memset(temp_path, - 1 , n * sizeof(int ))
1392
-
1393
1419
cdef bint longer = False
1394
- cdef bint good = True
1420
+ cdef bint longest_reversed = False
1395
1421
cdef bint flag
1396
1422
1397
1423
while not done:
1398
1424
counter = counter + 1
1399
1425
if counter % 10 == 0 :
1400
1426
# Reverse the path
1401
-
1402
1427
for i in range (length// 2 ):
1403
1428
t = path[i]
1404
1429
path[i] = path[length - i - 1 ]
1405
1430
path[length - i - 1 ] = t
1406
1431
1432
+ if directed:
1433
+ # We now work on the reverse graph
1434
+ reverse = not reverse
1435
+
1407
1436
if counter > reset_bound:
1408
1437
bigcount = bigcount + 1
1409
1438
counter = 1
1410
1439
1411
1440
# Time to reset the procedure
1412
1441
memset(member, 0 , n * sizeof(int ))
1442
+ if directed and reverse:
1443
+ # We restore the original orientation
1444
+ reverse = False
1413
1445
1414
1446
# First we pick a random vertex u of (out-)degree at least one
1415
1447
u = randint(0 , n - 1 )
@@ -1425,37 +1457,44 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1425
1457
member[u] = True
1426
1458
member[v] = True
1427
1459
1428
- if counter % backtrack_bound == 0 :
1460
+ if length > 5 and counter % backtrack_bound == 0 :
1429
1461
for i in range (5 ):
1430
1462
member[path[length - i - 1 ]] = False
1431
1463
length = length - 5
1432
1464
longer = False
1433
1465
1466
+ # We search for a possible extension of the path
1434
1467
available_vertices = []
1435
1468
u = path[length - 1 ]
1436
- for i in range (out_degree(sd, u)):
1437
- v = sd.neighbors[u][i]
1438
- if not member[v]:
1439
- available_vertices.append(v)
1469
+ if directed and reverse:
1470
+ for i in range (out_degree(rev_sd, u)):
1471
+ v = rev_sd.neighbors[u][i]
1472
+ if not member[v]:
1473
+ available_vertices.append(v)
1474
+ else :
1475
+ for i in range (out_degree(sd, u)):
1476
+ v = sd.neighbors[u][i]
1477
+ if not member[v]:
1478
+ available_vertices.append(v)
1440
1479
1441
- n_available = len (available_vertices)
1442
- if n_available > 0 :
1480
+ if available_vertices:
1443
1481
longer = True
1444
- x = randint(0 , n_available - 1 )
1445
- path[length] = available_vertices[x]
1482
+ x = randint(0 , len (available_vertices) - 1 )
1483
+ v = available_vertices[x]
1484
+ path[length] = v
1446
1485
length = length + 1
1447
- member[available_vertices[x] ] = True
1486
+ member[v ] = True
1448
1487
1449
1488
if not longer and length > longest:
1450
-
1489
+ # Store the current best solution
1451
1490
for i in range (length):
1452
1491
longest_path[i] = path[i]
1453
1492
1454
1493
longest = length
1494
+ longest_reversed = reverse
1455
1495
1456
- if not longer:
1457
-
1458
- memset(temp_path, - 1 , n * sizeof(int ))
1496
+ if not directed and not longer and out_degree(sd, path[length - 1 ]) > 1 :
1497
+ # We revert a cycle to change the extremity of the path
1459
1498
degree = out_degree(sd, path[length - 1 ])
1460
1499
while True :
1461
1500
x = randint(0 , degree - 1 )
@@ -1475,37 +1514,53 @@ cpdef tuple find_hamiltonian(G, long max_iter=100000, long reset_bound=30000,
1475
1514
j += 1
1476
1515
if path[i] == u:
1477
1516
flag = True
1517
+
1478
1518
if length == n:
1479
1519
if find_path:
1480
1520
done = True
1521
+ elif directed and reverse:
1522
+ done = has_edge(rev_sd, path[0 ], path[n - 1 ]) != NULL
1481
1523
else :
1482
1524
done = has_edge(sd, path[n - 1 ], path[0 ]) != NULL
1483
1525
1484
1526
if bigcount * reset_bound > max_iter:
1485
- verts = G.vertices(sort = True )
1486
- output = [verts[longest_path[i]] for i in range (longest)]
1527
+ output = [int_to_vertex[longest_path[i]] for i in range (longest)]
1487
1528
free_short_digraph(sd)
1529
+ if directed:
1530
+ free_short_digraph(rev_sd)
1531
+ if longest_reversed:
1532
+ return (False , output[::- 1 ])
1488
1533
return (False , output)
1489
1534
# #
1490
1535
# # Output test
1491
1536
# #
1492
1537
1538
+ if directed and reverse:
1539
+ # We revert the path to work on sd
1540
+ for i in range (length// 2 ):
1541
+ t = path[i]
1542
+ path[i] = path[length - i - 1 ]
1543
+ path[length - i - 1 ] = t
1544
+
1493
1545
# Test adjacencies
1546
+ cdef bint good = True
1494
1547
for i in range (n - 1 ):
1495
1548
u = path[i]
1496
1549
v = path[i + 1 ]
1497
- # Graph is simple, so both arcs are present
1498
1550
if has_edge(sd, u, v) == NULL :
1499
1551
good = False
1500
1552
break
1501
1553
if good is False :
1502
- raise RuntimeError (' vertices %d and %d are consecutive in the cycle but are not adjacent' % (u, v))
1503
- if not find_path and has_edge(sd, path[0 ], path[n - 1 ]) == NULL :
1504
- raise RuntimeError (' vertices %d and %d are not adjacent' % (path[0 ], path[n - 1 ]))
1554
+ raise RuntimeError (f" vertices {int_to_vertex[u]} and {int_to_vertex[v]}"
1555
+ " are consecutive in the cycle but are not adjacent" )
1556
+ if not find_path and has_edge(sd, path[n - 1 ], path[0 ]) == NULL :
1557
+ raise RuntimeError (f" vertices {int_to_vertex[path[n - 1]]} and "
1558
+ f" {int_to_vertex[path[0]]} are not adjacent" )
1505
1559
1506
- verts = G.vertices(sort = True )
1507
- output = [verts[path[i]] for i in range (length)]
1560
+ output = [int_to_vertex[path[i]] for i in range (length)]
1508
1561
free_short_digraph(sd)
1562
+ if directed:
1563
+ free_short_digraph(rev_sd)
1509
1564
1510
1565
return (True , output)
1511
1566
0 commit comments