Tải bản đầy đủ (.docx) (11 trang)

BÀI TOÁN cặp điểm gần NHẤT

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (153.46 KB, 11 trang )

BI TON CP IM GN NHT
I. M u
Đa số các thuật toán đều tập trung xử lí trên văn
bản (xử lí trên xâu) và các con số, chúng đợc thiết kế và
xử lí sẵn trong phần lớn các bài toán lập trình. Đối với các
bài toán hình học thì đòi hỏi khác hẳn, ngời lập trình
phải có sự tính toán tỉ mỉ các công thức về hình học trớc
khi đi vào lập trình. Khi làm các bài tập về hình học thờng phải đòi hỏi sự công phu lập trình hơn các bài tập
khác, nhất là việc thiết lập các công thức thì bài lập trình
mới có hiệu quả.
1. Đối tợng hình học cơ bản
Trong các bài toán Tin học thuộc loại hình học có 3 đối tợng
cơ bản là: Điểm, Đoạn thẳng và Đa giác.
Điểm: Đợc xét bằng một cặp số nguyên là tọa độ của
điểm đó trong hệ trục tọa độ Descartes thờng dùng.
Đoạn thẳng: Là một cặp điểm đợc nối với nhau một phần
của đờng thẳng.
Đa giác: Là một dÃy các điểm với hai điểm liên tiếp đợc nối
bởi một đoạn thẳng, và điểm đầu đợc nối với điểm cuối
tạo thành một hình gấp khúc khép kín.
2. Dữ liệu lu trữ các đối tợng hình học cơ bản
Làm việc với các đối tợng hình học chúng ta cần
quyết định thể hiện chúng nh thế nào? Thông thờng ta
dùng một mảng để biểu diễn một đa giác, dù rằng một số
trờng hợp khác chúng ta có thể dùng danh sách liên kết hay
1


các kiểu khác. Đối tợng chúng ta thờng xuyên phải làm việc
với hình học là tọa độ, do đó việc lu trữ toạ độ của các
điểm là phổ biến trong các bài toán lập trình về hình


học.

II. Ni dung
1. Phỏt biểu bài toán
Bài toán: Cho điểm trên mặt mẳng tọa độ, tìm khoảng cách ngắn
nhất giữa hai điểm bất kỳ.
2. Thuật tốn
Bài tốn này ta có thể dễ dàng thực hiện thuật toán , ta xét tất cả các cặp
điểm và chọn cặp có khoảng cách ngắn nhất giữa chúng. Thuật tốn có độ
phức tạp . Ta có một số thuật toán tốt hơn như sau:
2.1.Thuật toán chia để trị
a. Ý tưởng thuật toán
Thuật toán chia để trị giải quyết bài tốn này thực hiện như sau:


Sắp xếp các điểm đã cho theo thứ tự từ trái sang phải. Chia tập điểm
thành hai tập.



Thực hiện thao tác đệ quy, tìm cặp điểm gần nhất trong hai tập con,
trả lại giá trị là khoảng cách ngắn nhất trong tập đó.



Gọi là khoảng cách ngắn nhất, trong hai khoảng cách ngắn nhất
trong hai tập con. Ta chỉ quan tâm tới các cặp có khoảng cách nhỏ
hơn .

2





Xét trong “cửa sổ” độ rộng , sắp xếp tất cả các điểm trong cửa sổ đó
theo thứ tự tăng dần chiều tọa độ .



Nhận xét: vì khoảng cách giữa mỗi cặp điểm bất kỳ trên hai tập
không nhỏ hơn nên với mỗi một điểm trên “cửa sổ”, có khơng quá 3
điểm mỗi bên có khoảng cách nhỏ hơn hoặc bằng so với điểm đó.
Do vậy, với mỗi điểm, ta chỉ cần xét (ghép cặp) khơng q 6 điểm để
có thể tìm ra cặp điểm có khoảng cách Euclid nhỏ hơn .

b.

Cài đặt

Sử dụng đệ quy quay lui để cài đặt thuật tốn. Với mỗi đoạn:


Nếu độ dài đoạn , ta tính tốn bruteforce để tìm cặp điểm gần nhất.
Nếu độ dài ta chia đôi tập, đệ quy quay lui để tính khoảng cách gần



nhất trong hai tập con, chọn giá trị bé nhất trong hai khoảng cách.
Dùng mảng để trộn hai phía theo thứ tự tăng dần , lọc lấy những




phần tử có chênh lệch nhỏ hơn giá trị đang có. (). Chú ý sau khi
thực hiện, ta thực hiện sắp xếp trộn hai phía của đoạn theo thứ tự
tăng dần y luôn, độ phức tạp , không sử dụng thuật tốn sắp xếp


khác.
Với các phần tử trong cửa sổ, với mỗi điểm, xét các điểm lân cận
3


(sau khi đã sắp xếp) có chênh lệch nhỏ hơn . (số phần tử này

4


Dưới đây là chương trình cài đặt tính cặp điểm gần nhấttheo thuật tốn
chia để trị:
Input:


Dịng đầu tiên là số – số lượng điểm



dịng tiếp theo, mỗi dịng chứa tọa độ một điểm theo thứ tự.

Output: Khoảng cách nhỏ nhất cần tìm, chính xác 5 chữ số phần thập
phân.

CLOSESTPAIR.INP
3
1 1
2 2
2 1

CLOSESTPAIR.OUT
1.00000

Chương trình:
#include <bits/stdc++.h>
#define sz(x) int(x.size())
#define MIN(x,y) if (x < y) x = y
#define PB push_back
#define mp make_pair
#define Task "closestpair"
#define maxn 200002
#define MOD 1000000007
#define remain(x) if (x > MOD) x -= MOD
#define pii pair<int, int>
#define X first
#define Y second
using namespace std;
pii a[maxn], strip[maxn];
double ans = 1e10;
double Distance(pii P1, pii P2)
{
5



double XX = P1.X - P2.X;
double YY = P1.Y - P2.Y;
return sqrt(XX*XX + YY*YY);

}
double bruteForce(pii P[], int n)
{
double kmin = FLT_MAX;
for (int i = 0; i < n; i++)
for (int j = i+1; j < n; j++)
kmin = min(kmin, Distance(P[i], P[j]));
return kmin;
}
bool compareY(pii p1, pii p2)
{
return (p1.Y < p2.Y || (p1.Y == p2.Y && p1.X < p2.X));
}
double stripClosest(pii strip[], int n, double d)
{
double kmin = d;
for (int i = 0; i < n; ++i)
for (int j = i+1; j < n && (strip[j].Y - strip[i].Y) <
kmin; j++)
kmin = min(Distance(strip[i],strip[j]), kmin);
return kmin;
}
double Calc(pii P[], int n)
{
if (n <= 3)
{

sort (P, P+n, compareY);
return bruteForce(P, n);
}
int mid = n/2;
pii midPoint = P[mid];
double dl = Calc(P, mid);
double dr = Calc(P + mid, n-mid);
double d = min(dl, dr);
6


int ccount = 0;
merge(P, P+mid, P+mid, P+n, strip,compareY);
for (int i = 0; i < n; i++)
{
P[i] = strip[i];
if (abs(strip[i].X - midPoint.X) < d)
strip[ccount++] = strip[i];
}
return min(d, stripClosest(strip, ccount, d));
}
double Closest(pii a[], int n)
{
sort(a, a+n);
return Calc(a,n);
}
int main()
{
ios_base::sync_with_stdio(0);
freopen(Task".inp", "r", stdin);

freopen(Task".out", "w", stdout);
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i].X >> a[i].Y;
printf("%0.5f", Closest(a,n));
return 0;
}
c.

Đánh giá
Việc tính trong cửa sổ và sắp xếp trộn với một đoạn độ dài (khơng
tính hai đoạn con) có độ phức tạp O(n). Số lần lặp thủ tục tính có lời
gọi khơng quá lần nên tổng độ phức tạp thuật toán là .

7


2.2.Thuật tốn sweep line
a. Ý tưởng thuật tốn.
• Sắp xếp tọa độ các điểm theo chiều .
• Xét từng điểm từ trái qua phải. Giả sử ta đang xét tới điểm và giá trị
nhỏ nhất giữa hai điểm trong các điểm bên trái là . Rõ ràng ta chỉ giữ
lại những điểm có khoảng cách tới tọa độ của là (hình trái)



Với điểm ta chỉ cần xét những điểm có chênh lệch cả và so với
khơng q . (hình bên phải). Dễ thấy, vì là khoảng cách ngắn nhất
giữa điểm trong tập điểm bên trái đã xét nên chắc chắn có khơng


b.

q điểm như vậy.
Cài đặt
Sử dụng thêm cấu trúc set để lưu trữ các phần tử trong “vệt quét” hay
nói cách khác là các phần tử có tọa độ x có khoảng cách tới tọa độ x
của p đang xét nhỏ hơn giá trị d hiện tại. Đồng thời dễ ràng thêm,
xóa và tìm kiếm các phần tử trong phạm vi .

Dưới đây là chương trình cài đặt tính cặp điểm gần nhấttheo thuật toán
sweepline:
8


#include <bits/stdc++.h>
#define sz(x) int(x.size())
#define MIN(x,y) if (x < y) x = y
#define PB push_back
#define mp make_pair
#define F first
#define S second
#define Task "closestpair"
#define maxn 200002
#define MOD 1000000007
#define remain(x) if (x > MOD) x -= MOD
#define pii pair<int, int>
#define Y first
#define X second
using namespace std;
pii a[maxn];

set S;
double Distance(pii P1, pii P2)
{
double XX = P1.X - P2.X;
double YY = P1.Y - P2.Y;
return sqrt(XX*XX + YY*YY);
}
bool compareX(pii p1, pii p2)
{
return (p1.X < p2.X || (p1.X == p2.X && p1.Y < p2.Y));
}
double Closest(pii a[], int n)
{
sort(a, a+n, compareX);
S.insert(a[0]);
int left = 0;
double ans = 1e10;
for (int i = 1; i < n; i++)
{
9


int py = a[i].Y;
int px = a[i].X;
while (a[left].X < px - ans) S.erase(a[left++]);
for
(set
::
iterator
it

=
S.lower_bound(mp(1.0*py-ans, 1.0*px-ans)); it != S.end()
&& it->Y < ans+py; it++)
ans = min (ans, Distance(*it, a[i]));
S.insert(a[i]);
}
return ans;
}
int main()
{
ios_base::sync_with_stdio(0);
freopen(Task".inp", "r", stdin);
freopen(Task".out", "w", stdout);
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i].X >> a[i].Y;
printf("%0.5f", Closest(a,n));
return 0;
}
c.

Đánh giá
Mỗi điểm được thêm vào và xóa đi trong Set đúng 1 lần nên thời
gian xử lí thêm vào và xóa đi trên Set là . Với mỗi điểm việc tìm vị
trí bắt đầu có tọa độ trong phạm vi (hàm lower-bound) mất chi phí
thời gian . Số điểm để thuộc hình chữ nhật có thể xét ln khơng
vượt q 4. Do vậy, tổng độ phức tạp để thực hiện giải thuật là .

10



III.Kết luận
Với bài toán cặp điểm gần nhất, cả hai thuật tốn chia để trị và
sweepline đã trình bày trên đều có độ phức tạp là . Việc cài đặt
thuật toán sweepline được cài đặt đơn giản, ngắn gọn hơn (khi sử
dụng ngôn ngữ C++). Tuy nhiên, về mặt thời gian, thuật toán chia
để trị lại thực hiện hiệu quả hơn do việc xử lý Set trong C++ tốn
nhiều thời gian hơn.

Tài liệu tham khảo
1.
2.

Thomas H.Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford
Stein, Introduction To Algorithms, Third Edition.
Mark de Berg, Otfried Cheong, Marc van Kreveld, Mark Overmars,
Computational Geometry Algorithms and Appliacitons, Third
Edition.

11



×