Thuật toán quét Graham bắt đầu bằng việc xây dựng một đa giác khép kín đơn từ tập hợp điểm đã cho. Từ đa giác đơn tìm được ta tìm cách lấy ra các điểm nằm trên bao lồi theo cách: Thử đặt một điểm vào bao lồi rồi kiểm tra những điểm trước đó xem có còn thuộc bao hay không nếu không thuộc bao thì ta loại các điểm này ra; nếu thuộc bao thì ta giữ lại. Để làm được việc này ta thực hiện như sau: Khi đặt một điểm Pi vào bao lồi, ta lần quanh điểm đã được đưa vào bao lồi trước đó Pi-1 với Pi-2 (tức là xem xét ba điểm PiPi-1Pi-2) và kiểm tra nó có quay phải (theo chiều kim đồng hồ) hay không; nếu có quay phải thì chúng ta loại điểm Pi-1 ra khỏi bao lồi, rồi cứ tiếp tục như vậy với Pi-2 (PiPi-2Pi-3),. cho đến khi nào không còn quay phải nữa. Quá trình lặp lại cho đến khi điểm cần xét cuối cùng PN+1.
28 trang |
Chia sẻ: netpro | Lượt xem: 8423 | Lượt tải: 2
Bạn đang xem trước 20 trang tài liệu Đề tài Các thuật toán cơ bản trong Pascal, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
0 thì hai đoạn thẳng có hướng và cộng tuyến.
Ngược chiều kim đồng hồ
Cùng chiều kim đồng hồ
Hình 1.3. Dùng đại lượng C(, ) để xác định chiều quay của các đoạn thẳng có hướng liên tiếp và
1.6. Xác định hai đoạn thẳng giao nhau
Để kiểm tra hai đoạn thẳng và có giao nhau hay không ta tiến hành qua hai giai đoạn. Giai đoạn thứ nhất là phép loại nhanh các đoạn thẳng không thể giao nhau - đó là trường hợp hai hình chữ nhật định cận không giao nhau. Hình chữ nhật định cận của một hình là hình chữ nhật nhỏ nhất chứa hình đó và có các cạnh song song với hai trục toạ độ. Với đoạn thẳng thì hình chữ nhật định cận là hình chữ nhật được biểu diễn thông qua hai điểm và , trong đó , , , . Hai hình chữ nhật định cận của và là (, ) và (, ) giao nhau nếu và chỉ nếu phép hội là đúng.
Các hình chữ nhật phải giao nhau theo cả hai chiều; chiều của trục x (thể hiện ở hai phép so sánh đầu tiên) và chiều của trục y (thể hiện ở hai phép so sánh cuối cùng).
Giai đoạn thứ hai để xác định hai đoạn thẳng có giao nhau hay không khi biết các hình chữ nhật định cận đã giao nhau. Để biết được điều này, chúng ta xét xem mỗi đoạn thẳng có nằm trên đường thẳng chứa đoạn thẳng kia hay không. Nếu thoả mãn điều kiện trên thì ta kết luận hai đoạn thẳng giao nhau.
a)P4
P3
P2
P1
b)P2
P3
P4
P1
c)P2
P3
P4
P1
d)
P2
P1
P3
P4
Hình 1.4. Xác định xem đoạn thẳng nằm trên đường thẳng chứa đoạn thẳng . (a) Nếu nó nằm trên, thì các dấu của các và khác nhau; (b) Nếu nó không nằm trên, thì các dấu của hai đại lượng trên là giống nhau; (c)-(d) Tồn tại một đại lượng bằng 0 nếu có ít nhất một cận của nằm trên đường thẳng kia.
Ta có thể dùng phương pháp tích vectơ để xác định đoạn thẳng có nằm trên đường thẳng chứa đoạn thẳng hay không. Như đã nêu trong hình 1.4. (a) và (b), ta xét xem các đoạn thẳng có hướng và có hướng trái ngược nhau tương đối với hay không. Tức là, dấu của đại lượng và có khác nhau hay không. Nếu có, thì đoạn thẳng nằm trên đường thẳng, ngược lại thì đoạn thẳng không nằm trên đường thẳng. Trong trường hợp hoặc nằm trên đường thẳng chứa đoạn thẳng thì sẽ có một đại lượng hoặc bằng 0. Ta gọi đây là trường hợp cận như đã thấy trong hình 1.4. (c) và (d). Độ dài một đoạn thẳng bằng 0 khi và chỉ khi bằng 0.
Sau đây là đoạn chương trình minh hoạ hai đoạn thẳng có giao nhau hay không. Giá trị trả về là 1 nếu hai đoạn thẳng giao nhau; 0 nếu hai đoạn thẳng không giao nhau.
FUNCTION Intersect(L1,L2 : Line) : Boolean;
Var x1, x2, x3, x4, y1, y2, y3, y4 : Integer;
Begin
x1:=Min(L1.P1.x,L1.P2.x);
y1:=Min(L1.P1.y,L1.P2.y);
x2:=Max(L1.P1.x,L1.P2.x);
y2:=Max(L1.P1.y,L1.P2.y);
x3:=Min(L2.P1.x,L2.P2.x);
y3:=Min(L2.P1.y,L2.P2.y);
x4:=Min(L2.P1.x,L2.P2.x);
y4:=Min(L2.P1.y,L2.P2.y);
If (x2>x3) and (x4>x1) and (y2>y3) and (y4>y1) Then
If (ccw(l1.p1,l2.p2,l1.p2)*ccw(l1.p1,l2.p1,l1.p2)<=0)
and (ccw(l2.p1,l1.p2,l2.p2)*ccw(l2.p1,l1.p1,l2.p2)<=0) Then
Intersect:=True
Else Intersect:=False
Else Intersect:=False;
End;
2. Các thuật toán cơ bản
2.1. Đường khép kín đơn
Bài toán:
Cho một tập N điểm xác định trên mặt phẳng, các điểm đôi một không trùng nhau. Yêu cầu: Tìm một đường đi (là các đoạn thẳng nối các điểm đã cho lại với nhau) qua tất cả các điểm rồi quay trở về điểm ban đầu sao cho chúng không cắt nhau. Đường đi như trên được gọi là đường khép kín đơn.
Bài toán tìm đường đi khép kín đơn được ứng dụng trong nhiều lĩnh vực như tìm đường đi cho người đưa thư đi đến mỗi nhà mà không đi qua những đường đã đi, hay bài toán về cách vẽ các điểm bằng plotter sao cho hợp lý, ...
a)A
M
I
K
G
F
E
D
C
B
H
J
L
Đường khép kín đơn
ABCDEFGHIJKLM
b)A
M
I
K
G
F
E
D
C
B
H
J
L
Không là đường khép kín đơn
AFBCDEGHIJKLM
Hình 2.1. a) Đường khép kín đơn và b) Không là đường khép kín đơn
Để tìm đường khép kín đơn đi qua N điểm từng đôi một khác nhau A1, A2, ..., An đã cho trên mặt phẳng toạ độ. Đầu tiên, ta chọn một điểm làm gốc (thường là điểm có tung độ bé nhất). Tiếp đó tìm các góc của các đoạn thẳng nối giữa gốc và các điểm Ai còn lại so với trục hoành. Như vậy, các góc này có giá trị nằm trong đoạn [0, 360].
Sau khi đã tìm được các góc tương ứng với các điểm Ai, ta sắp xếp các điểm Ai theo thứ tự tăng dần của góc, nếu có nhiều góc bằng nhau thì sắp xếp ưu tiên điểm gần gốc trước, điểm xa gốc sau trừ các góc cuối cùng. Đối với các góc cuối cùng thì chúng ta ưu tiên điểm xa góc toạ độ trước. Với thứ tự đó chúng ta có một đường khép kín đơn đi qua tất cả các điểm đã cho.
y
A5
A0
A4
A2
A3
x
A1
Hình 2.2. Tính các góc thông qua đường chuẩn của gốc
Thứ tự là: A0, A1, A3, A4, A2, A5
Bây giờ chúng ta sẽ tìm hiểu cách tính các góc ứng với các điểm. Gọi dx, dy là khoảng cách từ điểm gốc đến một điểm cần tính góc tương ứng theo trục hoành (trục x) và trục tung (trục y) thì gốc cần tính ở đây là tang-1(dy/dx). Mặc dù đã có nhiều ngôn ngữ hỗ trợ tính hàm tang-1 (arctang), nhưng nó thường chậm, hơn nữa chúng ta lại gặp những rắc rối khi dx = 0 và điểm đang xét nằm trong góc phần tư nào. Với mục đích chỉ dùng để sắp xếp các điểm trong phần này, chúng ta có thể sử dụng một hàm khác dễ tính hơn nhưng vẫn có cùng thuộc tính như hàm arctang, đó là hàm dy/(dx+dy). Sau đây là đoạn chương trình trả về một giá trị trong đoạn [0, 360] có tính chất tương tự như góc tạo bởi đường thẳng so với phương ngang.
FUNCTION theta(P1, P2 :Point) : Real;
Var dx, dy, ax, ay : Integer;
t : Real;
Begin
dx:=P2.x–P1.x;
dy:=P2.y–P1.y;
ax:=abs(dx);
ay:=abs(dy);
If (dx=0) and (dy=0) Then t:=0
Else t:=dy/(ax+ay);
If dx<0 Then t:=2–t
Else
If dy<0 Then t:=4+t;
theta:=t*90.0;
End;
Sau đây là thuật toán tìm đa giác đơn khép kín từ một tập hợp điểm đã cho, thuật toán này thực chất chỉ là một phép sắp xếp theo các góc theta đối với một điểm góc đã được chọn trước (đó là điểm có tung độ y nhỏ nhất, nếu trường hợp có nhiều điểm như vậy thi ta chọn điểm có hoành độ x lớn nhất). Để đơn giản chúng tôi làm một thuật toán sắp xếp quen thuộc thông thường. Chúng ta cũng cần phải có một thủ tục để dùng để sắp xếp chi tiết khi có các góc bằng nhau. Thủ tục ArangeDetail có nhiệm vụ sắp xếp các điểm có cùng góc theo thứ tự tăng dần của khoảng cách đối với điểm gốc nếu như góc đó không phải là góc cuối cùng và sắp xếp theo thứ tự giảm dần nếu như đó là góc cuối.
PROCEDURE ArrangeDetail(Var Poly :Polygon, N : Integer);
Var i, j, k ,h : Integer;
P : Point;
Begin
i:=1;
While (i<=n) do
Begin
j:=i;
While (i<n) and
(theta(Poly[1],Poly[i])=theta(Poly[1],Poly[i+1])) do
i:=i+1;
If in Then
Begin
If i>j Then
For k:=j to i-1 do
For h:=k+1 to i do
If dist(Poly[1],Poly[k])>dist(Poly[1],Poly[h]) Then
Begin {dist là hàm khoảng cách giữa hai điểm}
P:=Poly[k];
Poly[k]:=Poly[h];
Poly[h]:=P;
End;
End
Else
If i>j Then
For k:=j to i-1 do
For h:=k+1 to i do
If dist(Poly[1],Poly[k])<dist(Poly[1],Poly[h]) Then
Begin
P:=Poly[k];
Poly[k]:=Poly[h];
Poly[h]:=P;
End;
i:=i+1;
End;
End;
PROCEDURE simpleclosepath(Var Poly :Polygon, N : Integer);
Var i, j : Integer;
P : Point;
Begin
Min:=1;
For i:=2 to n do
If Poly[i].y<Poly[min].y Then
Min:=i;
For i:=1 to n do
If (Poly[i].y=Poly[Min].y) and (Poly[i].x>Poly[Min].x Then
Min:=i;
P:=Poly[1];
Poly[1]:=Poly[Min];
Poly[Min]:=P;
For i:=2 to n-1 do
For j:=i+1 to n do
If theta(Poly[1], Poly[i])>theta(Poly[1], Poly[j]) Then
Begin
P:=Poly[i];
Poly[i]:=Poly[j];
Poly[j]:=P;
End;
ArrangeDetail(Poly, n);
End;
2.2. Thuật toán tìm bao lồi
Bao lồi của một tập hợp điểm Q là đa giác lồi P nhỏ nhất chứa tất các điểm của Q. Như vậy bất kỳ một điểm nào của tập hợp Q cũng nằm trong P hoặc trên biên của P và nếu ta lấy bất kỳ hai điểm nào của Q nối lại với nhau thì đoạn thẳng nối hai điểm này nằm trong P. Theo trực giác, ta có thể hình dung mỗi điểm của Q như một chiếc đinh lồi ra từ tấm ván. Bao lồi của Q là hình dáng của dãi cao su bao quanh các chiếc đinh đó.
c. Thuật toán diễu hành Jarvis (Thuật toán bao gói)
Thuật toán diễu hành Jarvis tính toán bao lồi của một tập hợp Q các điểm bằng một kỹ thuật có tên là đóng gói.
Thuật toán bao gói để tìm bao lồi là một thuật toán đơn giản và khá tự nhiên. Bắt đầu từ một điểm chắc chắn nằm trên bao lồi (điểm mốc), điểm này có thể là điểm có tung độ nhỏ nhất (hoặc tung độ lớn nhất, hoặc hoành độ nhỏ nhất, hoặc hoành độ lớn nhất). Dùng một tia quét ngược chiều kim đồng hồ cho đến khi đụng một điểm khác: điểm mới này phải thuộc bao lồi. Lấy điểm này làm mốc và tiếp tục quét cho đến khi gặp điểm tiếp theo, quá trình tiếp tục cho đến khi gặp lại điểm ban đầu. Như vậy ta thấy rằng, hàm theta vừa thiết lập ở trên là một trong những công cụ để ta làm tia quét.
Thuật toán chạy trong thời gian O(hN), trong đó h là số đỉnh của bao lồi. Do vậy, thuật toán diễu hành Jarvis hội tụ nhanh hay chậm phụ thuộc vào số đỉnh của bao lồi P của tập hợp điểm Q. Thuật toán này có nhược điểm là khi tất cả các điểm đều thuộc bao lồi thì thời gian thực hiện của nó là N2 (với N là số điểm của tập cần tìm bao lồi).
Bắt đầu từ một điểm P0 là điểm có tung độ y nhỏ nhất (trong trường hợp có nhiều điểm có tung độ y nhỏ nhất thì ta lấy điểm có hoành độ x lớn nhất), ta tìm điểm P1 có góc cự bé nhất đối với điểm P0 (trong trường hợp có nhiều góc cự bé nhất bằng nhau thì ta có thể lấy điểm xa nhất hoặc gần nhất so với P0 tuỳ theo từng yêu cầu cụ thể). Tiếp đến ta tìm điểm P2 là điểm có góc cực bé nhất đối với điểm P1. Quá trình cứ tiếp tục như vậy cho đến khi đạt đến điểm có hoành độ lớn nhất Pk, và như vậy chúng ta đã kiến tạo được xích phải của bao lồi. Để kiến tạo xích trái, ta bắt đầu từ điểm Pk và chọn Pk+1 với tư cách là điểm có góc cực bé nhất đối với Pk, nhưng so với trục x âm. Cứ tiếp tục như thế cho đến khi gặp điểm Po ban đầu. Tuy nhiên trong khi cài đặt chương trình chúng ta không cần phải chia ra thành hai phần xích trái và xích phải, mà ta gộp chung vào thành một. Rõ ràng điều này có thể thực hiện được bởi vì khi tính các điểm trên xích trái thì góc cực bé nhất đối với trục x âm cũng tương đương với góc cực ứng với trục x dương. Chúng ta có thể hình dung bằng hai hình vẽ minh hoạ sau:
Hình 2.3. a. Hình ảnh các góc cực với xích trái và xích phải
b. Hình ảnh các góc cực không phân biệt xích trái hay xích phải
Sau đây là đoạn chương trình minh hoạ cho thuật toán diễu hành Jarvis. Kết quả trả về của chương trình là số M (số điểm của bao lồi) và bao lồi là tập các điểm P1, P2, ..., PM.
FUNCTION jarvis(Var Poly : Polygon, n : Integer) : Integer;
Var i, j, Min, M : Integer;
Minangle : Real;
P : Point;
Begin
Min:=1;
For i:=2 to n do
If Poly[i].y<Poly[min].y Then
Min:=i;
For i:=1 to n do
If (Poly[i].y=Poly[Min].y) and (Poly[i].x>Poly[Min].x Then
Min:=i;
P:=Poly[1];
Poly[1]:=Poly[Min];
Poly[Min]:=P;
Poly[N+1]:=Poly[1];
j:=2;
Repeat
M:=j-1;
Min:=j;
Minagle:=theta(Poly[M], Poly[j]);
For i:=j+1 to n+1 do
If Minagle>theta(Poly[M], Poly[i]) Then
Begin
Min:=i;
Minagle:=theta(Poly[M], Poly[i]);
End;
P:=Poly[j];
Poly[j]:=Poly[Min];
Poly[Min]:=P;
j:=j+1;
Until Min=N+1
jarvis:=M;
End;
b. Thuật toán quét graham
Thuật toán quét Graham bắt đầu bằng việc xây dựng một đa giác khép kín đơn từ tập hợp điểm đã cho. Từ đa giác đơn tìm được ta tìm cách lấy ra các điểm nằm trên bao lồi theo cách: Thử đặt một điểm vào bao lồi rồi kiểm tra những điểm trước đó xem có còn thuộc bao hay không nếu không thuộc bao thì ta loại các điểm này ra; nếu thuộc bao thì ta giữ lại. Để làm được việc này ta thực hiện như sau: Khi đặt một điểm Pi vào bao lồi, ta lần quanh điểm đã được đưa vào bao lồi trước đó Pi-1 với Pi-2 (tức là xem xét ba điểm PiPi-1Pi-2) và kiểm tra nó có quay phải (theo chiều kim đồng hồ) hay không; nếu có quay phải thì chúng ta loại điểm Pi-1 ra khỏi bao lồi, rồi cứ tiếp tục như vậy với Pi-2 (PiPi-2Pi-3),... cho đến khi nào không còn quay phải nữa. Quá trình lặp lại cho đến khi điểm cần xét cuối cùng PN+1.
H
G
C
A
B
E
D
F
H
G
C
A
B
E
D
F
H
G
C
A
B
E
D
F
H
G
C
A
B
E
D
F
H
G
C
A
B
E
D
F
H
G
C
A
B
E
D
F
H
G
C
A
B
E
D
F
H
G
C
A
B
E
D
F
H
G
C
A
B
E
D
F
Hình 2.4. Tiến trình của thuật toán quét Graham tìm bao lồi của một tập hợp điểm
Việc cài đặt thuật toán không mấy phức tạp, đầu tiên ta dùng một thuật toán sắp xếp lại các điểm theo thứ tự tăng dần của các góc so với một điểm gốc để được một đa giác đơn khép kín. Tiếp theo ta thực hiện việc đặt vào các điểm và loại bỏ các điểm để có được bao lồi. Thuật toán có thể minh hoạ như sau:
FUNCTION grahamscan(Var Poly : Polygon, n : Integer) : Integer;
Var i, M : Integer;
P : Point;
Begin
simpleclosepath(Poly, n);
M:=3;
For i:=4 to n do
Begin
While ccw(Poly[M], Poly[M-1], Poly[i])=1 Do
M:=M-1;
M:=M+1;
P:=Poly[M]; Poly[M]:=Poly[i]; Poly[i]:=P;
End;
Grahamscan:=M;
End;
2.3. Điểm nằm trong đa giác
Để xét xem một điểm có P nằm trong đa giác hay không, chúng ta có thể sử dụng hai thuật toán sau:
- Thuật toán số lần cắt (Crossing Number - cn)
Thuật toán số lần cắt đếm số lần một tia xuất phát từ điểm P cắt cạnh của đa giác. Điểm P nằm ngoài đa giác thì số lần cắt là chẵn; ngược lại khi số lần cắt là lẻ thì điểm P nằm trong đa giác. Thuật toán này nhiều khi còn được gọi là thuật toán kiểm tra chẵn lẻ.
- Thuật toán số lần bao gói (Winding Number - wn)
Thuật toán này đếm số lần đa giác bao quanh điểm P. Nếu số lần bao gói lớn hơn 0 thì ta kết luận điểm P nằm trong đa giác; ngược lại thì kết luận điểm P nằm ngoài đa giác.
Nếu đa giác chúng ta xét là đa giác đơn (nghĩa là nó không tự cắt), thì cả hai phương pháp trên cho cùng một kết quả như nhau với tất cả các điểm P cần kiểm tra. Nhưng với những đa giác không đơn (nghĩa là nó tự cắt), thì hai phương pháp có thể cho kết quả khác nhau đối với một số điểm. Ví dụ, khi một đa giác có phần chồng lên chính nó, thì những điểm P ở vùng chồng nhau được cho là nằm ngoài đa giác đối với thuật toán số lần cắt (do có số lần cắt các cạnh đa giác là chẵn), nhưng đối với thuật toán bao gói thì điểm P là nằm trong (do có số lần bao gói lơn hơn 0). Chúng ta có thể thấy rõ qua hình ảnh minh hoạ sau:
Hình 2.5. a. Thuật toán số điểm cắt
b. Thuật toán số lần bao gói
Trong hình vẽ ví dụ này, chúng ta thấy những điểm nằm trong phần chồng nhau của đa giác thì thuật toán số lần bao gói cho kết quả wn=2, nghĩa là nó nằm trong đa giác hai lần. Tuy nhiên đối với thuật toán số điểm cắt thì cn là chẵn, vì vậy nó không nằm trong đa giác. Rõ ràng, thuật toán số lần bao gói tốt hơn so với thuật toán số điểm cắt, nhưng việc cài đặt khó hơn nhiều và độ phức tạp thuật toán cao hơn.
a. Thuật toán số lần cắt
Như đã đề cập ở trên, thuật toán này đếm số lần một tia xuất phát từ điểm P cần kiểm tra cắt các cạnh của đa giác. Nếu số lần cắt là chẵn, thì điểm P nằm ngoài đa giác; ngược lại số lần cắt lẻ thì điểm P nằm trong đa giác. Với một tia xuất phát từ P, thì rõ ràng điểm cuối của nó phải nằm ngoài đa giác. Vì vậy, nếu một điểm nằm trong đa giác thì chuỗi tuần tự của nó khi tia cắt các cạnh của đa giác như sau: in®out®...®in®out. Nếu một điểm nằm ngoài đa giác thì chuỗi tuần tự khi tia cắt các cạnh như sau: out®in®...®in®out. Có thể thấy điều này qua hình vẽ minh hoạ sau:
Hình 2.6. Điểm nằm trong đa giác có số lần cắt các cạnh của đa giác là lẻ, điểm nằm ngoài đa giác có số lần cắt các cạnh của đa giác là chẵn.
Tuy nhiên chúng ta cần phải lưu ý một số trường hợp đặc biệt như tia bắt đầu từ P đi qua đỉnh của đa giác, nằm chồng trên một cạnh của đa giác. Chúng ta có thể minh hoạ các trường hợp thông qua hình vẽ sau:
Hình 2.7. Các trường hợp đặc biệt cần xử lý khi tính cn của thuật toán số lần cắt.
Đối với trường hợp a, tia kiểm tra đi qua một đỉnh của đa giác có hai cạnh nằm về hai phía, chúng ta đếm số lần cắt là 1.
Đối với trường hợp b và c, tia kiểm tra đi qua một đỉnh của đa giác có hai cạnh nằm về một phía, chúng ta đếm số lần cắt là 0.
Đối với trường hợp d, tia kiểm tra trùng một cạnh của đa giác có hai cạnh nối với cạnh trùng (có tia kiểm tra đi qua) nằm khác phía, chúng ta đếm số lần cắt là 1 (cũng có thể áp dụng cho nhiều cạnh trùng liên tiếp). Nếu điểm P nằm trên cạnh trùng với tia kiểm tra thì số lần cắt được đếm là 1.
Đối với trường hợp e và f, tia kiểm tra trùng một cạnh của đa giác có hai cạnh nối với cạnh trùng (có tia kiểm tra đi qua) nằm cùng phía, chúng ta đếm số lần cắt là 0 (cũng có thể áp dụng cho nhiều cạnh trùng liên tiếp). Nếu điểm P nằm trên cạnh trùng với tia kiểm tra thì số lần cắt được đếm là 1.
Để cho thuật toán đơn giản tia xuất phát từ P ta lấy là tia nằm ngang (song song với trục x, nghĩa là điểm cuối của tia có toạ độ (maxint, P.y)), điều này sẽ thuận lợi cho việc nhận biết giao điểm của các cạnh với tia kiểm tra có nằm ở các đỉnh của đa giác hay không, hoặc một cạnh có trùng với tia kiểm tra hay không.
Khi tia kiểm tra giao với một cạnh PiPi+1 của đa giác, nếu Pi.y hoặc Pi+1.y bằng P.y thì có một đỉnh (tương ứng Pi hoặc Pi+1) của cạnh đó; nếu Pi.y và Pi+1.y bằng P.y thì cạnh này trùng với tia kiểm tra; còn nếu Pi.y và Pi+1.y khác P.y thì tia kiểm tra cắt cạnh PiPi+1 tại một điểm giữa cạnh này. Sau đó tiến hành kiểm tra tính cùng phía hay khác phía của các cạnh liên quan.
Đối với trường hợp tia kiểm tra đi qua một đỉnh Pi+1 của đa giác (nghĩa là Pi+1.y = P.y) ta sẽ xét đối với các cạnh: PiPi+1, Pi+1Pi+2, nếu (Pi.y - P.y)´(Pi+2.y - P.y)>0 thì hai cạnh này cùng phía so với tia kiểm tra, ngược lại hai cạnh này khác phía so với tia kiểm tra.
Đối với trường hợp tia kiểm tra đi qua một cạnh PiPi+1 của đa giác (nghĩa là Pi.y = Pi+1.y = P.y) ta sẽ xét với các cạnh: Pi-1Pi, Pi+1Pi+2, nếu (Pi-1.y - P.y)´(Pi+2.y - P.y)>0 thì hai cạnh này cùng phía với nhau so với tia kiểm tra, ngược lại hai cạnh này khác phía với nhau so với tia kiểm tra.
Trong thuật toán sau đây ta đã hoán đổi đa giác ban đầu về một đa giác cùng hình dạng nhưng đỉnh bắt đầu là đỉnh Q[1]. Q[1] là đỉnh có toạ độ y nhỏ nhất. Trong trường hợp có nhiều đỉnh có cùng toạ độ y nhỏ nhất thì ta lấy đỉnh có toạ độ x nhỏ nhất. Trong cài đặt thuật toán, ta sử dụng kỹ thuật dùng một điểm Q[0] có toạ độ (-∞, ∞) để làm lính canh cho việc xử lý trường hợp đỉnh Q[1]. Kết quả trả về của đoạn chương trình là True nếu điểm P nằm trong đa giác; False nếu điểm P nằm ngoài đa giác.
FUNCTION cn_poly(P: Point; Poly: Polygon, n: Integer):Boolean;
Var d1, d2, cn, i, j, Min : Integer;
L1, L2 : Line; Q : Polygon;
Begin
Min:=1;
For i:=2 to n do
If Poly[i].y<Poly[min].y Then
Min:=i;
For i:=1 to n do
If (Poly[i].y=Poly[Min].y) and (Poly[i].x<Poly[Min].x Then
Min:=i;
j:=1;
For i:=Min to N do
Begin
Q[j]:=Poly[i];
j:=j+1;
End;
For i:=1 to Min-1 do
Begin
Q[j]:=Poly[i];
j:=j+1;
End;
Q[0].x:=-maxint;
Q[0].y:=maxint; {Điểm làm lính canh}
Q[N+1]:=Q[1];
L1.P1:=P;
L1.P2.x:=maxint;
L1.P2.y:=P.y;
cn:=0;
For i:=1 to n do
Begin
L2.P1:=Q[i];
L2.P2:=Q[i+1];
If Intersect(L1, L2) Then
Begin
If (P.y=Q[i].y) Then
Begin
If (P.y=Q[i+1].y) Then {Trùng với một cạnh}
If ((P.x>=Q[i].x) and (P.x<=Q[i+1].x))
or ((P.x>=Q[i+1].x) and (P.x<=Q[i].x)) Then
cn:=cn+1
Else
Begin
d1:=Q[i-1].x – P.x;
d2:=Q[i+2].x – P.x;
If d1*d2 < 0 Then
cn:=cn+1;
End;
Else
Begin
d1:=Q[i-1].x – P.x;
d2:=Q[i+1].x – P.x;
If d1*d2 < 0 Then
cn:=cn+1;
End;
End;
Else
If (P.y Q[i+1]) Then
cn:=cn+1;
End;
End;
If (cn mod 2 = 0) Then
cn_poly:=False;
Else
cn_poly:=True;
End;
b. Thuật toán số lần bao gói
Thuật toán số lần bao gói đếm số lần đa giác bao quanh điểm P, nếu số lần này lơn hơn 0 thì điểm P nằm trong đa giác. Thuật toán này cho phép ta kiểm tra đối với đa giác tự cắt (có các phần chồng lên nhau).
Với một đa giác P1, P2, ...,Pn và điểm P cho trước. Một tia xuất phát từ P song song với trục hoành dùng để xét xem tia kiểm tra này có cắt các cạnh hay không? Khi tia kiểm tra cắt một cạnh của đa giác, ta sẽ quyết định cộng thêm cho số lần bao gói (wn) 1 đơn vị, hay trừ đi số lần bao gói 1 đơn vị tuỳ theo hướng của cạnh bị cắt. Hình ảnh sau minh hoạ việc cộng thêm 1 đơn vị hay trừ đi 1 đơn vị cho wn.
Hình 2.8. Khi tia kiểm tra cắt các cạnh của đa giác thì số lần bao gói tăng lên hoặc giảm xuống 1 đơn vị tuỳ theo cạnh đó đi lên hay đi xuống
Khi tia kiểm tra xuất phát từ P cắt cạnh PiPi+1, ta xét xem cạnh này đi lên hay đi xuống để biết P nằm bên trái hay bên phải của cạnh PiPi+1. Nếu tam giác PiPi+1P ngược chiều kim đồng hồ thì P nằm bên trái cạnh PiPi+1, lúc này ta cho wn tăng lên 1 đơn vị; ngược lại PiPi+1P cùng chiều kim đồng hồ thì P nằm bên phải cạnh PiPi+1 và cho wn giảm đi 1 đơn vị. Vì vậy, chúng ta cần thiết kế một hàm mà kết quả trả về cho biết điểm P nằm phía bên trái hay phía bên phải hay trên một cạnh của đa giác. Nếu kết quả trả về là 1 thì P nằm bên trái của cạnh đó; nếu kết quả trả về là 0 thì P nằm trên cạnh đó; nếu kết quả trả về là -1 thì P nằm bên phải của cạnh đó.
P
Pi
Pi+1
Hình 2.9. a. Cạnh đi lên, P nằm bên phải cạnh PiPi+1.
P
Pi
Pi+1
b. Cạnh đi xuống P nằm bên phải trái PiPi+1.
Sau đây là đoạn chương trình cho biết điểm P nằm bên trái hay bên phải hay trên cạnh PiPi+1 của một đa giác.
FUNCTION isLeft(P, P1, P2 : Point) : Integer;
Var d : Integer;
Begin
d:=((P1.x-P.x)*P2.y-P.y)-(P2.x-P.x)*(p1.y-P.y));
If d>0 Then
isLeft:=1
Else
If d=0 Then
isLeft:=0
Else
isLeft:=-1
End;
Tiếp theo đây là đoạn chương trình cho biết điểm P có nằm trong đa giác hay không. Kết quả tra về là True nếu điểm P nằm trong đa giác; ngược lại kết quả trả về là False nếu điểm P nằm ngoài đa giác.
FUNCTION wn_poly(P: Point; Poly: Polygon, n: Integer):Boolean;
Var wn, i, j, Min : Integer;
L1, L2 : Line; Q : Polygon;
Begin
wn:=0;
For i:=1 to n-1 do
Begin
If (Poly[i].y<=P.y) Then
Begin
If (Poly[i+1]>P.y) Then
If (isLeft(P, Poly[i], Poly[i+1])>0) Then
wn:=wn+1
End
Else
Begin
If (Poly[i+1].y<=P.y) Then
If (isLeft(P, Poly[i], Poly[i+1])<0) Then
wn:=wn-1;
End;
End;
If (wn > 0) Then
wn_poly:=True;
Else
wn_poly:=False;
End;
Phần này đã trình bày hai thuật toán dùng để kiểm tra xem một điểm có nằm trong đa giác hay không. Đối với thuật toán cn, ta cũng có thể cải tiến theo hướng như thuật toán wn để quá trình kiểm tra đỡ phức tạp hơn, hiệu quả của thuật toán cao hơn, và độ phức tạp thuật toán bé hơn. Độ phức tạp của hai thuật toán trên là O(N)
4. Giao và hợp của các đối tượng cơ bản
Các bài toán tìm phần giao và phần hợp của các đối tượng hình học phẳng như hình tròn, đa giác, ... là những bài toán khá hay và để tìm ra các lời giải tối ưu thì đòi hỏi người lập trình phải có tư duy tốt về toán học. Đặc biệt, tìm giao của hai đa giác là một trong những bài toán cơ sở của hình học tính toán và có nhiều ứng dụng trong các lĩnh vực khác nhau của đồ hoạ máy tính, CAD, GIS, ...
4.1. Giao của hai đường thẳng tổng quát
Một đường thẳng tổng quát trong mặt phẳng toạ độ bao giờ cũng có phương trình là ax + by = c (1). Tuy nhiên, không phải lúc nào ta cũng có phương trình đường thẳng như dạng trên, mà thay vào đó là đường thẳng đi qua hai điểm P1(x1, y1), P2(x2, y2). Trong trường hợp như vậy, ta phải tìm cách chuyển về dạng (1) để cho việc tìm giao điểm được dễ dàng hơn.
Đối với đường thẳng đi qua hai điểm phân biệt P1(x1, y1), P2(x2, y2) ta có thể chuyển về dạng (1) theo cách: viết phương trình đường thẳng đi qua hai điểm P1(x1,y1), P2(x2,y2) như sau:
Û (x - x1)(y2 – y1) = (y – y1)(x2 – x1)
Û (y2 – y1)x + (x1 – x2)y = (x1 – x2)y1 + (y2 – y1)x1
Û (y2 – y1)x + (x1 – x2)y = x1y2 - x2y1
Đây chính là phương trình dạng (1) đã được đề cập ở trên.
Vấn đề đặt ra là tìm giao điểm của hai đường thẳng tổng quát có phương trình lần lượt là: a1x + b1y = c1 và a2x + b2y = c2. Việc tìm giao điểm này thực chất là việc giải hệ phương trình:
(2)
Sau đây là đoạn chương trình tìm giao điểm của hai đường thẳng dựa trên việc giải hệ phương trình (2). Kết quả trả về của hàm là True và toạ độ của điểm giao nhau (một cặp số thực thông qua bản ghi Point) nếu hai đường thẳng này giao nhau, ngược lại, nếu hai đường thẳng không giao nhau thì hàm trả về kết quả là False.
FUNCTION int_point(a1,b1,c1,a2,b2,c2:Real; Var P:Point):Boolean;
{Ở đây chúng ta có thể xem Point là một bản ghi có hai thành phần đều là số thực}
Var D, DX, DY : Real;
Begin
D:=a1*b2-a2*b1;
DX:=c1*b2-c2*b1;
DY:=a1*c2-a2*c1;
If D0 Then
Begin
P.x:=DX/D;
P.y:=DY/D;
int_point:=True;
End
Else
int_point:=False;
End;
4.2. Giao và hợp của hai đường tròn
Bài toán: Với hai đường tròn có tâm và bán kính cho trước lần lượt là P1(x1, y1), R1 và P2(x2, y2), R2.
Yêu cầu: Tìm phần giao và phần hợp của hai đường tròn này.
Hình 2.12. Phần diện tích giao nhau của hai hình tròn là phần được gạch chéo
Việc tìm phần giao của hai đường tròn này được thực hiện thông qua phương pháp chia lưới, nghĩa là chúng ta chia mặt phẳng ra thành một lưới các ô vuông bởi các đường thẳng song song với trục tung và trục
Các file đính kèm theo tài liệu này:
- Phương pháp thiết kế thuật toán giải quyết một số bài toán trong hình học.doc