前言: 为了检验路径搜索算法的优劣,需要用大量的图的数据来运行对应的算法,例如深度优先搜搜、宽度优先搜索、迪杰斯特拉算法等等,而对于大多数实际问题,例如搜索最短路径的问题,其问题空间本身是一种特殊的图——平面无向图,无向图的定义相信大家都明白,所谓“平面”,即如果以一个合适的角度将图中的所有节点和边投影到一个平面上,不能有两条及以上数量的边相互交叉。
如果能随机生成平面无向图,我们就可以用其来对各种搜索算法进行测试。基于此需求,本人想出了一个随机生成平面无向图的算法,并加以实现。
代码(已经上传至码云):随机生成平面无向图
算法描述
图——可以简单的理解为节点集合和边集合,想一想,如果想要随机生成平面无向图,我们需要保证什么?
我们需要确保三点:图的连通性、图为平面图、图的生成是具有一定随机的。
对于连通性,我们需要先生成图的最小连通子图,从而保证图的连通性;对于图的“平面性”,我们可以赋予节点一个二维坐标(x,y),从而保证所有的节点都在一个平面上,对于图的“随机性”,我们可以在生成节点坐标时,随机的确定其(x,y)的具体值。
在这里,需要明确一点的是“对于一个图来说,其极小连通子图一定是平面图”,这点可以用反证法来证明,如果极小连通子图不是平面图,则至少会存在一个类似多面体的结构,比如四面体,这样的话就与极小连通子图的性质相悖了!
经过上述的分析,我们有了初步的算法思路:
(1)根据节点数量生成随机的极小连通子图;生成的极小连通子图时,给每个节点都赋予一个坐标(x,y),坐标中的x、y的值是具有随机性的,并保证节点之间的边(在这里就是线段)不交叉;
(2)添加剩余的边,同时保证添加的边与现存的边不会相交,直到添加完所有的边。
为了存储生成的图,我们用了两个数组来分别存储节点的信息和边的信息:NodeArray[NodeCounts]和EdgeArray[EdgeCounts]。
算法的具体描述如下:
输入:节点个数N、边的个数M、边的长度范围;
输出:节点数组NodeArray[],边数组EdgeArray[]
准备工作:新建节点类Node和边类Edge,Node类的数据为节点的x坐标、y坐标和节点的编号,Edge类的数据为边两端的节点node1和node2.
具体步骤:
(1)生成1-N的随机序列Random[];
(2)顺序的遍历随机序列,从中拿出Random[i]作为新节点NewNode的编号ID(编号从1-N);
(3)如果新节点NewNode是第一个确定编号的点,将其坐标置为(0,0);如果不是,则从已经确定了坐标的旧节点中随机挑选一个OldNode来与NewNode进行“配对”,并随机确定NewNode的坐标(x,y),生成新边NewEdge(NewNode,OldNode);
(4)检验新边NewEdge(NewNode,OldNode)是否有效,如果无效,则通过随机生成NewNode的坐标(x,y)更新新边NewEdge,具体约束条件为:a.NewEdge(NewNode,OldNode)的长度在输入的边的范围内;b.NewEdge(NewNode,OldNode)必须保证与已经存在的边不能有交点(除了OldNode的其它边);c.NewNode的坐标不能在已有的边上(即点在线段上);
(5)如果NewEdge(NewNode,OldNode)有效,则将NewNode添加进节点数组NodeArray[]中,将NewEdge添加到边数组EdgeArray[]中,然后返回(3)继续生成新边;
(6)当所有的节点都确定坐标后,就生成了最小连通子树,此时确定了(N-1)条边,接下来再随机添加两个节点之间的边,总数为(M-(N-1)),这里的边也得保证与已经存在的边不能有交点(除了其两个节点的其它边);
(7)当M条边都添加完了之后,输出结果;
关键代码
工具:VS2017
代码(已经上传至码云):随机生成平面无向图
节点与边用容器存储:
vector<Node> NodeArray;
vector<Edge> EdgeArray;
//************************************
// Method: CreatPlaneUndirectedGraph
// FullName: CreatPlaneUndirectedGraph
// Access: public
// Returns: void
// Qualifier:生成平面连通无向图
// Parameter: int NodeCounts 节点数量
// Parameter: int EdgeCounts 边的数量
// Parameter: int EdgeMaxLen 边的最大值
// Parameter: int EdgeMinLen 边的最小值
//************************************
void CreatPlaneUndirectedGraph(int NodeCounts, int EdgeCounts, int EdgeMaxLen, int EdgeMinLen)
{
//先生成最小平面连通无向图
//生成一个随机序列
int *randomArray = new int[NodeCounts];
RandomArray(randomArray,NodeCounts);
srand(time(NULL));
//顺序的遍历随机序列,添加边
for (int i = 0; i < NodeCounts; ++i)
{
int Node_id = randomArray[i];
//如果是第一个,设置其坐标为(0,0)
if (i == 0)
{
Node new_node(0,0, Node_id);
NodeArray.push_back(new_node);
}
//否则,随机确定其坐标
else
{
//从已经确定坐标的点中,随机挑选一个出来作为连接节点
int val = rand() % i;
Node choose_node= NodeArray.at(val);
//产生符合要求的新节点
Node new_node(0,0, Node_id);
CreatNewNodeX_Y(new_node,choose_node,EdgeMaxLen,EdgeMinLen);
//将边和节点添加进容器中
Edge new_edge(choose_node,new_node);
EdgeArray.push_back(new_edge);
NodeArray.push_back(new_node);
}
}
//再添加剩余的边
while (EdgeArray.size() < EdgeCounts)
{
int node1_id=0, node2_id=0;
while (node1_id == node2_id)
{
//RandomCreatFunc(1, NodeArray.size());
node1_id = rand() % NodeArray.size();
node2_id = rand() % NodeArray.size();
}
Node choose_node1=NodeArray.at(node1_id);
Node choose_node2= NodeArray.at(node2_id);
if (IsNodeConnected(choose_node1, choose_node2) == false)
{
Edge newEdge(choose_node1, choose_node2);
if (IsEdgeIntersectedEdgeArray(newEdge) == false)
EdgeArray.push_back(newEdge);
}
}
}
辅助函数为:
int max(int x,int y)
{
return x > y ? x : y;
}
int min(int x, int y)
{
return x > y ? y : x;
}
double mult(Node a, Node b, Node c)
{
return (a.GetX() - c.GetX())*(b.GetY() - c.GetY()) -