3.3 Một số thuật toán cơ sở cho các đối tượng không gian
3.3.2 Giao của các polyline
Để giải quyết bài toán này, trước tiên cần xác định giao của các đoạn thẳng.
Cho tập S các đoạn thẳng, kiểm tra xem tồn tại giao của cặp đoạn thẳng
bất kỳ. Xét một cách tuần tự, thuật toán này cần thời gian thực hiện là O(n2) và
dừng khi phát hiện một giao điểm. Sử dụng thuật toán quét đoạn thẳng plan- sweep sẽ cải thiện được tốc độ, mức O(log(n)).
Kỹ thuật đoạn thẳng quét có thể phát hiện giao điểm của tập các đoạn thẳng nhờ vào thuộc tính sau. Giả sử vẽ một đoạn thẳng đứng, giao một số đoạn thẳng thuộc S (hình 3.5). Sử dụng một danh sách lưu các giao điểm đó sắp xếp
theo toạ độ y tại mỗi thời điểm.
s1
s2
Xét giao giữa hai đoạn thẳng s1, s2, nếu đường thẳng đứng l càng gần giao điểm này thì các giao của s1, s2 với l càng có nhiều khả năng liên tiếp trong danh
sách.
Thuộc tính này cho phép chúng ta không cần kiểm tra giao của một đoạn thẳng với tất cả các đoạn thẳng khác. Quét đường thẳng đứng l từ trái qua phải và duy trì một danh sách các đoạn thẳng đang có giao điểm với l, khi đó chỉ cần
kiểm tra giao của các đoạn thẳng xuất hiện kề nhau trong danh sách. Xây dựng một danh sách L và một danh sách sự kiện E lưu toạ độ x của các điểm mút. Có thể xảy ra hai trường hợp:
l gặp điểm bên trái của một đoạn thẳng s. Chèn s vào L, khi đó, nhiều nhất
hai đoạn thẳng trong L (trên và dưới s) được kiểm tra giao với s.
l gặp điểm bên phải của một đoạn thẳng s. Xoá s khỏi L, khi đó hai đoạn
thẳng lân cận với s được kiểm tra giao điểm với nhau.
SegmentIntersectionTest (S: tập các đoạn thẳng): boolean
begin
Sắp xếp 2n điểm mút của các đoạn thẳng trong S, đưa vào E
for (i = 1 to 2n) do begin
p = E[i]
if (p là điểm bên trái của s)
begin
Insert(s,L)
if (Above(s,L) giao với s) return true
end if
if (p là điểm bên phải của s)
begin
if (Above(s,L) giao với Below(s,L)) return true Delete(s,L)
end if
end for end
Trong thuật toán này, danh sách sự kiện E đơn giản là một mảng, danh
sách L cần có các thao tác chèn, xoá và các hàm Above, Below, do đó có thể sử
dụng cấu trúc cây. Trong trường hợp bài toán có kích thước đầu vào không lớn, việc sử dụng thuật toán nhạy cảm với kích thước đầu ra là một lựa chọn thích hợp. Có hai thuật toán loại này.
Thuật toán thứ nhất là sự mở rộng từ thuật toán đọan thẳng quét đã trình bày trên, thời gian thực hiện là O((n+k)log n), với k là số giao điểm.
Thuật toán thứ hai tính số giao điểm giữa hai tập đoạn thẳng, các đoạn thẳng trong mỗi tập là không giao nhau. Thuật toán này khá tối ưu, có thể đạt O(nlogn + k).
Với thuật toán thứ nhất, cần sử dụng hai điều kiện bổ sung:
Một giao điểm được xem như một sự kiện vì nó tạo nên thay đổi trong danh sách L. Do vậy, mỗi khi phát hiện một giao điểm, cần chèn nó vào
Khi đường thẳng quét gặp một giao điểm giữa s1, s2 thì hai đoạn thẳng này phải được đảo vị trí trong L. Và khi đó, tất cả các đoạn thẳng lân cận với
chúng phải được kiểm tra
SegmentIntersection (S: tập các đoạn thẳng): tập điểm
begin
Sắp xếp 2n điểm mút của các đoạn thẳng trong S, đưa vào E L = i
while (L <> i)
begin
p = Min(E) // lấy từ E
if (p là điểm bên trái của s)
begin
Insert(s,L)
s1 = Above(s,L), s2 = Below(s,L)
if (s1 giao với s) AddInter(s ∩ s1, E)
if (s2 giao với s) AddInter(s ∩ s2, E)
end if
if (p là điểm bên phải của s)
begin
s1 = Above(s,L), s2 = Below(s,L) Delete(s,L)
if (s1 giao với s2 về bên phải p) AddInter(s1 ∩ s2, E)
end if
if (p là giao s1, s2)
s3 = Above(Max(s1,s2), L); s4 = Below(Min(s1,s2)), L)
if (s3 giao Min(s1,s2))), AddInter(s3 ∩ Min(s1,s2)), E)
if (s4 giao Max(s1,s2))), AddInter(s4 ∩ Max(s1,s2)), E) Đổi chỗ s1, s2 trong L
end
if (s3 giao với s2) AddInter(s3 ∩ s2, E)
if (s2 giao với s) AddInter(s ∩ s2, E)
end
end
Trong thuật toán này, bước chuẩn bị (sắp xếp) thực hiện trong O(n log n).
Vòng lặp chính thực hiện 2n+k lần, với k là số giao điểm. Trong vòng lặp, mỗi thao tác cần O(log n). Nếu E được cài đặt như một cấu trúc hàng đợi ưu tiên. Do vậy, để thực hiện toàn bộ thuật toán, cần O((n+k)log n). Rõ ràng thuật toán này
nhanh hơn cách sử dụng vòng lặp khi số lượng giao điểm k nhỏ.
Mặc dù vậy, vẫn tồn tại một khoảng cách giữa độ phức tạp của thuật toán với trường hợp thuận lợi nhất. Vấn đề này được khắc phục khi sử dụng thuật toán thứ hai, xác định giao của hai tập hợp các đoạn thẳng. Có thể quy ước các đoạn thẳng trong hai tập có màu khác nhau, chẳng hạn xanh và đỏ, thuật toán này còn được gọi là thuật toán Red-blue intersection.
So với thuật toán trước, điểm sửa đổi đầu tiên của thuật toán này là ở chỗ nó sẽ duy trì hai danh sách Lr, Lb. Vì các đoạn thẳng cùng màu không giao nhau, nên các danh sách chỉ thay đổi khi gặp điểm mút của một đoạn thẳng. Tức là, danh sách sự kiện có thể được quản lý như một mảng tĩnh gồm 2n phần tử được sắp xếp (O(n log n)).
Để xác định các giao điểm của một đoạn thẳng trong tập này với các đoạn thẳng trong tập kia, giả sử đường màu đỏ r và tập xanh {bi}, khi đường thẳng quét l chạm đến điểm mút bên phải của r, trước khi loại bỏ r, tìm trong danh
sách Lb các đoạn thẳng giao với r. Có hai trường hợp:
Trường hợp 1 (3.6a). Tất cả các đoạn thẳng trong Lb giao với r đều liên
tiếp trong Lb. Khi đó, có thể duyệt trước và sau trong Lb cho đến khi gặp một đoạn thẳng không giao với r. Độ phức tập ở đây là O(n log n + k).
Trường hợp 2 (3.6b). Tất cả các đoạn thẳng giao với r không xuất hiện
liên tục trong Lb. Trong hình trên, đường b2 không giao với r nhưng lại nằm giữa
b1 và b3 là hai đường có giao r. Do đó, cần duyệt toàn bộ danh sách để xác định
các giao điểm. b3 b2 l b 3 b2 b1 r (a) b3 b2 b1 r l (b) b1 r l (c)
Hình 3.6. Kiểm tra giao của đường thẳng r với các đường khác
Việc giải quyết tốt bài toán xác định giao của hai tập hợp các đoạn thẳng cho phép dễ dàng xác định giao của hai polyline. Khi đó, mỗi polyline được xem như một tập hợp các đoạn thẳng có cùng một màu