项目随笔1

最近在项目中遇到的一个问题,有些意思和启发,随手记下来。

问题简要描述为:在地图上的随便一个点,要搜索它周围n米范围内相邻的点,例如有一个的点A(555,369), 找出在它120米范围内所有的点。

容易想到的做法是用A与集合内所有的点进行距离运算,将距离小于120m的点记录下来即可。

但在测试中发现这样做存在以下问题:

(1)当点的集合特别大的时候,要进行很多运算与比较,效率相当低效。

(2)特别是当要经常求某些点一定范围内的相邻点时,这种低效更加明显,达到让人不能接受的地步。例如点的集合是100w, 而且要搜索所有点在120m范围内的相邻点,那就要 计算比较100w * 100w次,效率相当之低。

经过讨论与分析,最终确定一种简单且行之有效的方法:

主要思想是:

(1)将地图上所有的点所在的区域,划分为一个个格子,所有的点都会落到某个格子内。

例如:如以(0,0)为坐标原点,将所有点所在的区域划分为一个个100m * 100m的格子,点A(555,369)就落在坐标为[5,3]的格子。

(2)遍历每个点,确定每个点所在的格子坐标,遍历结束后生成一张

格子坐标 -> 在该格子内的所有点列表  的映射关系表。

如:[0,0]号格子共有两个点:(33,82),(60,72)

  [2,0]号格子有两个点 : [226,65],[229,43]

(3)根据上面的这张映射表,就很容易求得某个点方圆n米范围内所有的相邻点,并且效率相当的高,只需比较很少的点即可。

例如:要求点A(555,369) 90米内的所有相邻点 (假设整个区域有1000个点)

一般求法:遍历整个点区域内所有的点,与点A做距离运算,然后比较,得出结果。 --共需计算比较999次

运用上述映射关系表计算:

1)首先根据点A的原始坐标计算得到A点所在格子的坐标 为[5,3]

2) 因为max(1,90 / 100) = 1, 所以只需在格子[5,3]左右,上下、对角线方向共9各格子内搜索是否有距离小于90m的相邻点即可。

即在X坐标为: 5-1 -> 5+1, Y坐标:3-1 -> 3+1的范围内搜索即可 ,分别为[4,2],[4,3],[4,4]  [5,2],[5,3],[5,4]  [6,2],[6,3][6,4]九个格子。

只需比较10次即可得到结果,应为超出这9各格子的范围距离肯定大于90m。计算比较次数大大减少。

主要实现如下:

点结构:

struct Point
{
	int posX;
	int posY;
	string pointName;
	Point()
		: posX(0)
		, posY(0)
	{
		pointName = GetPointName(0,0);
	}
	Point(int pX, int pY)
		: posX(pX)
		, posY(pY)
	{
		pointName = GetPointName(pX,pY);
	}

	Point & operator=(const Point & p)
	{
		if (this == &p)
		{
			return *this;
		}
		posX = p.posX;
		posY = p.posY;
		pointName = p.pointName;
		return *this;
	}

	friend bool operator<(const Point &p1, const Point & p2)
	{
		if (p1.posX == p2.posX)
		{
			return (p1.posY < p2.posY);
		}
		else
		{
			return p1.posX < p2.posX;
		}
	}

	friend bool operator==(const Point & point, const Point & p2)
	{
		return (p2.posX == point.posX) && (p2.posY == point.posY);
	}

	string GetPointName(int pX, int pY) const
	{
		stringstream ss;
		ss << pX << "-" << pY;
		return ss.str();
	}

	string ToString() const
	{
		stringstream ss;
		ss << "[" << posX << "," << posY << "]";
		return ss.str();
	}
     //计算距离
	double Distence(const Point & p) const
	{
		double d = sqrt((double)((p.posX-posX)*(p.posX-posX)+(p.posY-posY)*(p.posY-posY)));
		return d;
	}
};

 格子坐标与点列表映射表定义:

typedef map<int,vector<const Point*> > PointBlock;
typedef set<Point> PointSet; 

PointBlock _grids; //映射表
PointSet _points;//所有的点集合
int _blockUnit;	//划分格子的大小
Point _firstPoint;//坐标原点
class Geo
{
public:
	typedef map<int,vector<const Point*> > PointBlock;
	typedef set<Point> PointSet;

	Geo();
	~Geo();
	void GeneratePoints(int cnt);
	string SavePoints();

	int ReadPoints(const string & pointsFilePath);

	bool GetNearByPoints(const Point & p, double threshold, set<Point> & nearByPoints);
    
       //划分格子,生成映射表
	bool InitBlock(int unit);

	void OutPutBlocksMsg(const string & outputPath);
       
        //根据点原始坐标,生成格子编号
	int GetBlockIndex(const Point & p);
        //根据格子的坐标,生成格子编号
	int GetIdx(short dx, short dy);
	//根据格子编号,得到格子的X坐标
        short GetPosX(int Idx);
        //根据格子编号,得到格子的Y坐标
	short GetPosY(int Idx);

	void GetNearByPointsByGrid(const Point & p, double threshold, set<Point> & nearByPoints);

	void CountAllPointsNearByPoint(double threshold);
	void CountAllPointsNearByPointG(double threshold);

private:
	PointBlock _grids;
	PointSet _points;
	int _blockUnit;	//划分格子的大小
	Point _firstPoint;
};        

主要实现如下:

bool Geo::InitBlock(int unit)
{
	if (_points.empty())
	{
		return false;
	}
	_blockUnit = unit;
	//m_FirstPoint = *(m_Points.begin());//以第一个点作为坐标原点
	//std::cout << "FirstPoint X:" << m_FirstPoint.posX << "Y:" << m_FirstPoint.posY << std::endl;

	//生成栅格
	for (PointSet::iterator it = _points.begin(); it != _points.end(); ++it)
	{
		const Point & p = *it;
		int gridIndex = GetBlockIndex(p);
		vector<const Point *> & pList = _grids[gridIndex];
		pList.push_back(&p);
	}
	
	return true;
}

int Geo::GetBlockIndex(const Point & p)
{
	Point pDx(p.posX,_firstPoint.posY);
	double xDis = _firstPoint.Distence(pDx);
	short dX = (p.posX < _firstPoint.posX) ? -(short)(xDis / _blockUnit) : (short)(xDis / _blockUnit);

	Point pDy(_firstPoint.posX,p.posY);
	double yDis = _firstPoint.Distence(pDy);
	short dY = (p.posY < _firstPoint.posY) ? -(short)(yDis / _blockUnit) : (short)(yDis / _blockUnit);

	int gridIndex = GetIdx(dX,dY);

	return gridIndex;
}
int Geo::GetIdx( short dx, short dy )
{
	int Idx = ((int)dx << 16) | (dy & 0xFFFF);
	return Idx;
}

short Geo::GetPosX( int Idx )
{
	return (Idx >> 16 ) & 0xFFFF;
}

short Geo::GetPosY( int Idx )
{
	return Idx & 0xFFFF;
}
//求相邻点
void Geo::GetNearByPointsByGrid(const Point & p, double threshold, set<Point> & nearByPoints )
{
    int gridIndex = GetBlockIndex(p);
    PointBlock::iterator it = _grids.find(gridIndex);
    if (it == _grids.end())
    {
        return;
    }
    short dX = this->GetPosX(gridIndex);
    short dY = this->GetPosY(gridIndex);

    short num = (short)ceil((threshold / _blockUnit));
    num = num < 1 ? 1 : num;

    for (short i = dX - num; i <= dX + num; i++)
    {
        for (short j = dY - num; j <= dY + num; j++)
        {
            int tmpIndex = GetIdx(i,j);
            PointBlock::iterator it = _grids.find(tmpIndex);
            if (it == _grids.end())
            {
                continue;
            }
            vector<const Point *> & pList = it->second;
            for (vector<const Point*>::iterator pIt = pList.begin(); pIt != pList.end(); ++pIt)
            {
                if (p == *(*pIt))
                {
                    continue;
                }
                double dis = p.Distence(*(*pIt));
                if (dis <= threshold)
                {
                    nearByPoints.insert(*(*pIt));
                }
            }

        }
    }
}
//使用原始方法,即遍历所有点求相邻点
bool Geo::GetNearByPoints(const Point & p, double threshold, set<Point> & nearByPoints )
{
	for (PointSet::iterator it = _points.begin(); it != _points.end(); ++it)
	{
		if (*it == p)
		{
			continue;
		}
		double dis = p.Distence(*it);
		if (dis <= threshold)
		{
			nearByPoints.insert(*it);
		}
	}
	return true;
}

 测试结果:

10w数据,求所有点的在90m范围内的相邻点。

方法一: 遍历所有点计算距离 耗时864s

方法二:通过划分区域计算,耗时3.5s

差距巨大。

使用

原文地址:https://www.cnblogs.com/cmranger/p/4596721.html