贪心算法解决最少圆覆盖最多点问题
海面上有一些船需要与陆地进行通信,需要在海岸线上布置一些基站。现将问题抽象为,在x轴上方,给出N条船的坐标 ,在x轴上安放的基站可以覆盖半径为d的区域内的所有点,问在x轴上至少要安放几个点才可以将x轴上方的点都覆盖起来。试设计一个算法求解该问题,并分析算法的正确性。
解:
设计思路:
首先将所有的点按横坐标升序排序。
点集非空时,每次取出横坐标最小的点,将该点视做左半圆周上的点,取距离该点横坐标为d的x轴上的点作为圆心,从圆心处以d为半径作圆(圆心坐标在该点坐标右侧),然后去除掉包含在该圆内的点。然后继续选出剩下点中横坐标最小的点,重复以上操作,直至将所有点包括到圆中,表示所有点都被覆盖,此时得出的圆的个数就是该问题的最优解。
先把点按X轴排序 求出能覆盖每条船的点在X轴上的区间。从最左边的点开始,如果下一个点的左区间比现在的右区间还大,就要新的基站;如果下一个的右区间小于现在的右区间,需要将现在的右区间更新为小的,因为必须覆盖所有点。
如果新圆心的坐标在前一个圆心坐标左侧,那么就舍弃前一个圆心坐标,相反,则表明该圆心成立,通过这种方法,可以使一个圆尽可能覆盖周围的点,局部最优,重复每次操作,则满足全局最优
最优子结构性质:
贪心选择性:
// Test.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
struct Node
{
double l;//最左可被覆盖的坐标
double r;//最右可被覆盖的坐标
};
Node a[1010];
int i,j,k,n,m;
double d,x,y;
int ans;//结果
const double ZERO=1e-8;//控制精度
bool cmp(Node a,Node b)//最左可被覆盖的坐标排序,从左到右排序
{
if (a.l<b.l) return 1;
return 0;
}
void Greedy()
{
double now=a[0].r;
ans++;
for (int i=1;i<n;i++)
{
if (a[i].l>now+ZERO)//下个点的最左被覆盖的坐标大于当前最右可被覆盖坐标
{
ans++;
now=a[i].r;
}else if (a[i].r<now+ZERO)//下个点的最左被覆盖的坐标小于当前最右可被覆盖坐标
{
now=a[i].r;
}
}
}
int main()
{
printf("输入覆盖半径d:\n");
cin>>d;
printf("输入船的数量n:\n");
cin>>n;
printf("输入n条船的坐标:\n");
for(int i=0;i<n;i++)
{
cin>>x>>y;
double len=sqrt((double)(d*d-y*y));//勾股定理
a[i].l=x-len;//计算最左可被覆盖的坐标
a[i].r=x+len;//计算最右可被覆盖的坐标
}
sort(a,a+n,cmp);
ans=0;
Greedy();
printf("在x轴上至少要安放%d个点才可以将所有点覆盖\n",ans);
return 0;
}