Các thuật toán hiệu quả cho vấn đề tầm nhìn dọc


18

Trong khi suy nghĩ về một vấn đề, tôi nhận ra rằng tôi cần tạo ra một thuật toán hiệu quả để giải quyết các nhiệm vụ sau:

Vấn đề: chúng ta được cung cấp một hộp vuông hai chiều của cạnh n có các cạnh song song với các trục. Chúng ta có thể nhìn vào nó thông qua đầu. Tuy nhiên, cũng có m đoạn ngang. Mỗi phân khúc có một số nguyên y Phối ( 0yn ) và x -coordinates ( 0x1<x2n ) và kết nối điểm (x1,y)(x2,y) (nhìn vào bức tranh dưới đây).

Chúng tôi muốn biết, đối với mỗi phân khúc đơn vị trên đỉnh hộp, chúng ta có thể nhìn sâu vào bên trong hộp như thế nào nếu chúng ta nhìn qua phân khúc này.

x{0,,n1}maxi: [x,x+1][x1,i,x2,i]yi

Ví dụ: cho và đoạn nằm trong hình bên dưới, kết quả là . Nhìn vào cách ánh sáng sâu có thể đi vào hộp.m = 7 ( 5 , 5 , 5 , 3 , 8 , 3 , 7 , 8 , 7 )n=9m=7(5,5,5,3,8,3,7,8,7)

Bảy phân đoạn;  phần bóng mờ cho biết vùng có thể đạt được bằng ánh sáng

May mắn cho chúng tôi, cả và đều khá nhỏ và chúng tôi có thể thực hiện các tính toán ngoại tuyến.mnm

Thuật toán đơn giản nhất giải quyết vấn đề này là brute-force: cho mỗi phân đoạn đi qua toàn bộ mảng và cập nhật nó khi cần thiết. Tuy nhiên, nó cho chúng ta không ấn tượng lắm .O(mn)

Một cải tiến tuyệt vời là sử dụng cây phân đoạn có khả năng tối đa hóa các giá trị trên phân khúc trong khi truy vấn và đọc các giá trị cuối cùng. Tôi sẽ không mô tả thêm, nhưng chúng ta thấy rằng độ phức tạp thời gian là .O((m+n)logn)

Tuy nhiên, tôi đã đưa ra một thuật toán nhanh hơn:

Đề cương:

  1. Sắp xếp các phân đoạn theo thứ tự giảm dần của phối hợp (thời gian tuyến tính bằng cách sử dụng một biến thể của sắp xếp đếm). Bây giờ lưu ý rằng nếu bất kỳ phân đoạn -unit nào đã được bao phủ bởi bất kỳ phân khúc nào trước đó, thì không có phân đoạn nào sau đây có thể ràng buộc chùm sáng đi qua phân khúc -unit này nữa. Sau đó, chúng tôi sẽ thực hiện quét dòng từ trên xuống dưới cùng của hộp.x xyxx

  2. Bây giờ chúng ta hãy giới thiệu một số định nghĩa: -unit phân khúc là một phân khúc ngang tưởng tượng trên quét mà -coordinates là các số nguyên và có chiều dài là 1. Mỗi phân khúc trong quá trình quét có thể là không rõ ràng (có nghĩa là, một chùm ánh sáng đi từ đầu hộp có thể đạt đến phân khúc này) hoặc được đánh dấu (trường hợp ngược lại). Hãy xem xét một phân đoạn -unit với , luôn không được đánh dấu. Chúng tôi cũng giới thiệu các bộ . Mỗi bộ sẽ chứa toàn bộ chuỗi các phân đoạn -unit được đánh dấu liên tiếp (nếu có) với dấu không được đánh dấu saux x x 1 = n x 2 = n + 1 S 0 = { 0 } , S 1 = { 1 } , Phong , S n = { n } xxxxx1=nx2=n+1S0={0},S1={1},,Sn={n} x bộ phận.

  3. Chúng tôi cần một cấu trúc dữ liệu có thể hoạt động trên các phân đoạn này và thiết lập hiệu quả. Chúng tôi sẽ sử dụng cấu trúc tìm kết hợp được mở rộng bởi một trường có chỉ số phân đoạn -unit tối đa (chỉ mục của phân đoạn không được đánh dấu ).x

  4. Bây giờ chúng ta có thể xử lý các phân khúc một cách hiệu quả. Giả sử bây giờ chúng tôi đang xem xét phân khúc thứ thứ tự (gọi là "truy vấn"), bắt đầu bằng và kết thúc bằng . Chúng ta cần tìm tất cả các phân đoạn -unit không được đánh dấu được chứa trong phân đoạn thứ (đây chính xác là các phân đoạn mà chùm sáng sẽ kết thúc theo cách của nó). Chúng tôi sẽ làm như sau: trước tiên, chúng tôi tìm thấy phân đoạn chưa được đánh dấu đầu tiên bên trong truy vấn ( Tìm đại diện của tập hợp chứa và lấy chỉ số tối đa của tập hợp này, đó là phân đoạn không được đánh dấu theo định nghĩa ). Sau đó, chỉ số nàyx 1ix1 x i x 1 x y x x + 1 x x 2x2 xix1xđược bên trong truy vấn, thêm nó vào kết quả (kết quả cho phân khúc này là ) và đánh dấu chỉ số này ( Liên minh bộ chứa và ). Sau đó lặp lại quy trình này cho đến khi chúng tôi tìm thấy tất cả các phân đoạn không được đánh dấu , nghĩa là, truy vấn Tìm tiếp theo cung cấp cho chúng tôi chỉ mục .yxx+1xx2

Lưu ý rằng mỗi hoạt động tìm kiếm kết hợp sẽ được thực hiện chỉ trong hai trường hợp: hoặc chúng tôi bắt đầu xem xét một phân khúc (có thể xảy ra lần) hoặc chúng tôi vừa đánh dấu phân khúc -unit (điều này có thể xảy ra lần). Do đó, độ phức tạp tổng thể là ( là hàm Ackermann nghịch đảo ). Nếu một cái gì đó không rõ ràng, tôi có thể giải thích thêm về điều này. Có lẽ tôi sẽ có thể thêm một số hình ảnh nếu tôi có thời gian.x n O ( ( n + m ) α ( n ) ) αmxnO((n+m)α(n))α

Bây giờ tôi đã đạt đến "bức tường". Tôi không thể đưa ra một thuật toán tuyến tính, mặc dù có vẻ như nó nên có một thuật toán. Vì vậy, tôi có hai câu hỏi:

  • Có một thuật toán thời gian tuyến tính (nghĩa là ) giải quyết vấn đề hiển thị phân đoạn ngang?O(n+m)
  • Nếu không, bằng chứng nào cho thấy vấn đề về tầm nhìn là ?ω(n+m)

Làm thế nào nhanh chóng để bạn sắp xếp các phân khúc m của bạn ?
babou

@babou, câu hỏi chỉ định sắp xếp đếm, như câu hỏi nói, chạy theo thời gian tuyến tính ("thời gian tuyến tính sử dụng biến thể của sắp xếp đếm").
DW

Bạn đã thử quét từ trái sang phải? Tất cả những gì bạn cần là sắp xếp trên và cả hai bước và để đi về bên phải. Vậy trong tổng . x 2 O ( m ) O ( m ) O ( m )x1x2O(m)O(m)O(m)
lệ_id

@invalid_id Vâng, tôi đã thử. Tuy nhiên, trong trường hợp này, đường quét phải phản ứng thích hợp khi nó gặp phần đầu của phân đoạn (nói cách khác, thêm số bằng với phân đoạn của nó vào đa phân đoạn ), đáp ứng phần cuối của phân đoạn (loại bỏ sự xuất hiện của -coordine) và xuất phân đoạn hoạt động cao nhất (giá trị tối đa đầu ra trong multiset). Tôi chưa nghe nói về bất kỳ cấu trúc dữ liệu nào cho phép chúng tôi thực hiện việc này trong thời gian không đổi (khấu hao). yyy
mnbvmar

@mnbvmar có thể là một gợi ý ngu ngốc, nhưng về một mảng có kích thước , bạn quét và dừng mọi ô . Đối với ô evry bạn biết max và bạn có thể nhập nó vào ma trận, hơn nữa bạn có thể theo dõi tối đa tổng thể bằng một biến. O ( n ) ynO(n)y
lệ_id

Câu trả lời:


1
  1. Trước sắp xếp cả và tọa độ của các dòng trong hai mảng riêng biệt và . x 2 A B O ( m )x1x2ABO(m)
  2. Chúng tôi cũng duy trì kích thước mảng bit phụ để theo dõi các phân đoạn hoạt động.n
  3. Bắt đầu quét từ trái sang phải:
  4. cho(i=0,i<n,i++)
  5. {
  6. ..if với giá trịy c O ( 1 )x1=iyc O(1)
  7. .. {
  8. .... tìm ( )max
  9. .... lưu trữ ( )O ( 1 )maxO(1)
  10. ..}
  11. ..if với giá trịx2=iyc O(1)
  12. .. {
  13. .... tìm ( )max
  14. .... lưu trữ ( )maxO(1)
  15. ..}
  16. }

find ( ) có thể được thực hiện bằng cách sử dụng một mảng bit với bit. Bây giờ bất cứ khi nào chúng tôi loại bỏ hoặc thêm một phần tử vào chúng tôi có thể cập nhật số nguyên này bằng cách đặt một bit thành đúng hoặc sai tương ứng. Bây giờ bạn có hai tùy chọn tùy thuộc vào ngôn ngữ lập trình được sử dụng và giả định tương đối nhỏ, tức là nhỏ hơn ít nhất là 64 bit hoặc một lượng cố định của các số nguyên này:maxnLnlonglongint

  • Nhận bit ít nhất có ý nghĩa trong thời gian liên tục được hỗ trợ bởi một số phần cứng và gcc.
  • Bằng cách chuyển đổi thành số nguyên bạn sẽ nhận được mức tối đa (không trực tiếp nhưng bạn có thể lấy được nó).LO(1)

Tôi biết đây là một hack khá vì nó giả sử giá trị tối đa cho và do đó có thể được coi là một hằng số sau đó ...nn


Như tôi thấy, giả sử bạn có bộ xử lý 64 bit x86, bạn chỉ có thể xử lý . Nếu theo thứ tự hàng triệu thì sao? n64n
mnbvmar

Sau đó, bạn sẽ cần nhiều số nguyên hơn. Với hai số nguyên, bạn có thể xử lý tối đa 128, v.v ... Vì vậy, bước tìm tối đa được ẩn trong số lượng số nguyên cần thiết, bạn vẫn có thể tối ưu hóa nếu nhỏ. Bạn đã đề cập trong câu hỏi của bạn rằng tương đối nhỏ nên tôi đoán nó không theo thứ tự hàng triệu. Theo định nghĩa, long long dài luôn có ít nhất 64 bit theo định nghĩa ngay cả trên bộ xử lý 32 bit. nO(m)mn
lệ_id

Tất nhiên là đúng, tiêu chuẩn C ++ định nghĩa long long intlà loại số nguyên ít nhất 64 bit. Tuy nhiên, sẽ không phải là nếu rất lớn và chúng ta biểu thị kích thước từ là (thường là ), thì mỗi cái sẽ mất thời gian ? Sau đó, chúng tôi sẽ kết thúc với tổng . nww=64findO(nw)O(mnw)
mnbvmar

Vâng, không may cho các giá trị lớn của đó là trường hợp. Vì vậy, bây giờ tôi tự hỏi làm thế nào lớn trong trường hợp của bạn và liệu nó có bị ràng buộc. Nếu nó thực sự theo thứ tự hàng triệu thì hack-around này sẽ không hoạt động nữa, nhưng nếu cho các giá trị thấp thì nó sẽ nhanh và thực tế là . Vì vậy, sự lựa chọn thuật toán tốt nhất là, như thường lệ, phụ thuộc đầu vào. Ví dụ: sắp xếp chèn thường nhanh hơn sắp xếp hợp nhất, ngay cả với thời gian chạy so với . n c w n c O ( n + m ) n 100 O ( n 2 ) O ( n log n )nncwncO(n+m)n100O(n2)O(nlogn)
lệ_id

3
Tôi bối rối bởi sự lựa chọn định dạng của bạn. Bạn biết bạn có thể gõ mã ở đây, phải không?
Raphael

0

Tôi không có thuật toán tuyến tính, nhưng thuật toán này dường như là O (m log m).

Sắp xếp các phân đoạn dựa trên tọa độ và chiều cao đầu tiên. Điều này có nghĩa là (x1, l1) luôn đến trước (x2, l2) bất cứ khi nào x1 <x2. Ngoài ra, (x1, l1) ở độ cao y1 đến trước (x1, l2) ở độ cao y2 bất cứ khi nào y1> y2.

Đối với mọi tập hợp con có cùng tọa độ đầu tiên, chúng tôi thực hiện như sau. Đặt đoạn đầu tiên là (x1, L). Đối với tất cả các phân đoạn khác trong tập hợp con: Nếu phân đoạn dài hơn phân đoạn đầu tiên, thì hãy thay đổi nó từ (x1, xt) thành (L, xt) và thêm nó vào tập hợp con L theo thứ tự phù hợp. Nếu không thì thả nó đi. Cuối cùng, nếu tập hợp con tiếp theo có tọa độ đầu tiên nhỏ hơn L, sau đó chia (x1, L) thành (x1, x2) và (x2, L). Thêm (x2, L) vào tập hợp con tiếp theo theo đúng thứ tự. Chúng ta có thể làm điều này bởi vì phân đoạn đầu tiên trong tập hợp con cao hơn và bao gồm phạm vi từ (x1, L). Phân đoạn mới này có thể là phân khúc bao gồm (L, x2), nhưng chúng tôi sẽ không biết điều đó cho đến khi chúng tôi xem xét tập hợp con có tọa độ đầu tiên L.

Sau khi chúng tôi chạy qua tất cả các tập hợp con, chúng tôi sẽ có một tập hợp các phân đoạn không trùng nhau. Để xác định giá trị Y là gì đối với một X đã cho, chúng ta chỉ phải chạy qua các phân đoạn còn lại.

Vì vậy, sự phức tạp ở đây là gì: Sắp xếp là O (m log m). Vòng lặp qua các tập con là O (m). Một tra cứu cũng là O (m).

Vì vậy, có vẻ như thuật toán này độc lập với n.

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.