Home  >  Article  >  Java  >  Detailed explanation of depth-first and breadth-first algorithm examples

Detailed explanation of depth-first and breadth-first algorithm examples

零下一度
零下一度Original
2017-06-25 10:58:025814browse

First of all, let me introduce the second part of the Algorithms website, which is the online course of the book Algorithms.

In addition, the course on graph algorithms on Coursera is also very good.

Several ways to represent graphs:

In that way (data structure ) represents the graph, which includes the following two requirements: (1) The space must be appropriate (2) The implementation of the instance method must be fast

Then there are three options to choose from:

(1) The set of edges is as follows:

Setofedges graph representation

is simple but does not satisfy the Two conditions - to implement the adjacency list adj(), you must traverse all the edges in the graph.

(2) Adjacency matrix:

adjacency-matrix graph

##Use a V multiplied by V The Boolean proof is not satisfied in space.

(3) Adjacency list:

adjacency-list graph

##Use a vertex as the index An array list, each element of which is a list of vertices adjacent to the vertex.

Since the above method has greater flexibility, if it is represented by an adjacency list, the following data structure can be defined to represent a Graph object.

public class Graph
{private readonly int verticals;//顶点个数private int edges;//边的个数private List<int>[] adjacency;//顶点联接列表public Graph(int vertical)
    {this.verticals = vertical;this.edges = 0;
        adjacency=new List<int>[vertical];for (int v = 0; v < vertical; v++)
        {
            adjacency[v]=new List<int>();
        }
    }public int GetVerticals ()
    {return verticals;
    }public int GetEdges()
    {return edges;
    }public void AddEdge(int verticalStart, int verticalEnd)
    {
        adjacency[verticalStart].Add(verticalEnd);
        adjacency[verticalEnd].Add(verticalStart);
        edges++;
    }public List<int> GetAdjacency(int vetical)
    {return adjacency[vetical];
    }
}
Depth-first algorithm

Before talking about the depth-first algorithm, we can first look at the maze exploration problem. The following is the correspondence between a maze and a graph:

Each intersection point in the maze represents a vertex in the graph, and each channel corresponds to an edge.

maze and graph

The Trémaux rope exploration method can be used to explore the maze. That is:

  • Put a rope behind you

  • Put it in every place you visit A rope marks the visited intersection points and passages

  • #When encountering a place that has been visited, follow the rope back to a place that has not been visited before:

The picture is as follows:

Tremaux maze exploration

Below It is a small animation of maze exploration:

maze exploration##The depth-first search algorithm simulates maze exploration. In actual graph processing algorithms, we usually separate the representation of the graph and the processing logic of the graph. So the overall design pattern of the algorithm is as follows:

    Create a Graph object
  • Create Graph The object is passed to the graph algorithm processing object, such as a Paths object
  • #Then query the processed results to obtain the information
  • We can see that the dfs method is called recursively,
maintains a marked[] mark array, and determines whether the node has been visited before calling

.

Depth first algorithm description

: When visiting a vertex1. Mark it as visited;

2. Recursively visit all its neighbor vertices that have not been marked.

public class DepthFirstSearch
{private bool[] marked;//记录顶点是否被标记private int count;//记录查找次数private DepthFirstSearch(Graph g, int v)
    {
        marked = new bool[g.GetVerticals()];
        dfs(g, v);
    }private void dfs(Graph g, int v)
    {
        marked[v] = true;
        count++;foreach (int vertical in g.GetAdjacency(v))
        {if (!marked[vertical])
                dfs(g,vertical);
        }
    }public bool IsMarked(int vertical)
    {return marked[vertical];
    }public int Count()
    {return count;
    }
}

试验一个算法最简单的办法是找一个简单的例子来实现。

trace of depth-first search

算法应用:

连通性。给定一幅图,回答“两个给定顶点是否连通?” 或者 “图中有多少个连通子图?”

寻找路径。给定一幅图和一个起点,回答“从s到给定目的顶点v是否存在一条路径?如果有,找出这条路径。”

检测环。给定的图是无环图吗?

双色问题。能够用两种颜色将图的所有顶点着色,使得任意一条边连个顶点的颜色都不相同?这个问题等价于:这是一个二分图吗?

 

深度优先路径查询

有了这个基础,我们可以实现基于深度优先的路径查询,要实现路径查询,我们必须定义一个变量来记录所探索到的路径

所以在上面的基础上定义一个edgesTo变量来后向记录所有到s的顶点的记录,和仅记录从当前节点到起始节点不同,我们记录图中的每一个节点到开始节点的路径。为了完成这一日任务,通过设置edgesTo[w]=v,我们记录从v到w的边,换句话说,v-w是做后一条从s到达w的边。 edgesTo[]其实是一个指向其父节点的树

注意代码只是在前面算法的基础上维护了一个edgTo数组,并用栈Stack保存路径。

public class DepthFirstPaths
{private bool[] marked;//记录是否被dfs访问过
    private int[] edgesTo;//记录最后一个到当前节点的顶点private int s;//搜索的起始点public DepthFirstPaths(Graph g, int s)
    {
        marked = new bool[g.GetVerticals()];
        edgesTo = new int[g.GetVerticals()];this.s = s;
        dfs(g, s);
    }private void dfs(Graph g, int v)
    {
        marked[v] = true;foreach (int w in g.GetAdjacency(v))
        {if (!marked[w])
            {                edgesTo[w] = v;dfs(g,w);
            }
        }
    }public bool HasPathTo(int v)
    {return marked[v];
    }public Stack<int> PathTo(int v){if (!HasPathTo(v)) return null;
        Stack<int> path = new Stack<int>();for (int x = v; x!=s; x=edgesTo[x])
        {
            path.Push(x);
        }
        path.Push(s);return path;
    }
}

 Trace depth-first search of computer 5

上图中是黑色线条表示 深度优先搜索中,所有定点到原点0的路径, 他是通过edgeTo[]这个变量记录的,可以从右边可以看出,

他其实是一颗树,树根即是原点,每个子节点到树根的路径即是从原点到该子节点的路径。

下图是深度优先搜索算法的一个简单例子的追踪。

 trace depth-first search

 

 

连通分量

API如下:

CC的实现使用了marked[ ]数组来寻找一个顶点作为每个连通分量中深度优先搜索的起点。递归的深搜第一次调用的参数是顶点0,会标记所有与0连通的顶点。然后构造函数中的for循环会查找每个没有被标记的顶点并递归调用dfs来标记和它相邻的所有顶点。另外,它还使用了一个以顶点作为索引的数组id[ ],将同一个连通分量中的顶点和连通分量的标识符关联起来。这个数组使得connected( )方法的实现变得十分简单。

public class CC {private boolean[] marked;private int[] id;private int count;public CC(Graph g){
        marked = new boolean[g.getVertexCount()];
        id = new int[g.getVertexCount()];for(int s = 0; s < g.getVertexCount(); s++){if(!marked[s]){
                dfs(g,s);
                count++;
            }
        }
    }private void dfs(Graph g, int v) {
        marked[v] = true;
        id[v] = count;for(int w: g.adj(v))if(!marked[w])
                dfs(g,w);
    }/** v和w连通吗*/public boolean connected(int v, int w)    { return id[v] == id[w]; }/** v所在的连通分量的标识符*/public int id(int v)    { return id[v]; }/** 连通分量数*/public int count()        {return count;}

 

检测环

/**
 * 给定的图是无环图吗
 * 检测自环:假设没有自环,没有平行边 */public class Cycle {private boolean[] marked;private boolean hasCycle;public Cycle(Graph g){
        marked = new boolean[g.getVertexCount()];for(int i = 0;i<g.getVertexCount();i++)if(!marked[i])    dfs(g, i, i);
    }private void dfs(Graph g, int v, int u) {
        marked[v] = true;for(int w: g.adj(v))if(!marked[w])    dfs(g, w, v); // 若w没被标记过,那么从w继续递归深搜,把w的父节点作为第二参数else if(w != u) hasCycle = true; // 若w被标记过,那么若无环,w必然和父节点相同,否则就是有环    }/** 是否含有环*/public boolean hasCycle(){return hasCycle;}

 

双色问题

/**
 * 双色问题:能够用两种颜色将图的所有顶点着色,使得任意一条边上的两个端点的颜色都不同吗?
 * 等价于:判断是否是二分图的问题 */public class TwoColor {private boolean[] marked;private boolean[] color;private boolean isColorable;public TwoColor(Graph g){
        isColorable = true;
        marked = new boolean[g.getVertexCount()];
        color = new boolean[g.getVertexCount()];for(int i = 0; i<g.getVertexCount(); i++)//遍历所有顶点if(!marked[i])    dfs(g, i);//没有mark就进行深搜    }private void dfs(Graph g, int v) {
        marked[v] = true;        // 标记for(int w: g.adj(v))    // 对邻接表进行遍历if(!marked[w]){        // 如果没有被标记color[w] = !color[v];    // 当前w节点颜色置为和父节点不同的颜色dfs(g, w);                // 对当前节点继续深搜}else if(color[w] == color[v]){    // 如果已经被标记,看是否颜色和父节点相同isColorable = false;         // 若相同则不是二分图            }
    }/** 是否是二分图*/public boolean isBipartite(){return isColorable;}

 

 

广度优先算法

通常我们更关注的是一类单源最短路径的问题,那就是给定一个图和一个源S,是否存在一条从s到给定定点v的路径,如果存在,找出最短的那条(这里最短定义为边的条数最小)

深度优先算法是将未被访问的节点放到一个堆中(stack),虽然在上面的代码中没有明确在代码中写stack,但是 递归间接的利用递归堆实现了这一原理。

和深度优先算法不同, 广度优先是将所有未被访问的节点放到了队列中。其主要原理是:

先将起点加入队列,然后重复一下步骤直到队列为空:

1.取队列中的下一个顶点V并标记它

2.将与v相邻的所有未被标记过的顶点加入队列

广度优先是以距离递增的方式来搜索路径的。

class BreadthFirstSearch
{private bool[] marked;private int[] edgeTo;private int sourceVetical;//Source verticalpublic BreadthFirstSearch(Graph g, int s)
    {
        marked=new bool[g.GetVerticals()];
        edgeTo=new int[g.GetVerticals()];this.sourceVetical = s;
        bfs(g, s);
    }private void bfs(Graph g, int s)
    {
        Queue<int> queue = new Queue<int>();
        marked[s] = true;
        queue.Enqueue(s);while (queue.Count()!=0)
        {int v = queue.Dequeue();foreach (int w in g.GetAdjacency(v))
            {if (!marked[w])
                {
                    edgeTo[w] = v;
                    marked[w] = true;
                    queue.Enqueue(w);
                }
            }
        }
    }public bool HasPathTo(int v)
    {return marked[v];
    }public Stack<int> PathTo(int v)
    {if (!HasPathTo(v)) return null;

        Stack<int> path = new Stack<int>();for (int x = v; x!=sourceVetical; x=edgeTo[x])
        {
            path.Push(x);
        }
        path.Push(sourceVetical);return path;
    }

}

算法应用:最短路径问题

 

总结:

深度优先搜索和广度优先搜索都是将起点存入数据结构中,然后重复一下步骤直到数据结构被清空:

1.取其中的下一个顶点并标记它

2.将v的所有相邻而未被标记的顶点加入数据结构

这两个算法 的不同之处仅在于从数据结构中获取下一个顶点的规则(广度优先来说是最早加入的顶点,对于深度优先搜索来说是最晚加入的顶点)。

 

The above is the detailed content of Detailed explanation of depth-first and breadth-first algorithm examples. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn