起因是之前做某个功能用到了一种思想,先将图上的数据整理出来,之间的路径关系转换成字符串去查找路径。(当时没有封装,而且那功能想要的结果也不止简单的从A到D查询路径,是要梳理出图上所有路径,根据规则排除穿插路径)
先放一下本篇算法的最后效果(其实大家应该都想象的出来效果甚至算法,网上现有的应该挺多的)
本人由于有上面提到的那个功能思路,在写这个路径查询的时候没有在网上查资料,也没考虑到什么高效,纯靠楞想一点点敲出来。
一开始想算法时候没有明确的代码思路,这个时候可以先手动模拟算法在excel上走走看流程,找找有哪些逻辑规律。
整个算法大体流程是:
1、读取图纸数据并按照特定规则转换数据
2、查询路径
3、将路径转换成结果
算法不变的是2、3点,第1点要根据图纸数据实际情况具体分析。
第1步提到的特定规则,是以线为主体,记录两端关联的对象,如果线连着线,为了统一规则需要虚拟出一个对象。下图截取了一段,虚拟的点是"50D,50E,511"。
第2步的查询路径:根据第1步的结果拿到了数据结构 线-关联1|关联2,查询出起始所在数据项集合A,遍历集合A里另一个关联,递归直到查询不到关联或者遇到终点,查询不到的话回退到上个分支点(通过数据状态回退)。(查询到的数据是路径里经过的节点集合)
第3步将路径转换成结果:得到路径节点集合后 到第1步的总数据里 拿到两个节点中的线。(按照第1步的规则,读取出的数据两个节点只会有一条线)
根据前面的工作,开始准备创建类(我一开始没考虑那么细节,都是后期迭代出来的)
public class RuleMain
{
/// 起始
public Entity startEntity;
/// 结束
public Entity endEntity;
/// 输出结果
public LinePathMode linePathMode;
public RuleMain(Entity startEntity, Entity endEntity)
{
this.startEntity = startEntity;
this.endEntity = endEntity;
linePathMode = new LinePathMode();
}
//准备数据会根据情况变化 摘出去
#region 准备数据
#endregion
#region 处理数据(查询路径
/// <summary>
/// 查询路径
/// </summary>
/// <param name="currentHandle">当前起始端</param>
/// <param name="endHandle">最终目标</param>
/// <param name="linePathMode">要处理的数据对象</param>
/// <param name="currentPath">查询过程中的记录</param>
/// <param name="resultPaths">最终路径结果</param>
/// <param name="branchLog">分支节点记录</param>
/// <param name="branchAllLog">查询过程中节点记录</param>
/// <param name="backState">是否回退</param>
private void DealLinePathMode(string currentHandle, string endHandle, LinePathMode linePathMode,
List<string> currentPath, ref List<List<string>> resultPaths, List<Tuple<string, int, int>> branchLog, List<string> branchAllLog, ref bool backState)
{
linePathMode.SetElementState(currentHandle, ElementState.Activate);
if (branchAllLog.Contains(currentHandle)) branchAllLog.Remove(currentHandle);
//查找另一端可能的元素
IEnumerable<string> otherHandles = linePathMode.GetOtherElementHandles(currentHandle);
foreach (string otherHandle in otherHandles)
{
if (!backState)
{
linePathMode.SetElementState(otherHandle, ElementState.Activate);
if (otherHandles.Count() > 1 && !branchAllLog.Contains(currentHandle))
{//有分支,记录下截止当前已经查询过的记录
branchAllLog.Add(currentHandle);
branchLog.Add(new Tuple<string, int, int>(currentHandle, currentPath.Count, otherHandles.Count()));
}
currentPath.Add(otherHandle);
if (otherHandle == endHandle)
{//找到结束元素
linePathMode.SetElementState(endHandle, ElementState.Activate);
string[] temp = new string[currentPath.Count];
currentPath.CopyTo(temp);
resultPaths.Add(temp.ToList());
//回到上一个
if (branchLog.Count > 0) backState = true;
}
else
{
DealLinePathMode(otherHandle, endHandle, linePathMode, currentPath, ref resultPaths, branchLog, branchAllLog, ref backState);
}
}
if (backState)
{//回退
linePathMode.SetElementState(currentPath.LastOrDefault(), ElementState.None);
currentPath.RemoveAt(currentPath.Count - 1);
if (currentHandle == branchLog.LastOrDefault().Item1)
{
backState = false;
Tuple<string, int, int> temp = branchLog.LastOrDefault();
if (temp.Item3 - 2 > 0)
{
branchLog.RemoveAt(branchLog.Count - 1);
branchLog.Add(new Tuple<string, int, int>(temp.Item1, temp.Item2, temp.Item3 - 1));
}
else
{
branchLog.RemoveAt(branchLog.Count - 1);
}
}
}
}
if (otherHandles.Count() == 0)
{//找不到路径, 回到上一个分支点的状态
if (branchLog.Count > 0) backState = true;
}
}
#endregion
#region 梳理数据,返回最终结果
private static List<Tuple<List<Entity>, List<Entity>>> GetFindPathResults(Entity startEntity, LinePathMode linePathMode, params List<string>[] handlePaths)
{
List<Tuple<List<Entity>, List<Entity>>> findPathResults = new List<Tuple<List<Entity>, List<Entity>>>();
foreach (var item in handlePaths)
{
findPathResults.Add(GetFindPathResult(startEntity, linePathMode, item));
}
return findPathResults;
}
/// <summary>
/// 将查询到的路径转变成结果数据
/// </summary>
/// <param name="startEntity"></param>
/// <param name="linePathMode"></param>
/// <param name="handlePath"></param>
/// <returns></returns>
private static Tuple<List<Entity>, List<Entity>> GetFindPathResult(Entity startEntity, LinePathMode linePathMode, List<string> handlePath)
{
List<Entity> elements = new List<Entity>();
List<Entity> lines = new List<Entity>();
if (handlePath == null) return new Tuple<List<Entity>, List<Entity>>(elements, lines);
Entity currentEntity = startEntity;
for (int i = 0; i < handlePath.Count - 1; i++)
{
//得到两个元素之间关联的线
ItemData itemData = linePathMode.GetItemDataByTwoHandle(handlePath[i], handlePath[i + 1]);
elements.Add(linePathMode.GetElement(handlePath[i]).EntityElement);
elements.Add(linePathMode.GetElement(handlePath[i + 1]).EntityElement);
lines.Add(itemData.LineEntity);
}
return new Tuple<List<Entity>, List<Entity>>(elements, lines);
}
#endregion
/// <summary>
/// 流程
/// </summary>
/// <returns></returns>
public List<Tuple<List<Entity>, List<Entity>>> GetResult(BaseRuleData baseRuleData)
{
linePathMode = baseRuleData.GetResult();
string currentHandle = startEntity.Handle.ToString();
string endHandle = endEntity.Handle.ToString();
List<string> currentPath = new List<string>();
List<List<string>> resultPaths = new List<List<string>>();
List<Tuple<string, int, int>> branchLog = new List<Tuple<string, int, int>>();
//记录本次线路经过的元素
currentPath.Add(currentHandle);
bool backState = false;
List<string> branchAllLog = new List<string>();
DealLinePathMode(currentHandle, endHandle, linePathMode, currentPath, ref resultPaths, branchLog, branchAllLog, ref backState);
List<string> handlePathMin = resultPaths.OrderBy(o => o.Count).FirstOrDefault();//路径节点最少的
List<string> handlePathMax = resultPaths.OrderBy(o => o.Count).LastOrDefault();//路径节点最长的
//将查询到的路径数据整理成想要的结果
return GetFindPathResults(startEntity, linePathMode, handlePathMin, handlePathMax);
}
}
//这是调用的方法
RuleDataPath ruleDataPath = new RuleDataPath(draw, startEntity);//准备数据
RuleMain ruleMain = new RuleMain(startEntity, endEntity);
return ruleMain.GetResult(ruleDataPath);
扩展:
从类图的字段可以看出我没有记录线的长度,结果只考虑了节点数量,如果考虑路径长短只需要在声明ItemData的时候记录下当前线段的长度即可,不需要做别的额外处理,因为这个算法把所有可能的路径都查询到了。只要记录了相关数据,想要得出最长、最短、路过节点最多、最少,最后无非是对每条路径的筛选。同理如果是两个节点之间有多条线相连也只要在ItemData里记录下就好。