Tại sao nói rằng tìm kiếm đầu tiên chạy trong thời gian


9

Người ta thường nói (ví dụ, trong Wikipedia ) rằng thời gian chạy của tìm kiếm đầu tiên (BFS) trên biểu đồ là . Tuy nhiên, bất kỳ biểu đồ được kết nối nào cũng có và, ngay cả trong biểu đồ không được kết nối, BFS sẽ không bao giờ nhìn vào một đỉnh bên ngoài thành phần có chứa đỉnh bắt đầu. Thành phần đó chứa nhiều nhất các cạnh, do đó, nó chứa tối đa các đỉnh và đó là những đỉnh duy nhất mà thuật toán sẽ truy cập.G=(V,E)O(|V|+|E|)|V||E|+1|E||E|+1

Điều này có nghĩa là , vậy tại sao chúng ta không nói rằng thời gian chạy chỉ là ?|V|+|E|2|E|+1O(|E|)

Điều này xuất hiện trong các bình luận về một câu hỏi về thời gian chạy thuật toán của Disjkstra .


Tại sao bạn cho rằng có một đỉnh bắt đầu? Ví dụ, BFS trong bài toán khớp tối đa bắt đầu từ tất cả các đỉnh chưa từng có trong thuật toán karp hopcroft. Trong trường hợp này, nếu biểu đồ đã cho là rừng gồm nhiều thành phần được kết nối, chúng ta sẽ có nhiều đỉnh hơn so với edgers và chúng ta sẽ truy cập tất cả
narek Bojikian

2
@narekBojikian Mặc dù BFS có thể được sử dụng theo nhiều cách khác nhau, khi được trình bày dưới dạng thuật toán độc lập, nó luôn luôn có một đỉnh bắt đầu.
David Richerby

Câu trả lời:


9

BFS thường được mô tả một cái gì đó như sau (từ Wikipedia ).

 1  procedure BFS(G,start_v):
 2      let Q be a queue
 3      label start_v as discovered
 4      Q.enqueue(start_v)
 5      while Q is not empty
 6          v = Q.dequeue()
 7          if v is the goal:
 8              return v
 9          for all edges from v to w in G.adjacentEdges(v) do
10             if w is not labeled as discovered:
11                 label w as discovered
12                 w.parent = v
13                 Q.enqueue(w)

Vấn đề là một vấn đề hơi khó hiểu: nó ẩn trong dòng 3! Câu hỏi là, chúng ta sẽ sử dụng cấu trúc dữ liệu nào để lưu trữ các đỉnh đã được phát hiện?

Giải pháp đơn giản nhất là sử dụng mảng Boolean với một mục nhập trên mỗi đỉnh. Trong trường hợp này, chúng ta phải khởi tạo mọi phần tử của mảng falsevà điều này sẽ mất thời gian . Điều này áp dụng cho mọi biểu đồ, ngay cả khi không có cạnh nào cả, vì vậy chúng tôi không thể giả sử bất kỳ mối quan hệ nào giữavà và chúng ta có thời gian chạy .Θ(|V|)|V||E|O(|V|+|E|)

Chúng ta có thể tránh việc có cấu trúc dữ liệu với thời gian khởi tạo không? Nỗ lực đầu tiên của chúng tôi có thể là sử dụng một danh sách liên kết. Tuy nhiên, bây giờ kiểm tra nếu một đỉnh đã được phát hiện (dòng 10) cần thời gian tuyến tính theo số lượng đỉnh được truy cập, thay vì thời gian không đổi như trước. Điều này có nghĩa là thời gian chạy trở thành , tệ hơn nhiều trong trường hợp xấu nhất. (Lưu ý rằng chúng tôi không muốn viết lại thành vì điều đó thậm chí còn tệ hơn: nó có thể tệ như , trong khi )Θ(|V|)O(|V||E|)O(|E|2)|V|4|V||E||V|3

Sử dụng một mảng được thay đổi kích thước động sẽ cho phép chúng tôi giữ danh sách được sắp xếp, vì vậy bây giờ việc tra cứu sẽ chỉ mất thời gian nhưng điều đó vẫn cho thời gian chạy chỉ , mà vẫn còn tệ hơn so với tiêu chuẩn.O(log|V|)O(|E|log|V|)

Cuối cùng, chúng ta có thể sử dụng bảng băm có kích thước động: bắt đầu với một bảng có kích thước không đổi  và nhân đôi mỗi khi nó đầy một nửa. Điều này có nghĩa là kích thước cuối cùng của bảng nhiều nhất gấp đôi số đỉnh được phát hiện trước khi thuật toán kết thúc và điều này nhiều nhất là vì chúng ta không bao giờ phát hiện ra bất cứ điều gì bên ngoài thành phần của đỉnh bắt đầu. Hơn nữa, tổng số lượng công việc đã thực hiện sao chép bảng băm để mở rộng nó nhiều nhất là. Tra cứu và chèn vào bảng băm được khấu hao để chúng tôi thực sự có được thời gian chạy của .c|E|+1c+2c+4c++2|E|4|E| O(1)O(|E|)

Vì vậy, là có thể, nhưng muốn thực hiện điều đó trong một triển khai thực sự? Tôi có thể nói có lẽ là không. Trừ khi bạn có lý do để tin rằng đồ thị đầu vào của bạn sẽ có nhiều thành phần nhỏ, chi phí duy trì bảng băm sẽ thêm một yếu tố không đổi đáng chú ý vào thời gian chạy. Phát triển bảng băm có thể mất thời gianvà tra cứu sẽ yêu cầu bạn tính toán hàm băm và, trung bình, nhìn vào nhiều hơn một vị trí trong bảng. Hiệu suất bộ nhớ cache kém của các bảng băm cũng có thể làm tổn thương bạn trên một máy tính thực. Trong hầu hết các trường hợp với việc triển khai mảng tiêu chuẩn, phần là thuật ngữ chi phối củaO(|E|)4|E|O(|E|)O(|V|+|E|) thời gian hoạt động không đáng để sử dụng bảng băm để loại bỏ thuật ngữ thống trị, do chi phí thực tế để làm việc này.


1
Tôi nghĩ rằng nó có thể quá mạnh để tuyên bố rằng các bảng băm trong thực tế có hiệu suất bộ đệm kém. Nếu được thực hiện với chuỗi (tức là danh sách được liên kết), tôi đồng ý. Nhưng nếu được thực hiện với một đoạn bộ nhớ liên tục và địa chỉ mở, không quá nhiều.
Juho

Câu trả lời tuyệt vời thực sự! Mặc dù vậy, một lưu ý cận biên, các bảng băm có kích thước động thực sự là một lựa chọn tốt không chỉ nếu có nhiều thành phần nhỏ, mà còn nếu giá trị băm cho bất kỳ đỉnh nào bị giới hạn bởi một hằng số hợp lý và điều này xảy ra thường xuyên. Rất vui được trả lời!
Carlos Linares López

1
David, tôi đã có những suy nghĩ tương tự nhiều năm trước. Tôi nghĩ rằng câu trả lời nằm trong quan điểm lịch sử.
kelalaka
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.