程序员面试金典: 9.7数学与概率 7.6在二维平面上,有一些点,请找出经过点最多的那条线

#include <iostream>
#include <stdio.h>
#include <map>
#include <vector>

using namespace std;

/*
问题:在二维平面上,有一些点,请找出经过点数最多的那条线。
分析:暴力破解,共有n(n-1)/2 条直线,对每个直线,判断剩余n-2个点是不是在直线上
      总时间复杂度为O(n^3)。
	  可能需要排除某些线段,不是所有线段。
	  或者是求闭包,闭包问题记得需要3个顶点,然后每次替换掉其中两个顶点,画出图形
	  然后得到闭包中以某个点作为直线其中一点的斜率最大值和最小值,遍历斜率吗?
	  不会。
书上解法:
      牛逼,画出所有所有直线,统计所有直线出现次数,时间复杂度为O(n^2)
	  之所以降低了复杂度,将处理的对象从点变成了线。
	  易错:斜率为浮点数,作为映射中的键的时候,需要考虑与斜率k,在精度p范围之内的
	  k-p,k+p的斜率对应直线都是一样的,统计斜率为k的直线的时候,需要将k-p,k+p对应的直线也进行统计
	  例如:k=1.12,p=0.01,那么斜率1.11,1.13对应的直线也需要统计处理
输入:
6(点的个数,接下来有n行,每一行对应一个点,每个点第一个数值为x,第二个数值为y)
1 1
2 2
3 3 
1 2
2 4
3 1
输出:
1(该直线的斜率) 0(该直线的截距)

关键:
1  牛逼,画出所有所有直线,统计所有直线出现次数,时间复杂度为O(n^2)
之所以降低了复杂度,将处理的对象从点变成了线。
易错:斜率为浮点数,作为映射中的键的时候,需要考虑与斜率k,在精度p范围之内的
k-p,k+p的斜率对应直线都是一样的,统计斜率为k的直线的时候,需要将k-p,k+p对应的直线也进行统计
例如:k=1.12,p=0.01,那么斜率1.11,1.13对应的直线也需要统计处理
还要考虑斜率无穷大的情况
2 
//直线存放好后,下面就立即统计该直线出现次数,如果后续再次出现相同直线,肯定已经累加了统计次数,这样做是对的
int times = count(slopeToLines , intersectToTimes , line);
*/

const double g_precision = 1e-3;//给定精度,用于判定两个浮点数是否相同

typedef struct Point
{
	Point(){}
	double _x;
	double _y;
}Point;

class Line
{
public:
	Line(){}
	Line(Point& point1 , Point& point2):_point1(point1),_point2(point2),_isSlopeInfinite(false){}//默认斜率不是无限大的
	//根据两点,计算斜率和截距
	void calculate()
	{
		//如果两个点横坐标不等
		if( fabs(_point1._x - _point2._x) > g_precision )
		{
			_slope = (_point1._y - _point2._y) / (_point1._x - _point2._x);
			_intersect = _point1._y - _slope * _point1._x;
			_slope = standardlize(_slope);
			_intersect = standardlize(_intersect);
		}
		//设置斜率无限大为真
		else
		{
			_isSlopeInfinite = true;
			_intersect = standardlize(_point1._x);//设置截距为距离x轴的距离
		}
	}

	//将斜率和截距浮点数精度控制在设置的精度范围,采用的方法是:将浮点数除以精度取整后再乘以精度
	double standardlize(double value)
	{
		int num = (int) (value / g_precision);
		return  g_precision * num;
	}

	//判断两个直线是否相等:斜率和截距都要在相等或者都是斜率无穷大且斜率相等,即两直线的斜率和距离差值都要在精度范围之内
	bool isEqual(Line& line)
	{
		//由于直线中的斜率和截距都要计算,因此先执行计算函数
		calculate();
		line.calculate();

		//如果两个的斜率都不是无穷大
		if(!line._isSlopeInfinite && (!_isSlopeInfinite) )
		{
			if( fabs(_slope - line._slope) <= g_precision && fabs(_intersect - line._intersect) <= g_precision )
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		//如果两个斜率都是无穷大
		else if(line._isSlopeInfinite && _isSlopeInfinite)
		{
			if( fabs(_intersect - line._intersect))
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		else
		{
			return false;
		}
	}
	Point _point1;
	Point _point2;
	double _slope;
	double _intersect;
	bool _isSlopeInfinite;//斜率是否无穷大,即给定两个点的x坐标相同
};

//统计与line相等的直线个数
int countEquivalentLine(vector<Line>& lines , Line& line)
{
	if(lines.empty() )
	{
		return 0;
	}
	int count = 0;
	for(vector<Line>::iterator it = lines.begin() ; it != lines.end() ; it++)
	{
		if(line.isEqual(*it))
		{
			count++;
		}
	}
	return count;
}

int count(map< double , vector<Line> >& slopeToLines , map<double , int>& intersectToTimes , Line& line)
{
	//如果斜率正常,进入斜率映射中进行统计
	if(!line._isSlopeInfinite)
	{
		int totalCount = 0;
		if(slopeToLines.find(line._slope) != slopeToLines.end())
		{
			totalCount += countEquivalentLine((slopeToLines.find(line._slope))->second , line );
		}
		//因为考虑到精度,因此斜率k+p,k-p默认与斜率为k的直线是相同的
		if( slopeToLines.find(line._slope + g_precision) != slopeToLines.end())
		{
			totalCount += countEquivalentLine((slopeToLines.find(line._slope+ g_precision))->second , line );
		}
		if( slopeToLines.find(line._slope - g_precision) != slopeToLines.end())
		{
			totalCount += countEquivalentLine((slopeToLines.find(line._slope - g_precision))->second , line );
		}
		return totalCount;
	}
	//首先判定该直线斜率是否无穷大, 如果无穷大,进入无穷大映射中查找截距相同的直线即为相同直线
	else
	{
		int totalCount = 0;
		if( intersectToTimes.find(line._intersect) != intersectToTimes.end() )
		{
			totalCount += intersectToTimes.find(line._intersect)->second;
		}
		if( intersectToTimes.find(line._intersect + g_precision) != intersectToTimes.end() )
		{
			totalCount += intersectToTimes.find(line._intersect + g_precision)->second;
		}	
		if( intersectToTimes.find(line._intersect - g_precision) != intersectToTimes.end() )
		{
			totalCount += intersectToTimes.find(line._intersect - g_precision)->second;
		}	
		return totalCount;
	}
}

Line findLine(vector<Point>& vecPoint)
{
	if(vecPoint.empty())
	{
		return Line();
	}
	int size = vecPoint.size();
	// 将斜率无限大(即与x轴垂直)的直线单独放在一个映射中,键是截距(距离x轴的距离),值是该截距对应直线出现次数
	map<double , int> intersectToTimes;
	map< double , vector<Line> > slopeToLines;
	int maxTimes = 0;
	Line maxLine;
	for(int i = 0 ; i < size - 1; i++)
	{
		Point point1 = vecPoint.at(i);
		for(int j = i + 1 ; j < size ; j++)
		{
			Point point2 = vecPoint.at(j);
			Line line(point1 , point2);
			line.calculate();
			//如果斜率无限大,放入斜率无限大的直线数组中
			if( line._isSlopeInfinite)
			{
				map<double ,int>::iterator it = intersectToTimes.find(line._intersect);
				if( it != intersectToTimes.end())
				{
					(it->second)++;
				}
				else
				{
					intersectToTimes.insert(pair<double , int>(line._intersect , 1));
				}
			}
			//斜率正常的直线,存入映射中
			else
			{
				//如果斜率存在,就加入到该斜率对应的直线数组中
				map< double , vector<Line> >::iterator it = slopeToLines.find(line._slope);
				if( it != slopeToLines.end() )
				{
					it->second.push_back(line);
				}
				//如果该斜率不存在,就建立映射
				else
				{
					vector<Line> lines;
					lines.push_back(line);
					slopeToLines.insert(pair< double , vector<Line> >(line._slope , lines));
				}
			}
			
			//直线存放好后,下面就立即统计该直线出现次数,如果后续再次出现相同直线,肯定已经累加了统计次数,这样做是对的
			int times = count(slopeToLines , intersectToTimes , line);
			if(times > maxTimes)
			{
				maxTimes = times;
				maxLine = line;
			}
		}
	}
	return maxLine;
}

void process()
{
	int n;
	vector<Point> vecPoint;
	Line maxLine;
	while(cin >> n)
	{
		vecPoint.clear();
		for(int i = 0 ; i < n ; i++)
		{
			Point point;
			cin >> point._x >> point._y;
			vecPoint.push_back(point);
		}

		//所有的点输入好之后,下面就是计算包含点最多的直线
		maxLine =  findLine(vecPoint);
		cout << maxLine._slope << " " << maxLine._intersect << endl;
	}
}

int main(int argc, char* argv[])
{
	process();
	getchar();
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值