目录
题目:二维最接近点对问题
【问题描述】
给定平面上n个点,找出其中的一对点的距离,使得在这n个点的所有点对中,该距离为所有点对中最小的。
【输入形式】
第一行一个整数n,表示点的个数。
接下来n行,每行两个整数x,y,表示一个点的行坐标和列坐标。
1<=n<=1e5,0<=x,y<=1e9。
【输出形式】
仅一行,一个实数,表示最短距离,四舍五入保留2位小数。
【样例输入】
4 0 0 0 1 1 0 1 1
【样例输出】
1.00
老师要求:递归算法里不可以出现排序!!!!!!!!!!!!
暴力算法:
算法原理:
求出每两个点之间的距离,选出最小的
算法伪码:
输入:点集P()(一个二维数组,例:P[0][0]代表第一个点的x,P[0][1]代表第一个点的y)
1.定义最小距离dist为第一个点到第二个点的距离
2.两层for循环遍历更新dist的值
3.返回dist
算法复杂度:
时间复杂度:O()
空间复杂度: 由于本题n<1e5,<=4,999,950,000
double baoli(double a[][2],int n) {//暴力算法
int dist = getdist(a[0],a[1]);
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
dist =smaller( dist,getdist(a[i], a[j]));
}
}
return dist;
}
分治算法:
这里转载一篇个人讲的蛮好的文章:平面点对问题(分治法的经典问题)_给定n个点,其坐标为 ( xi,yi ) ( 0£i£n-1 ) ,要求使用分治策略求解,设计算-CSDN博客
第一版:
算法原理:
把点集P划分为L和R(L、R大小相等),分别计算L和R中最近点对的距离,计算L和R中距离最近点对的距离。
算法伪码:
Mindistance(P,l,r)
输入:点集P(二维数组,P[i][0]代表第i个点的x,P[i][1]代表第i个点的y), l和r分别为点集P(数组)的左下标和右下标
输出:最近点对的距离d
1.if 点少于三个 直接计算并比较
2.根据X值的大小对P进行排序(个人选用)
3.做中垂线m(m=(l+r)/2),把点集P划分为L和R(L、R大小相等)
4.d1=Mindistance(P,m,r)
5.d2=Mindistance(P,l,m)
6.d=d1和d2中较小的值
7.计算m两侧不超过d范围内的点(理论上不会超过6个 )之间的最近距离d3。若d3小于d,更新d=d3。
算法复杂度:
时间复杂度:
空间复杂度:
试了好多遍,老是有一个测试用例不过,于是放出第二个纠错大招,输出数据,以查看其变化过程。我错的是感叹号部分。提醒一下自己注意细节!!!!!
C代码:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include <cmath>
using namespace std;
int Partition(double a[][2], int l, int r) {//初始化待划分区间
int i = l, j = r;
double pivot0 = a[l][0];//挖坑法 pivot坑
double pivot1 = a[l][1];
//cout<< "in Partition1" << endl;///
//for (int i = l; i <= r ; i++) {
// cout<< "a[" << i << "][0]:" << a[i][0] << " " << "a[" << i << "][1]:" << a[i][1] << endl;
//}
while (i < j)
{ //右侧扫描
while (i < j && a[j][0]>= pivot0)
{
j--;//找到小的
}
if (i < j)//左小于右
{
double tempx = a[i][0];
a[i][0] = a[j][0];
a[j][0] = tempx;
double tempy = a[i][1];
a[i][1] = a[j][1];
a[j][1] = tempy;
i++;
}
while (i < j && a[i][0] <= pivot0)
{
i++;
}
if (i < j)
{
double tempx = a[i][0];
a[i][0] = a[j][0];
a[j][0] = tempx;
double tempy = a[i][1];
a[i][1] = a[j][1];
a[j][1] = tempy;
j--;
}
}
//cout<< "in Partition2" << endl;
//for (int i = l; i <= r ; i++) {
// cout<< "a[" << i << "][0]:" << a[i][0] <<" "<< "a[" << i << "][1]:" << a[i][1] << endl;
// //cout << "a[i][0]: " << a[i][0] << " " << "a[i][1]: " << a[i][1] << endl;
//}
a[i][0] = pivot0;
a[i][1] = pivot1;
//cout << "in Partition3" << endl;/
//for (int i = l; i <= r ; i++) {
// cout << "a[" << i << "][0]:" << a[i][0] << " " << "a[" << i << "][1]:" << a[i][1] << endl;
//}
return i;
}
//进行快速排序
void qst(double a[][2], int l, int r) {
//cout << "in qst1" << endl;
//for (int i = l; i <= r; i++) {
// cout << "a[" << i << "][0]:" << a[i][0] << " " << "a[" << i << "][1]:" << a[i][1] << endl;
//}
if (l < r)
{
double pivot = Partition(a ,l, r);
qst(a, l, pivot - 1);
qst(a, pivot + 1, r);
}
}
double smaller(double a,double b) {
return a<b ? a : b;
}
double getdist(double a[], double b[]) {
double ans = sqrt(pow(a[0] - b[0], 2) + pow(a[1] - b[1], 2));
return ans;
}
double MinDistance(double a[][2],int l ,int r ) {
int n = r+1 - l;//右+1-左
int m = (l+r)/2;
double distance = 0.0;//
if (n == 2) {//两个点
distance = getdist(a[0], a[1]);
}
else if (n == 3) {//三个点
double d1= getdist(a[0], a[1]);//点0,1
double d2= getdist(a[2], a[1]);//点2,1
double d3= getdist(a[0], a[2]);//点0,2
double d4 = smaller(d1, d2);
distance = smaller(d4, d3);
}
else { //划分
int lb=m,rb=m;
double e1 = MinDistance(a,l,m );
double e2 = MinDistance(a,m,r);
distance = smaller(e1, e2);
while (lb>=l && abs(a[lb][0]-a[m][0])<distance) { //从中垂线向两边找区间
lb--;//m--
}
while (rb<=r && abs(a[rb][0] - a[m][0]) < distance) {
rb++;//m++
}
lb += 1;
rb -= 1;
for (int i = lb; i <= rb-1; i++) {
for (int j = i + 1; j <= rb;j++) {
if (a[i][1] - a[j][1] <=distance) {//德尔塔y<mindistance
distance = smaller(getdist(a[i], a[j]), distance);
}
}
}
}
return distance;
}
int main() { //
int n;
double d = 0.0;
double a[100000][2] = { 0 };//a[][0]x,a[][1]是y
cin >> n;
for (int i = 0; i < n; i++) {
scanf("%lf %lf",&a[i][0],&a[i][1]);
}
qst(a,0,n-1);//n元素个数 !!!!!!!!!!n-1
//for (int i = 0; i < n; i++) {
// cout << "a[" << i << "][0]:" << a[i][0] << " " << "a[" << i << "][1]:" << a[i][1] << endl;
//}
//cout << endl;///
d=MinDistance(a, 0, n-1);
printf("%.2f\n", d);
return 0;
}
第二版:
算法原理:
把点集P划分为L和R(L、R大小相等),分别计算L和R中最近点对的距离,计算L和R中距离最近点对的距离。
算法伪码:
Mindistance(P,l,r)
1.输入:点集P(二维数组,P[i][0]代表第i个点的x,P[i][1]代表第i个点的y,P[i][2]代表第i个点按Y值大小排序的顺序), l和r分别为点集P(数组)的左下标和右下标,并将P[i][2]赋值为i
2.将数组P按照x的大小进行升序排序(使用快排)
3.将数组P按照y的大小进行升序排序(使用快排)得到的顺序保存在P[i][2]
4.调用Mindistance
if 点少于三个 直接计算并比较
做中垂线m(m=(l+r)/2),把点集P划分为L和R(L、R大小相等)
d1=Mindistance(P,m,r)
d2=Mindistance(P,l,m)
d=d1和d2中较小的值
从m分别向左和向右找距离m两侧不超过d的点,得到左边界lb和右边界rb
遍历,用数组b有序存放中线右边的y,
遍历中线左边的点,定义lk为b索引,初始化为0,以b[lk]做索引,遍历m和rb之间的点(由原理可知,符合条件右边点的数目小于6,遍历次数小于6),计算中线左边的点和右边点之间的距离d3。若d3小于d,更新d=d3。
输出:最近点对的距离d
算法复杂度:
时间复杂度:
空间复杂度:
C代码:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include <cmath>
using namespace std;
void swap(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}
int Partition1(double a[][3], int l, int r) {//初始化待划分区间
int i = l, j = r;
double pivotx = a[l][0];//挖坑法 pivot坑简化版
double pivoty = a[l][1];
while (i < j)
{ //右侧扫描
while (i < j && a[j][0]>= pivotx)
{
j--;//找到小的
}
if (i < j)//左小于右
{
swap(a[i][0], a[j][0]);
swap(a[i][1], a[j][1]);
i++;
}
while (i < j && a[i][0] <= pivotx)
{
i++;
}
if (i < j)
{
swap(a[i][0], a[j][0]);
swap(a[i][1], a[j][1]);
j--;
}
}
a[i][0] = pivotx;
a[i][1] = pivoty;
return i;
}
//进行快速排序
void qst1(double a[][3], int l, int r) {
if (l < r)
{
double pivot = Partition1(a ,l, r);
qst1(a, l, pivot - 1);
qst1(a, pivot + 1, r);
}
}
int Partition2(double a[][3], int l, int r) {//初始化待划分区间
int i = l, j = r;
double pivot = a[l][1];//第一个数的Y
int index = a[l][2];//a[][3]记录按Y排序的点顺序,最左边数的Y顺序
while (i < j)
{ //右侧扫描
while (i < j && a[j][1] >= pivot)
{
j--;//找到小的
}
if (i < j)//左小于右
{
swap(a[i][2], a[j][2]);
}
while (i < j && a[i][1] <= pivot)
{
i++;
}
if (i < j)
{
swap(a[i][2], a[j][2]);
}
}
a[i][2] = index;
return i;
}
//进行快速排序
void qst2(double a[][3], int l, int r) {
if (l < r)
{
double pivot = Partition2(a, l, r);
qst2(a, l, pivot - 1);
qst2(a, pivot + 1, r);
}
//cout << "in qst2" << endl;
//for (int i = l; i <= r; i++) {
// cout << "a[" << i << "][2]:" << a[i][2] << endl;
//}
}
double smaller(double a,double b) {
return a<b ? a : b;
}
double getdist(double a[], double b[]) {
double ans = sqrt(pow(a[0] - b[0], 2) + pow(a[1] - b[1], 2));
return ans;
}
double MinDistance(double a[][3],int l ,int r ) {
int n = r+1 - l;//右+1-左
int m = (l+r+1)/2;
double distance = 0.0;//
if (n == 2) {//两个点
distance = getdist(a[0], a[1]);
}
else if (n == 3) {//三个点
double d1= getdist(a[0], a[1]);//点0,1
double d2= getdist(a[2], a[1]);//点2,1
double d3= getdist(a[0], a[2]);//点0,2
double d4 = smaller(d1, d2);
distance = smaller(d4, d3);
}
else { //划分
int lb=m,rb=m;
double e1 = MinDistance(a,l,m );
double e2 = MinDistance(a,m,r);
distance = smaller(e1, e2);
while (lb>=l && abs(a[lb][0]-a[m][0])<distance) { //从中垂线向两边找区间
lb--;//从m--
}
while (rb<=r && abs(a[rb][0] - a[m][0]) < distance) {
rb++;//从m++
}
lb += 1;
rb -= 1;
int inde = 0;
int* b = new int[rb - m + 1];
for (int i = 0; i < n; i++) {
if (a[i][2] >= m && a[i][2] <= rb) {
b[inde] = a[i][2];//b有序存放中线右边的y
inde++;
}
}
for (int i = lb; i < m; i++) {// 遍历中线左边的点
int lk = 0;//lk:y的排序 0-rb - lb + 1
while (abs(a[b[lk]][1] - a[i][1]) > distance && lk < rb - m + 1) {
lk++;
}
int t = 0;
while (lk < rb - m + 1 && t < 6) {
distance = smaller(getdist(a[i], a[b[lk]]), distance);
lk++;
t++;
}
}
delete[] b;
}
return distance;
}
int main() { //
int n;//元素个数
double d = 0.0;
double a[100000][3] = { 0 };//a[][0]x,a[][1]是y
cin >> n;
for (int i = 0; i < n; i++) {//输入
scanf("%lf %lf",&a[i][0],&a[i][1]);
a[i][2] = i;
}
qst1(a, 0, n - 1);// !!!!!!!!!!!!!!!!!!!!!!!!!!!!n-1
qst2(a, 0, n - 1);
//for (int i = 0; i < n; i++) {
// cout<<"all:" << endl;
// cout << "a[" << i << "][0]:" << a[i][0] << " " << "a[" << i << "][1]:" << a[i][1] << " a[" << i << "][2]:" << a[i][2] <<endl;
//}
//cout << endl;///
d=MinDistance(a, 0, n-1);
printf("%.2f\n", d);
return 0;
}