Given n points on a 2D plane, find the maximum number of points that lie on the same straight line.
//Definition for a point.
struct Point {
int x;
int y;
Point() : x(0), y(0) {}
Point(int a, int b) : x(a), y(b) {}
};
int maxPoints(vector<Point> &points) {
}
题目只有一句话:给定二维空间内的点,求在同一条直线上的最多点的数量。
分析:
1)与之前做的最小正方形的一道三分法题目很相似,但是经过分析,是没有办法是用三分法求解的;
2)两点确定一条直线,那么可以枚举任意两点所确定的一条直线,然后判断其他点是否在直线上并进行计数,求出最大点数;
a.枚举任意两点确定一条直线时,有一个简单的优化操作,比如ponts[1],points[2]确定了一条直线,为了避免重复操作,即不出现points[2]和points[1]确定的直线被重复计算,可以再内部循环中,控制循环起点。
具体核心代码如下:
if (points.size()==1)
{
return 1;
}
int result=0;
for (int i=0;i<points.size()-1;i++)
{
for (int j=i+1;j<points.size();j++)
{
int currentNum=2;
//cout<<"=================="<<endl;
//cout<<"i:"<<i<<endl;
//cout<<"j:"<<j<<endl;
//i,j两点确定一条直线,计算直线上点的数量
if ((points[i].x-points[j].x)!=0)
{
/*这种判断方式理论上可行但是有误差
double k=(points[i].y-points[j].y)*1.0/(points[i].x-points[j].x);
double b=points[i].y-k*points[i].x;*/
double a=(points[i].y-points[j].y)*1.0/(points[i].x-points[j].x);
for (int t=0;t<points.size();t++)
{
if (t!=i&&t!=j)
{
if (points[i].x-points[t].x!=0)
{
double b=(points[i].y-points[t].y)*1.0/(points[i].x-points[t].x);
if (a==b)
{
/* cout<<t<<endl;*/
currentNum++;
}
}
else
{
if (points[i].y==points[t].y)
{
/*cout<<t<<endl;*/
currentNum++;
}
}
}
}
}
else
{
for (int t=0;t<points.size();t++)
{
if (t!=i&&t!=j)
{
if (points[t].x==points[i].x)
{
currentNum++;
}
}
}
}
if (currentNum>result)
{
result=currentNum;
}
}
}
提交超时,进行算法分析,整个计算过程是一个三重循环,即外层循环用于枚举直线n2,内部循环计算直线上的点n,所以最终时间复杂度是n3,接下来的算法我真是没想到,在discuss中看了别人的java代码,学习后整理自己的核心代码如下:
vector <Point>::iterator iter;
vector <Point>::iterator iter1;
for (iter=points.begin();iter<points.end()-result;iter++)
{
int coincident=0;
map <double,int> maxPointsInLine;
typedef pair <double, int> line_Pair;
for (iter1=iter+1;iter1<points.end();iter1++)
{
double doukey;
//过滤重复点
if (iter->x==iter1->x&&iter->y==iter1->y)
{
coincident++;
continue;
}
//求key
if (iter->x-iter1->x!=0)
{
doukey=(iter->y-iter1->y)*1.0/(iter->x-iter1->x);
}
else
{
doukey=0;
}
map <double,int>::iterator mIter;
mIter=maxPointsInLine.find(doukey) ;
if (mIter!=maxPointsInLine.end())
{
mIter->second++;
}
else
{
maxPointsInLine.insert(line_Pair(doukey,1));
}
}
int maxp=0;
map <double,int>::iterator mIter1;
for (mIter1=maxPointsInLine.begin();mIter1!=maxPointsInLine.end();mIter1++)
{
if (maxp<mIter1->second)
{
maxp=mIter1->second;
}
}
if (result<maxp+coincident+1)
{
result=maxp+coincident+1;
}
}
3)算法总结:
上面的代码其实是一个时间复杂度是O(n2)的算法,算法巧妙的一点是利用了map<key,value>这个关联的容器数据结构,求解角度上也从之前的从每条直线入手,转换到以每个点为核心去求解,以为在二维空间内,通过一个点的直线,一旦其斜率确定,直线也就随之固定了,总结如下:
a.对空间中给出的每一个点,构建hash表(使用map结构),map<key,value>:key代表通过直线的斜率,value表示在这条直线上的其他点的数量(这里可以看出,map是一种关键字(key)唯一确定一个元素的数据结构);
b.对于重复点(坐标相同的点),作为冗余点设置,对其进行计数即可;
c.对于每个点来说,与改点在同一条直线上的 点的数量是:1+冗余点数量+在一条直线上的其他点的数量(也就是hash表中最大的value值)
问题出现了:这个hash表如何构建呢?
首先,没有必要针对于所有的斜率初始化构建hash表(也是没有办法构建的),因为,给定的点的数量是有限的,对于基点(构建hash表的核心点),只需每次计算其与其他“不同”点所确定的直线斜率就可以了,同时维护对应的hash表;
其次,hash表的维护,只需判断当前计算的直线斜率在hash表中是否存在,若存在更新,不存在插入。
算法中还有两个值得注意的细节:
a.最外重循环只循环了points.end()-result-points.begin()次;
b.内层循环的起点是从外重循环的下一个点开始的,原因与之前我自己想的优化思路类似,即在对points[i]进行计算时,points[j](j>i)与points[i]所确定的直线已经计算过了,说道这也就可以明白,a的优化正确行了,因为,在b的基础上,每次计算只需计算与当前基点在同一条直线上的后续点的数量,当后续节点数量小于当前的最大值时,后面再计算出的最优值肯定不会优于已经计算出的最优值,因为所有点的数量已经不够了。
表述可能不标准,还是看代码吧。。。。。。