Kiểm tra xem việc thêm cạnh vào DAG có dẫn đến chu kỳ không


7

Về khởi đầu: Đây là một vấn đề của cuộc thi lập trình, nhưng không phải từ một cuộc thi đang diễn ra. Thật không may, tôi không thể cung cấp bất kỳ liên kết nào đến nhiệm vụ này, vì nó không có sẵn công khai. Đó là từ một trong những cuộc thi lập trình địa phương của Ba Lan năm 2011 do Trường Khoa học Máy tính Warsaw tổ chức.

Tôi có một biểu đồ với các đỉnh không có cạnh và danh sách các cạnh . Trên -TH thứ hai cạnh -thứ đã được thêm vào một đồ thị. Tôi muốn biết sau giây nào sẽ có một chu kỳ trong biểu đồ.V (1V5105)E (1E5105)ii

Giải pháp rõ ràng nhất là thực hiện DFS sau khi thêm từng cạnh, nhưng sẽ mất thời gian . Một giải pháp khác là giữ cho đồ thị được sắp xếp theo cấu trúc liên kết và khi thêm một cạnh, tôi có thể đặt nó theo cách sao cho nó không làm xáo trộn trật tự tôpô. Điều này sẽ mất nhiều thời gian . Tôi đã thực hiện một số nghiên cứu trên Google và dường như đây là thuật toán trực tuyến nhanh nhất.O((V+E)2)O(V2)

Theo tôi biết tất cả các cạnh trước đó tôi có thể sử dụng thuật toán ngoại tuyến. Thuật toán ngoại tuyến nhanh nhất tôi có thể nghĩ đến là tìm kiếm nhị phân. Nếu sau biểu đồ thứ hai thứ chứa một chu kỳ thì rõ ràng nó sẽ có một chu kỳ tại bất kỳ giây nào khác . Vì vậy, tôi có thể thực hiện tìm kiếm nhị phân để tìm nhỏ nhất bằng cách thực hiện DFS lần, mỗi lần lấy thời gian , do đó tổng độ phức tạp của giải pháp này sẽ là .klkkO(logE)O(V+E)O((V+E)logE)

Nó khá nhanh, nhưng tôi tự hỏi liệu có tồn tại thuật toán ngoại tuyến nào nhanh hơn không. Một điều tôi có thể nghĩ đến là cho mỗi trọng lượng cạnh bằng với thời gian khi cạnh này được thêm vào biểu đồ. Sau đó, nhiệm vụ sẽ tương đương với việc tìm một chu kỳ với trọng lượng cạnh tối đa tối thiểu. Tôi không biết nếu nó dẫn đến bất cứ nơi nào.


2
Bạn có thể muốn tìm kiếm "phát hiện chu kỳ trực tuyến" trên Google Scholar, mặc dù tôi đoán là các thuật toán đó sẽ chậm hơn so với phương pháp tìm kiếm nhị phân của bạn.
DW

2
Bạn có thể có thể xen kẽ thuật toán xóa lá lặp (Kahn?) Và xóa cạnh tối đa. tức là (1) tiếp tục xóa các nút lá cho đến khi bạn không thể tìm thấy bất kỳ nút nào, và sau đó (2) xóa cạnh được đánh số cao nhất và lặp lại cho đến khi trống. Cuối cùng (2) là cạnh mong muốn.
Máy xay sinh tố

2
OK, tôi đã đưa ra nhận xét trước đó để trả lời - phải mất một thời gian để tra cứu Kahn và xem các bước bổ sung có hợp lý không.
KWillets

Câu trả lời:


2

Thuật toán của KWillets

KWillets dường như có thuật toán tốt nhất . Về cơ bản, bạn lặp lại xen kẽ giữa hai bước sau:

  1. Đối với mỗi nguồn (đỉnh không có cạnh đi), hãy xóa nguồn và tất cả các cạnh dẫn ra khỏi nó. Lặp lại cho đến khi không còn nguồn nào nữa.

  2. Xóa cạnh được đánh số cao nhất, nghĩa là cạnh có tại thời điểm gần nhất (trong số tất cả các cạnh còn lại). Quay trở lại bước 1.

Khi biểu đồ trống, hoàn tác bước 1 và bước 2 cuối cùng và có biểu đồ sớm nhất có chu kỳ. Nói cách khác, lấy cạnh cuối cùng bị loại bỏ trong bước 2 cuối cùng trước khi biểu đồ trở nên trống rỗng và biểu đồ chứa cạnh đó và tất cả các cạnh trước đó là cạnh đầu tiên chứa chu kỳ.

Vì danh sách các cạnh của bạn đã được sắp xếp theo thời gian tăng dần, điều này có thể được thực hiện trong thời gian . Bạn sẽ duy trì danh sách các cạnh trong danh sách liên kết đôi, với các con trỏ giữa các nút đó và cạnh tương ứng trong biểu đồ. Bước 1 yêu cầu thời gian cho mỗi nút hoặc cạnh bị xóa, nếu bạn giữ một danh sách công việc riêng biệt của tất cả các đỉnh nguồn và bất cứ khi nào bạn xóa một cạnh bạn kiểm tra xem điều đó đã tạo ra một đỉnh nguồn mới.O(V+E)O(1)

Tìm kiếm nhị phân

Có thể thực hiện một số tối ưu hóa nhỏ cho phương pháp tìm kiếm nhị phân của bạn, mặc dù chúng sẽ không cải thiện thời gian chạy không có triệu chứng: thời gian chạy tiệm cận sẽ vẫn là .O((V+E)logE)

Giả sử bạn phân tích biểu đồ tại thời điểm và phát hiện ra rằng nó có chứa một chu kỳ. Phân tách biểu đồ thành các thành phần kết nối mạnh mẽ. Bây giờ bạn có thể xóa vĩnh viễn tất cả các nút bị cô lập (nghĩa là các nút nằm trong SCC có kích thước 1); chúng không thể là một phần của chu trình. Bạn có thể tiếp tục tìm kiếm nhị phân từ đây.k

Ngoài ra, khi bạn phân tách thành các thành phần được kết nối mạnh, đối với mỗi thành phần được kết nối mạnh, hãy làm như sau. Đếm số lượng nút trong SCC, giả sử trong số chúng. Sắp xếp các cạnh trong SCC trong thời gian tăng. Nhìn vào thời gian của trong số họ, nói thời gian . Sau đó, lần đầu tiên khi có một chu kỳ trong thành phần được kết nối mạnh đó phải là thời gian trở lên. Làm điều này cho mỗi thành phần được kết nối mạnh mẽ, và mất sớm nhất trong những thời điểm đó. Sau đó, như là một tối ưu hóa, không cần phải xem xét bất kỳ lúc nào sớm hơn thời gian tìm kiếm nhị phân.n0n0j0j0

Tuy nhiên, hai tối ưu hóa này có thể sẽ không cung cấp nhiều hơn một tốc độ nhân tố không đổi nhỏ, tốt nhất.


Điều này là tốt - tôi cần một chút thời gian để có được thông tin chi tiết cùng nhau, nhưng tôi quá bận ATM và tôi đánh giá cao ý kiến ​​thứ hai :)
KWillets

@KWillets, thuật toán của bạn rất thông minh! Đồ tốt.
DW

6

Thuật toán của Kahn được biết đến với việc sắp xếp cấu trúc liên kết, nhưng nó cũng có thể được sử dụng để tìm các thành phần được kết nối mạnh. Tôi sử dụng nó để kiểm tra chu kỳ, trong đó không cần một loại cấu trúc liên kết thực tế. Trong biến thể này, các nút được loại bỏ khi chúng được truy cập, thay vì được thêm vào đầu ra và chấm dứt lỗi chỉ ra ít nhất một chu kỳ.

Nó hoạt động bằng cách cắt bỏ các nút gốc hoặc nút lá (có độ tương ứng 0 độ hoặc độ 0) từ biểu đồ một lần, cho đến khi không tìm thấy thêm nút nào thuộc loại đó. Vì quá trình này không thể cắt một chu kỳ (tất cả các nút trên một chu kỳ có mức độ không chính xác và outdegree> 0), nên nó kết thúc với một biểu đồ không trống nếu có ít nhất một chu kỳ.

Nếu chúng ta cắt cạnh e tối đa còn lại tại mỗi điểm kết thúc như vậy, chúng ta có thể tiếp tục quá trình để xem liệu E \ e có còn chứa một chu kỳ hay không, và cứ lặp đi lặp lại cho đến khi chúng ta cắt đủ các cạnh để tạo E theo chu kỳ. Cắt cạnh cuối cùng k là k đầu tiên làm cho E [1 ... k] tuần hoàn.

Thuật toán của Kahn thực hiện quy trình cắt lá này một cách tuyến tính bằng cách sử dụng hàng đợi các nút để xóa; nó được khởi tạo với các lá của toàn bộ biểu đồ (ít nhất là một trong số chúng phải tồn tại, nếu nó theo chu kỳ) và, khi mỗi cái bị xóa, (các) nút cha của nó được kiểm tra để xem chúng lần lượt trở thành lá và nếu vậy chúng được thêm vào hàng đợi làm việc. Bằng cách kiểm tra vùng lân cận của mỗi lần xóa, nó hoạt động tuyến tính thông qua biểu đồ, mà không cần lập chỉ mục các cấu trúc ngoài hàng đợi FIFO.

Chúng ta có thể thực hiện kiểm tra độ ẩm của hàng xóm giống như khi chúng ta cắt cạnh tối đa và thêm bất kỳ lá nào được tạo vào hàng đợi công việc, hoặc, nếu không được tạo, tiếp tục cắt.

Để thực hiện quy trình cắt tối đa tuyến tính, chúng ta phải tìm cạnh không bị xóa tối đa thông qua quá trình quét giảm dần của mảng cạnh, nhưng mỗi lần quét bắt đầu ở nơi trước đó bị tắt và thuật toán chỉ thực hiện một lần duy nhất toàn bộ mảng, do đó một phần là O (E) trong tổng số. Nhìn chung, tôi tin rằng phương pháp này là O (V + E), giống như của riêng Kahn.

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.