Tìm đường đi ngắn nhất trong biểu đồ truy cập các nút nhất định


82

Tôi có một đồ thị vô hướng với khoảng 100 nút và khoảng 200 cạnh. Một nút được gắn nhãn 'bắt đầu', một nút là 'kết thúc' và có khoảng một tá nút được gắn nhãn 'phải vượt qua'.

Tôi cần tìm đường đi ngắn nhất qua biểu đồ này bắt đầu từ 'đầu', kết thúc ở 'cuối' và đi qua tất cả các nút 'phải đi qua' (theo bất kỳ thứ tự nào).

( http://3e.org/local/maize-graph.png / http://3e.org/local/maize-graph.dot.txt là biểu đồ được đề cập - nó đại diện cho một mê cung ngô ở Lancaster, PA)

Câu trả lời:


77

Những người khác so sánh điều này với Vấn đề Người bán hàng Đi du lịch có lẽ đã không đọc kỹ câu hỏi của bạn. Trong TSP, mục tiêu là tìm chu trình ngắn nhất truy cập tất cả các đỉnh (chu trình Hamilton) - nó tương ứng với việc mọi nút có nhãn 'phải đi qua'.

Trong trường hợp của bạn, cho rằng bạn chỉ có khoảng một tá nhãn 'phải vượt qua', và cho rằng 12! khá nhỏ (479001600), bạn chỉ có thể thử tất cả các hoán vị của chỉ các nút 'phải vượt qua' và xem đường dẫn ngắn nhất từ ​​'bắt đầu' đến 'kết thúc' truy cập các nút 'phải vượt qua' theo thứ tự đó - nó sẽ đơn giản là nối các đường đi ngắn nhất giữa hai nút liên tiếp trong danh sách đó.

Nói cách khác, trước tiên hãy tìm khoảng cách ngắn nhất giữa mỗi cặp đỉnh (bạn có thể sử dụng thuật toán Dijkstra hoặc các thuật toán khác, nhưng với số lượng nhỏ đó (100 nút), ngay cả thuật toán Floyd-Warshall mã đơn giản nhất cũng sẽ chạy đúng lúc). Sau đó, khi bạn có cái này trong bảng, hãy thử tất cả các hoán vị của các nút 'phải bỏ qua' của bạn và phần còn lại.

Một cái gì đó như thế này:

//Precomputation: Find all pairs shortest paths, e.g. using Floyd-Warshall
n = number of nodes
for i=1 to n: for j=1 to n: d[i][j]=INF
for k=1 to n:
    for i=1 to n:
        for j=1 to n:
            d[i][j] = min(d[i][j], d[i][k] + d[k][j])
//That *really* gives the shortest distance between every pair of nodes! :-)

//Now try all permutations
shortest = INF
for each permutation a[1],a[2],...a[k] of the 'mustpass' nodes:
    shortest = min(shortest, d['start'][a[1]]+d[a[1]][a[2]]+...+d[a[k]]['end'])
print shortest

(Tất nhiên đó không phải là mã thực, và nếu bạn muốn đường dẫn thực, bạn sẽ phải theo dõi xem hoán vị nào cho khoảng cách ngắn nhất và cũng là đường đi ngắn nhất của tất cả các cặp, nhưng bạn có ý tưởng.)

Nó sẽ chạy trong ít nhất vài giây trên bất kỳ ngôn ngữ hợp lý nào :)
[Nếu bạn có n nút và k nút 'phải bỏ qua', thời gian chạy của nó là O (n 3 ) cho phần Floyd-Warshall và O (k! N ) cho tất cả các phần hoán vị và 100 ^ 3 + (12!) (100) thực tế là đậu phộng trừ khi bạn có một số ràng buộc thực sự hạn chế.]


5
Với kích thước đầu vào nhỏ, tôi đồng ý với câu trả lời. Nhưng tôi quan tâm đến việc tại sao bạn lại bác bỏ khẳng định của người khác rằng đây là trường hợp của TSP. Theo cách hiểu của tôi, bạn có thể trích xuất tất cả các nút phải chuyển và sử dụng nó để tạo một biểu đồ con. Các cạnh giữa các nút trong đồ thị con có trọng số tương ứng với nghiệm APSP trên đồ thị gốc. Sau đó, câu hỏi không trở thành một ví dụ của TSP trên đồ thị con? Giải pháp của bạn dường như là một giải pháp brute-force để vấn đề (mà là tốt).
maditya

6
@maditya: Đầu tiên, tôi hy vọng bạn đồng ý rằng (trích dẫn nhận xét của Steven A Lowe về một câu trả lời khác) một câu trả lời như "TSP là khó, bwahahaha" không phải là câu trả lời thích hợp cho những người có vấn đề thực sự cần giải quyết, đặc biệt là một câu trả lời rất dễ dàng. được giải quyết trên bất kỳ máy tính nào từ vài thập kỷ trước. Thứ hai, điều này không giống với TSP vì những lý do tầm thường (định dạng đầu vào khác nhau): phiên bản nhỏ của TSP mà nó chứa dành cho một đồ thị nhỏ hơn, không phải là một ở kích thước đầu vào N. Vì vậy, mức độ hoàn chỉnh NP phụ thuộc vào số lượng nút 'phải vượt qua' có tiệm cận: nếu nó luôn luôn 12, hoặc O (log N), nó không phải là NP-đầy đủ, vv
ShreevatsaR

1
Tôi không chắc liệu kết quả sẽ là một đường dẫn. Hãy tưởng tượng phải đi từ ađể cthông qua b. Nó có thể là những con đường ngắn nhất từ bđến acchia sẻ một cạnh. Trong trường hợp đó, cạnh sẽ được lặp lại hai lần. Một trong hai đường dẫn cần phải xấu hơn đường tối ưu để không tạo ra các chu kỳ.
Spak

1
@PietroSaccardi Từ mô tả trong câu hỏi, có vẻ như mục tiêu chỉ đơn giản là tìm "con đường" ngắn nhất để đi qua tất cả các nút đó và có thể ổn nếu một số cạnh được lặp lại. Đó là, "đường dẫn" đang được sử dụng theo nghĩa lỏng lẻo. Trên thực tế, nếu các cạnh lặp lại không được phép, thậm chí có thể không có câu trả lời (ví dụ: xem xét biểu đồ B — A — C nơi bạn được yêu cầu đi từ A đến C khi đi qua B: không có cách nào để không lặp lại B — Một cạnh).
ShreevatsaR

Thuật toán Floyd-Warshall không được triển khai chính xác ở đây: vì tất cả các ô của mảng được khởi tạo bằng INF, dòng d[i][j] = min(d[i][j], d[i][k] + d[k][j])trở thành d[i][j] = min(INF, INF + INF)và tất cả các ô luôn luôn bằng INF. Bạn cần thêm một bước để điền vào mảng này với độ dài các cạnh từ biểu đồ.
Stef

24

chạy Thuật toán của Djikstra để tìm các đường đi ngắn nhất giữa tất cả các nút quan trọng (bắt đầu, kết thúc và phải vượt qua), sau đó, đường dẫn đầu tiên theo chiều sâu sẽ cho bạn biết đường đi ngắn nhất thông qua đồ thị con kết quả chạm vào tất cả các nút bắt đầu .. . mustpasses ... end


Điều này có vẻ như nó có tiềm năng hiệu quả hơn giải pháp đã chọn. Tôi cá là nếu bạn bổ sung thêm một chút, nó sẽ đạt điểm cao hơn. Đầu tiên tôi phải nghĩ xem liệu một con đường ngắn nhất giữa tất cả các cặp có đảm bảo một con đường giữa tất cả các nút được yêu cầu hay không .. nhưng tôi tin rằng nó sẽ (giả sử là không được định hướng).
galactikuh

Tôi nghĩ rằng điều này không hiệu quả nếu một đường dẫn không được lặp lại các cạnh.
tuket

1
Làm thế nào để tìm đường đi ngắn nhất trong ví dụ của Advent of Code ngày 24? Adventofcode.com/2016/day/24
Erwin Rooijakkers

15

Đây là hai vấn đề ... Steven Lowe đã chỉ ra điều này, nhưng không dành đủ sự tôn trọng cho nửa sau của vấn đề.

Trước tiên, bạn nên khám phá các đường đi ngắn nhất giữa tất cả các nút quan trọng của bạn (bắt đầu, kết thúc, phải đi qua). Khi các đường dẫn này được phát hiện, bạn có thể xây dựng một đồ thị đơn giản hóa, trong đó mỗi cạnh trong đồ thị mới là một đường dẫn từ một nút quan trọng đến một nút quan trọng khác trong đồ thị gốc. Có rất nhiều thuật toán tìm đường mà bạn có thể sử dụng để tìm đường đi ngắn nhất tại đây.

Tuy nhiên, khi bạn có biểu đồ mới này, bạn đã gặp chính xác vấn đề Người bán hàng đi du lịch (à, gần như ... Không cần phải quay lại điểm xuất phát của bạn). Bất kỳ bài viết nào liên quan đến điều này, được đề cập ở trên, sẽ được áp dụng.


14

Thực ra vấn đề bạn đăng cũng tương tự như người bán hàng đi du lịch, nhưng tôi nghĩ gần với vấn đề tìm đường đơn giản hơn. Thay vì cần phải truy cập từng nút, bạn chỉ cần truy cập một tập hợp các nút cụ thể trong thời gian (khoảng cách) ngắn nhất có thể.

Lý do cho điều này là, không giống như vấn đề người bán hàng lưu động, một mê cung ngô sẽ không cho phép bạn đi trực tiếp từ bất kỳ điểm này đến bất kỳ điểm nào khác trên bản đồ mà không cần phải đi qua các nút khác để đến đó.

Tôi thực sự muốn giới thiệu tìm kiếm đường dẫn A * như một kỹ thuật để xem xét. Bạn thiết lập điều này bằng cách quyết định xem các nút nào có quyền truy cập trực tiếp vào các nút khác và "chi phí" của mỗi bước nhảy từ một nút cụ thể là bao nhiêu. Trong trường hợp này, có vẻ như mỗi "bước nhảy" có thể có chi phí ngang nhau, vì các nút của bạn có vẻ tương đối gần nhau. A * có thể sử dụng thông tin này để tìm đường dẫn chi phí thấp nhất giữa hai điểm bất kỳ. Vì bạn cần phải đi từ điểm A đến điểm B và truy cập khoảng 12 ở giữa, nên ngay cả một cách tiếp cận bạo lực bằng cách sử dụng tìm đường cũng không ảnh hưởng gì.

Chỉ là một sự thay thế để xem xét. Nó trông rất giống với vấn đề của người bán hàng lưu động, và đó là những bài báo tốt để đọc tiếp, nhưng hãy nhìn kỹ hơn và bạn sẽ thấy rằng những điều phức tạp duy nhất của nó. ^ _ ^ Điều này xuất phát từ suy nghĩ của một nhà lập trình trò chơi điện tử, người đã từng giải quyết những việc này trước đây.


2
+1 - đây là một câu trả lời tốt hơn nhiều so với 'vấn đề người bán hàng đi du lịch là khó, bwahahaha'
Steven A. Lowe

5

Andrew Top có ý tưởng đúng:

1) Thuật toán Djikstra 2) Một số kinh nghiệm TSP.

Tôi đề xuất phương pháp phỏng đoán Lin-Kernighan: đây là một trong những phương pháp nổi tiếng nhất đối với bất kỳ vấn đề NP Complete nào. Điều duy nhất khác cần nhớ là sau khi bạn mở rộng biểu đồ một lần nữa sau bước 2, bạn có thể có các vòng trong đường mở rộng của mình, vì vậy bạn nên đi vòng quanh những vòng tròn đó (xem mức độ của các đỉnh dọc theo đường đi của bạn).

Tôi thực sự không chắc giải pháp này sẽ tốt như thế nào so với giải pháp tối ưu. Có thể có một số trường hợp bệnh lý liên quan đến đoản mạch. Rốt cuộc, vấn đề này trông rất giống Steiner Tree: http://en.wikipedia.org/wiki/Steiner_tree và bạn chắc chắn không thể ước tính Steiner Tree bằng cách chỉ thu nhỏ đồ thị của bạn và chạy Kruskal's chẳng hạn.


5

Đây không phải là vấn đề TSP và không phải là khó NP vì câu hỏi ban đầu không yêu cầu rằng các nút phải chuyển chỉ được truy cập một lần. Điều này làm cho câu trả lời trở nên đơn giản hơn nhiều so với chỉ brute-force sau khi biên dịch một danh sách các đường đi ngắn nhất giữa tất cả các nút phải vượt qua thông qua thuật toán Dijkstra. Có thể có một cách tốt hơn để thực hiện nhưng một cách đơn giản sẽ là làm việc ngược lại một cây nhị phân. Hãy tưởng tượng một danh sách các nút [bắt đầu, a, b, c, kết thúc]. Tính tổng các khoảng cách đơn giản [bắt đầu-> a-> b-> c-> kết thúc] đây là khoảng cách mục tiêu mới của bạn để đánh bại. Bây giờ hãy thử [start-> a-> c-> b-> end] và nếu điều đó tốt hơn hãy đặt nó làm mục tiêu (và nhớ rằng nó đến từ mẫu nút đó). Làm việc ngược lại các hoán vị:

  • [start-> a-> b-> c-> end]
  • [start-> a-> c-> b-> end]
  • [start-> b-> a-> c-> end]
  • [start-> b-> c-> a-> end]
  • [start-> c-> a-> b-> end]
  • [start-> c-> b-> a-> end]

Một trong số đó sẽ ngắn nhất.

(Các nút 'được truy cập nhiều lần' ở đâu, nếu có? Chúng chỉ được ẩn trong bước khởi tạo đường ngắn nhất. Đường ngắn nhất giữa a và b có thể chứa c hoặc thậm chí là điểm cuối. Bạn không cần quan tâm )


Chỉ được ghé thăm một lần là bắt buộc với điều kiện đó là con đường ngắn nhất.
Aziuth

Huh. Tôi đã khá chắc chắn một phút trước, nhưng vâng, bạn nói đúng. Rõ ràng không phải trong một cái cây có vài nhánh. Nhưng, có một điều là, nếu chúng ta trừu tượng hóa vấn đề thành một đồ thị mới chỉ chứa các nút phải qua được kết nối đầy đủ, với các nút có khoảng cách của đường đi ngắn nhất trong đồ thị ban đầu, chúng ta sẽ đến TSP. Vì vậy, bạn có chắc rằng nó không khó NP không? Tôi giả định rằng trong bài toán tổng quát, số lượng nút phải vượt phụ thuộc vào tổng số nút, và nếu, ví dụ, nếu tổng là một đa thức của đường phải, chúng ta nhận được độ cứng NP, phải không?
Aziuth

Lộ trình từ a-> b có thể đi qua c. Vì vậy, không có brnach ngăn cản bất kỳ khác. Nó chỉ là hoán vị.
bjorke

Đúng? Nhưng hoán vị là O (n!) Nếu chúng ta giả định rằng số lượng các nút phải vượt có một số liên hệ với tổng số các nút, giống như "tổng các nút là một đa thức của các nút phải vượt". Bạn vừa giải quyết TSP bằng vũ lực.
Aziuth

2

Xem xét số lượng nút và cạnh là tương đối hữu hạn, bạn có thể tính toán mọi đường đi có thể và đi đường ngắn nhất.

Nói chung, đây được gọi là vấn đề người bán hàng lưu động và có thời gian chạy đa thức không xác định, bất kể bạn sử dụng thuật toán nào.

http://en.wikipedia.org/wiki/Traveling_salesman_problem


1

Câu hỏi nói về must-pass theo BẤT KỲ thứ tự nào . Tôi đã cố gắng tìm kiếm một giải pháp về thứ tự xác định của các nút phải vượt qua. Tôi đã tìm thấy câu trả lời của mình nhưng vì không có câu hỏi nào trên StackOverflow có câu hỏi tương tự nên tôi đăng ở đây để cho phép mọi người tối đa được hưởng lợi từ nó.

Nếu thứ tự hoặc phải chuyển được xác định thì bạn có thể chạy thuật toán dijkstra nhiều lần. Ví dụ, giả sử bạn phải bắt đầu từ sđi qua k1, k2k3(theo thứ tự tương ứng) và dừng lại ở e. Sau đó, những gì bạn có thể làm là chạy thuật toán dijkstra giữa mỗi cặp nút liên tiếp. Các chi phícon đường sẽ được đưa ra bởi:

dijkstras(s, k1) + dijkstras(k1, k2) + dijkstras(k2, k3) + dijkstras(k3, 3)


0

Làm thế nào về việc sử dụng bạo lực trên hàng chục nút 'phải truy cập'. Bạn có thể bao gồm tất cả các kết hợp có thể có của 12 nút đủ dễ dàng và điều này giúp bạn có một mạch tối ưu mà bạn có thể theo dõi để che chúng.

Bây giờ vấn đề của bạn được đơn giản hóa thành một trong việc tìm các tuyến đường tối ưu từ nút bắt đầu đến mạch, sau đó bạn đi theo xung quanh cho đến khi bạn đã bao phủ chúng, rồi tìm tuyến đường từ đó đến cuối.

Đường dẫn cuối cùng bao gồm:

bắt đầu -> đường dẫn đến mạch * -> mạch phải thăm các nút -> đường dẫn đến kết thúc * -> kết thúc

Bạn tìm thấy những con đường tôi đã đánh dấu * như thế này

Thực hiện tìm kiếm A * từ nút bắt đầu đến mọi điểm trên mạch cho mỗi điểm trong số này thực hiện tìm kiếm A * từ nút tiếp theo và trước đó trên mạch cho đến cuối (vì bạn có thể theo dõi vòng mạch theo một trong hai hướng) cuối cùng là rất nhiều đường dẫn tìm kiếm và bạn có thể chọn đường dẫn với chi phí thấp nhất.

Có rất nhiều chỗ để tối ưu hóa bằng cách lưu vào bộ nhớ đệm các tìm kiếm, nhưng tôi nghĩ điều này sẽ tạo ra các giải pháp tốt.

Mặc dù vậy, việc tìm kiếm một giải pháp tối ưu chẳng đi đến đâu vì điều đó có thể liên quan đến việc rời khỏi mạch phải truy cập trong tìm kiếm.


0

Một điều không được đề cập ở bất cứ đâu, đó là liệu có ổn cho cùng một đỉnh được truy cập nhiều lần trong đường dẫn hay không. Hầu hết các câu trả lời ở đây đều giả định rằng việc truy cập vào cùng một cạnh nhiều lần là được, nhưng tôi đưa ra câu hỏi (một đường dẫn không nên truy cập cùng một đỉnh nhiều hơn một lần!) Là không nên truy cập cùng một đỉnh hai lần.

Vì vậy, cách tiếp cận brute force vẫn sẽ được áp dụng, nhưng bạn phải loại bỏ các đỉnh đã được sử dụng khi bạn cố gắng tính toán từng tập con của đường đi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.