求穿过平面上最多点的直线(设计思想)

作者寄语:学习中总结的一些问题,难免有纰漏,欢迎探讨!
一.代码说明
通常在面试题目在可以看到“ 求穿过二维平面上最多点的直线 ”,此类问题中,比较重要的是“ 特殊问题怎么转换为常规问题 ”,这正是编程思想的核心,下面我将沿着这个思路进行解决上述问题。

二.解决思路
首先我在这里使用“倒推法”的思路,也就是通常所说的“ 由果索因 ”法进行分析上述问题,分为下面三个步奏;
第一 “定位结果”
依据题目要求,可以得知,本题需要的结果是一条 y=kx+b 的直线,限制条件是“ 穿过的点最多 ”,这就是需要的结果,转到第二步;
第二:“常规化转换”
有了上述定位的结果,我们可以依据 结果导向 的方法进行问题的常规化转化 ;
(1)第一步转化为“ 怎么通过若干个点得到一条直线,并让这条直线穿过最多的点”, 由数学知识可知: “两点确定一条直线。” 穿过最多的点 即两两确定的直线在同一条直线上 ;那么,现在的问题就可以转化为“ 得到两两确定的直线特征方程 ”并且对直线特征方程进行统计,重复最多的那条就是穿过点最多的那条直线;
(2)第二步转化为“ 平面上N个点最多能确定几条直线(考虑重复点),并对确定的直线进行样本统计,得到重复最多的那条直线 ”。类似于 “握手问题”, 可知平面上N个点最多能确定n(n-1)/2条直线(注意剔除重复点),并把这些直线方程做样本统计得出重复最多的直线方程。这又可以转换为“求数组中每个不同元素出现的次数”即元素样本统计;
(3)数组中元素样本统计可以转化为“数组排序进行重复元素的统计”或借助“map容器插入进行重复元素的统计”;
通过上述的转化我们达到了特殊问题常规化的转换的目的,下面第三个步奏就是进行编码前的 模块划分设计 ,这个非常重要,直接决定了你的代码的可读性和通用扩展性;
第三:“模块设计”
模块划分设计我遵守的原则就是“ 最小颗粒度 ”的划分原则,这种设计的好处就是有利于代码的迭代开发,易于扩展。自顶向下逐步设计。
(1)第一层“由点确定直线方程的函数”和“统计确定的直线方程出现的次数的函数”;
(2)第二层,设计存储点和直线方程的数据结构;
经过上面分析设计,我们就可以进行编码了。

三.代码实现
#include
#include 
#include
#include 
#include 
using namespace std;
struct PT
{
float x;
float y;
};
struct LINE
{
bool bVertical;
float k;//斜率
float b;//直线为y=k*x+b
bool operator < (const LINE&rhs) const
{
if (bVertical == rhs.bVertical)
{
if (k == rhs.k)
{
return b < rhs.b;
}
return k < rhs.k;
}
return bVertical < rhs.bVertical;
}
bool operator == (const LINE&rhs) const
{
if (bVertical == rhs.bVertical)
{
if (k == rhs.k)
{
return b == rhs.b;
}
return k == rhs.k;
}
return bVertical == rhs.bVertical;
}
};
//n个点集合两两组合产生直线
void creakline(vector &pointstruct, vector &lineslop);
//两个点组合产生直线的基本公式
void calslope(PT pt1, PT pt2, LINE &lineslope);
//利用插入到map统计得出LINE结构体个数的统计
void inserttomap(vector &lineslop, std::map &outmap);
//得出LINE结构体重复次数最多的那个结构体
void fineline(std::map &outmap);

//主函数
void main()
{
vector pointset;
PT pt1{ 1, 2 };
PT pt2{ 1, 3 };
PT pt3{ 1, 5 };
PT pt4{ 6, 9 };
PT pt5{ 7, 8 };
PT pt6{ 5, 2 };
pointset.push_back(pt1);
pointset.push_back(pt2);
pointset.push_back(pt3);
pointset.push_back(pt4);
pointset.push_back(pt5);
pointset.push_back(pt6);
vector Alllineslop;
map Outlineslop;
creakline(pointset, Alllineslop);
inserttomap(Alllineslop, Outlineslop);
fineline(Outlineslop);
system("pause");
}

void creakline(vector &pointstruct, vector &lineslop)
{
PT pt1;
PT pt2;
for (auto i = pointstruct.begin(); i != pointstruct.end(); i++)
{
for (auto k = i + 1; k != pointstruct.end(); k++)
{
pt1 = *i;
pt2 = *k;
LINE lineslope;
calslope(pt1, pt2, lineslope);
lineslop.push_back(lineslope);
}
}
}
void calslope(PT pt1, PT pt2, LINE &lineslope)
{
if (pt1.x == pt2.x&&pt1.y == pt2.y) return;
if (pt1.x == pt2.x)
{
lineslope.bVertical = 1;
lineslope.k = numeric_limits::max();
lineslope.b = pt1.x;
}
else
{
lineslope.bVertical = 0;
lineslope.k = (pt1.y - pt2.y) / (pt1.x - pt2.x);
lineslope.b = (pt1.x*pt2.y - pt2.x*pt1.y) / (pt1.x - pt2.x);
}
}
void inserttomap(vector &lineslop, std::map &outmap)
{
std::map testmap;
std::pair< std::map< LINE, int >::iterator, bool > ret;
auto vbegin = lineslop.begin();
while (vbegin!=lineslop.end())
{ 
                     if (testmap.find(*vbegin) == testmap.end())
{
testmap[*vbegin] = 1;
}
 else
{
   ++testmap[*vbegin];
}
++vbegin; 
} 
outmap = testmap;
}
void fineline(std::map &outmap)
{
LINE maxline = outmap.begin()->first;
int maxvlaue = outmap.begin()->second;
for (auto x = outmap.begin(); x != outmap.end(); x++)
{
if (x->second > maxvlaue)
{
maxline = x->first;
maxvlaue = x->second;
}
}
bool bVertical = maxline.bVertical;
float k = maxline.k;
float b = maxline.b;
if (bVertical)
{
cout << "穿过点最多的直线是" << "x=" << b << endl;
}
else
{
if (!k)cout << "穿过点最多的直线是" << "y=" << b << endl;
else
{
if (1 == k)
cout << "穿过点最多的直线是" << "y=x+" << b << endl;
else
cout << "穿过点最多的直线是" << "y=" << k << "x+" << b << endl;
}
}
}



四.思想总结
1.程序模块划分直接决定代码处理流程的简洁度和可读性可扩展性。
2.特殊问题常规化转化是绝大部分问题的解决方法

你可能感兴趣的:(C++)