循环查找算法

时间:2010-12-19 14:17:50

标签: algorithm graph

我需要找到一个在给定点开始和结束的循环。不保证它存在。 我使用bool[,] points来指示哪个点可以处于循环中。点数只能在网格上。 points表示网格上的给定点是否可以循环。 我需要使用最小点数来找到这个循环。 一点只能使用一次。 连接只能是垂直或水平的。

让这成为我们的观点(红色是起点):

删除死亡的ImageShack链接

我意识到我可以这样做:

while(numberOfPointsChanged)
{
    //remove points that are alone in row or column
}

所以我有:

删除死亡的ImageShack链接

现在,我可以找到路径。

删除死亡的ImageShack链接

但是如果有一些点没有被这个循环删除但不应该在路径中呢?

我写了代码:

class MyPoint
{
    public int X { get; set; }
    public int Y { get; set; }
    public List<MyPoint> Neighbours = new List<MyPoint>();
    public MyPoint parent = null;
    public bool marked = false;
}

    private static MyPoint LoopSearch2(bool[,] mask, int supIndexStart, int recIndexStart)
    {
        List<MyPoint> points = new List<MyPoint>();

        //here begins translation bool[,] to list of points
        points.Add(new MyPoint { X = recIndexStart, Y = supIndexStart });

        for (int i = 0; i < mask.GetLength(0); i++)
        {
            for (int j = 0; j < mask.GetLength(1); j++)
            {
                if (mask[i, j])
                {
                    points.Add(new MyPoint { X = j, Y = i });
                }
            }
        }

        for (int i = 0; i < points.Count; i++)
        {
            for (int j = 0; j < points.Count; j++)
            {
                if (i != j)
                {
                    if (points[i].X == points[j].X || points[i].Y == points[j].Y)
                    {
                        points[i].Neighbours.Add(points[j]);
                    }
                }
            }
        }
        //end of translating

        List<MyPoint> queue = new List<MyPoint>();
        MyPoint start = (points[0]); //beginning point
        start.marked = true; //it is marked
        MyPoint last=null;   //last point. this will be returned
        queue.Add(points[0]);


        while(queue.Count>0)
        {
            MyPoint current = queue.First(); //taking point from queue
            queue.Remove(current);           //removing it

            foreach(MyPoint neighbour in current.Neighbours) //checking Neighbours
            {
                if (!neighbour.marked) //in neighbour isn't marked adding it to queue
                {
                    neighbour.marked = true;
                    neighbour.parent = current;
                    queue.Add(neighbour);
                }
                //if neighbour is marked checking if it is startig point and if neighbour's parent is current point. if it is not that means that loop already got here so we start searching parents to got to starting point
                else if(!neighbour.Equals(start) && !neighbour.parent.Equals(current))
                {                        
                    current = neighbour;
                    while(true)
                    {
                        if (current.parent.Equals(start))
                        {
                            last = current;
                            break;
                        }
                        else
                            current = current.parent;

                    }
                    break;
                }
            }
        }

        return last;            
    }

但它不起作用。它找到的路径包含两点:start和它的第一个邻居 我做错了什么?

修改 忘了提......水平连接后必须有垂直,水平,垂直等等...... 每行和每列中的更多内容需要最多两个点(两个或没有)在循环中。但这种情况与“循环必须是最短的”相同。

4 个答案:

答案 0 :(得分:4)

首先,您应该将表示更改为更有效的表示。你应该使顶点成为一个结构/类,它保留了连接顶点的列表。

更改了表示后,您可以使用breadth-first search轻松找到最短周期。

您可以使用以下技巧加快搜索速度:以广度优先顺序遍历图形,标记遍历的顶点(并在通往每个顶点的根的路上存储“父顶点”数字)。一旦找到已标记的顶点,搜索就会完成。您可以通过返回存储的“父”顶点找到从找到的顶点到根的两条路径。


修改
你确定你的代码是对的吗?我尝试了以下方法:

while (queue.Count > 0)
{
    MyPoint current = queue.First(); //taking point from queue
    queue.Remove(current);           //removing it

    foreach (MyPoint neighbour in current.Neighbours) //checking Neighbours
    {
        if (!neighbour.marked) //if neighbour isn't marked adding it to queue
        {
            neighbour.marked = true;
            neighbour.parent = current;
            queue.Add(neighbour);
        }
        else if (!neighbour.Equals(current.parent)) // not considering own parent
        {
            // found!
            List<MyPoint> loop = new List<MyPoint>();
            MyPoint p = current;
            do
            {
                loop.Add(p);
                p = p.parent;
            }
            while (p != null);
            p = neighbour;
            while (!p.Equals(start))
            {
                loop.Add(p);
                p = p.parent;
            }
            return loop;
        }
    }
}

return null;

而不是代码中的相应部分(我也将返回类型更改为List<MyPoint>)。它工作正确地找到一个较小的循环,由3个点组成:红点,正上方的点和正下方的点。

答案 1 :(得分:3)

这就是我所做的。我不知道它是否已经过优化但是它确实可以正常工作。我没有像@marcog建议的那样对点进行排序。

private static bool LoopSearch2(bool[,] mask, int supIndexStart, int recIndexStart, out List<MyPoint> path)
    {
        List<MyPoint> points = new List<MyPoint>();
        points.Add(new MyPoint { X = recIndexStart, Y = supIndexStart });

        for (int i = 0; i < mask.GetLength(0); i++)
        {
            for (int j = 0; j < mask.GetLength(1); j++)
            {
                if (mask[i, j])
                {
                    points.Add(new MyPoint { X = j, Y = i });
                }
            }
        }

        for (int i = 0; i < points.Count; i++)
        {
            for (int j = 0; j < points.Count; j++)
            {
                if (i != j)
                {
                    if (points[i].X == points[j].X || points[i].Y == points[j].Y)
                    {
                        points[i].Neighbours.Add(points[j]);
                    }
                }
            }
        }

        List<MyPoint> queue = new List<MyPoint>();
        MyPoint start = (points[0]);
        start.marked = true;
        queue.Add(points[0]);

        path = new List<MyPoint>();

        bool found = false;

        while(queue.Count>0)
        {
            MyPoint current = queue.First();
            queue.Remove(current);

            foreach (MyPoint neighbour in current.Neighbours)
            {
                if (!neighbour.marked)
                {
                    neighbour.marked = true;
                    neighbour.parent = current;
                    queue.Add(neighbour);
                }
                else
                {
                    if (neighbour.parent != null && neighbour.parent.Equals(current))
                        continue;

                    if (current.parent == null)
                        continue;

                    bool previousConnectionHorizontal = current.parent.Y == current.Y;
                    bool currentConnectionHorizontal = current.Y == neighbour.Y;

                    if (previousConnectionHorizontal != currentConnectionHorizontal)
                    {
                        MyPoint prev = current;

                        while (true)
                        {
                            path.Add(prev);
                            if (prev.Equals(start))
                                break;
                            prev = prev.parent;
                        }

                        path.Reverse();

                        prev = neighbour;

                        while (true)
                        {
                            if (prev.Equals(start))
                                break;
                            path.Add(prev);                                
                            prev = prev.parent;
                        }

                        found = true;
                        break;
                    }                      
                }
                if (found) break;
            }
            if (found) break;
        }

        if (path.Count == 0)
        {
            path = null;
            return false;
        }
        return true;   
    }   

答案 2 :(得分:2)

如果实施不当,你的点移除步骤是最坏情况O(N ^ 3),最坏的情况是在每次迭代中剥离一个点。因为它并不总能在循环检测中为您节省大量的计算,所以我会避免这样做,因为它还为解决方案增加了额外的复杂性。

首先从点集中创建adjacency list。如果您按X和Y(单独)对点进行排序并按X和Y的顺序迭代点,则可以在O(NlogN)中有效地执行此操作。然后,要找到最短周期长度(由点数确定),请启动最初抛出队列中的所有点,从每个点开始BFS。在遍历边缘时,将路径源与当前点一起存储。然后你会知道BFS何时返回源,在这种情况下我们找到了一个循环。如果在找到循环之前最终得到一个空队列,则不存在。注意不要立即追溯到前一点,否则最终将导致由两点形成的失效循环。例如,您可能还希望避免由点(0, 0)(0, 2)(0, 1)形成的周期,因为这会形成一条直线。

BFS可能最糟糕的情况是呈指数级,但我相信这种情况要么被证明不存在,要么极为罕见,因为图表越密集,你就会越快找到一个循环,而图表就越稀疏队列越小。平均而言,它更可能与邻接列表构造更接近相同的运行时,或者在最坏的现实情况下O(N ^ 2)。

答案 3 :(得分:0)

我认为我会使用Dijkstra's algorithm的改编变体,它会在第二次到达任何节点时停止并返回循环。如果这种情况从未发生过,那么就没有一个循环。

这种方法应该比广度优先或深度优先搜索更有效 ,尤其是如果您有许多节点。保证您只访问每个节点一次,从而获得线性运行时。