欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

图的遍历(深度优先,广度优先)总结-java版

程序员文章站 2022-05-21 16:21:18
...

目录

图的遍历简介

广度优先搜索算法

算法的思想

算法实现的思想

算法的复杂度

深度优先搜索算法

算法的思想

算法实现的思想

算法的复杂度

java实现

图的基础java代码

广度优先算法

深度优先算法

测试


图的遍历简介

图的遍历是指从图中的任一顶点出发,对图中的所有顶点访问一次并且只访问一次。图的遍历是图的一种基本操作,图中的许多其他操作也都是建立在遍历的基础之上。在图中,没有特殊的顶点被指定为起始顶点,图的遍历可以从任何顶点开始。图的遍历主要有深度优先搜索和广度优先搜索两种方式。

广度优先搜索算法

算法的思想

从图中的某一个顶点x出发,访问x,然后访问与x所相邻的所有未被访问的顶点x1、x2……xn,接着再依次访问与x1、x2……xn相邻的未被访问的所有顶点。依次类推,直至图中的每个顶点都被访问。

算法实现的思想

广度优先遍历背后基于队列,下面介绍一下具体实现的方法:

访问起始顶点,并将插入队列;
从队列中删除队头顶点,将与其相邻的所有的未被访问的顶点插入队列中;
重复第二步,直至队列为空。
未被访问的顶点怎么识别呢?利用顶点的isVisited属性来进行标记。

同时还需要另一个队列(或者是list)用来保存访问的顺序,另一个队列的顶点入队顺序就是图的广度遍历顺序,因此,该队列保持 与 前一个队列的顶点入队操作 一致。由于前一个队列是辅助遍历的,它有出队的操作,它就不能记录整个顶点的访问序列了,因此才需要一个保存访问顺序的队列。当整个过程遍历完成后,将 保存访问顺序的队列 进行出队操作,即可得到整个图的广度优先遍历的顺序了。

算法的复杂度

该算法的时间复杂度为--遍历之前,给每个顶点进行初始化时需要遍历所有顶点V,在遍历过程中需要判断顶点的邻接点是否被遍历,也即遍历该顶点的邻接表,邻接表代表的实质是边,边总数为E,故总的时间复杂度为O(V+E),空间复杂度为O(V)--辅助队列的长度为顶点的长度

深度优先搜索算法

算法的思想

从图中的某一个顶点x出发,访问x,然后遍历任何一个与x相邻的未被访问的顶点y,再遍历任何一个与y相邻的未被访问的顶点z……依次类推,直到到达一个所有邻接点都被访问的顶点为止;然后,依次回退到尚有邻接点未被访问过的顶点,重复上述过程,直到图中的全部顶点都被访问过为止。

算法实现的思想

深度优先遍历背后基于堆栈,有两种方式:第一种是在程序中显示构造堆栈,利用压栈出栈操作实现;第二种是利用递归函数调用,基于递归程序栈实现。本文介绍第一种方式:

访问起始顶点,并将其压入栈中;
从栈中弹出最上面的顶点,将与其相邻的未被访问的顶点压入栈中;当深度优先遍历到某个顶点时,若该顶点的所有邻接点均已经被访问,则发生回溯,即返回去遍历 该顶点 的 前驱顶点 的 未被访问的某个邻接点。(相当于没有把新的节点压入栈中,下一个被访问的节点是当前节点下面的节点)
重复第二步,直至栈为空栈。
未被访问的顶点怎么识别呢?利用顶点的isVisited属性来进行标记。

对于深度优先而言,访问了 顶点A 时,紧接着只需要找到 顶点A 的一个未被访问的邻接点,再访问该邻接点即可。而对于广度优先,访问了 顶点A 时,就是要寻找 顶点A的 所有未被访问的邻接点,再访问 所有的这些邻接点。

同时还需要另一个队列(或者是list)用来保存访问的顺序

算法的复杂度

深度优先遍历的算法的时间复杂度:O(V+E)--遍历之前,给每个顶点进行初始化时需要遍历所有顶点V,在遍历过程中需要判断顶点的邻接点是否被遍历,也即遍历该顶点的邻接表,邻接表代表的实质是边,边总数为 E,故总的时间复杂度为O(V+E);空间复杂度:O(V)--用了一个栈,一个list

java实现

图的基础java代码

可以参考  https://blog.csdn.net/xushiyu1996818/article/details/90373591

广度优先算法

	/**
	 * 将图中所有节点的isVisited属性变成false
	 */
	public void clearVisitStatus(){
		Iterator<Vertex<T>> iteratorVertex=getVertexIterator();
		while(iteratorVertex.hasNext()){
			Vertex<T> vertex=iteratorVertex.next();
			vertex.unVisit();
		}
	}
	
	/** 广度优先遍历图,以origin作为标识的顶点作为起始点
	 * @param origin
	 * @return  返回一个list,其中元素为广度遍历到的顶点,顺序为广度遍历的顺序
	 * 如果队列中没有以origin作为标识的顶点,返回一个空的list
	 */
	public List<Vertex<T>> breathFirstTraversal(T origin){		
		//清除状态
		clearVisitStatus();
		//这个是广度遍历的队列
		Queue<Vertex<T>> queue=new LinkedList<>();
		//这个是返回的list,用来存储广度遍历到的顶点
		List<Vertex<T>> result=new LinkedList<>();
		Vertex<T> vertex=vertexMap.get(origin);
		if(vertex==null){
			//如果队列中没有以origin作为标识的顶点,返回一个空的list
			System.out.println("没有找到顶点"+origin);
			return result;
		}
		//以origin作为标识的顶点作为起始点
		queue.offer(vertex);
		//将这个顶点设置为已访问,访问状态在加入队列时设置
		vertex.visit();
		while(!queue.isEmpty()){
			vertex=queue.poll();
			//result里用来存储广度遍历到的顶点
			result.add(vertex);
			System.out.println("遍历到顶点:"+vertex.getLabel());
			Iterator<Edge> edgeIterator=vertex.getEdgeIterator();
			while(edgeIterator.hasNext()){
				Edge edge=edgeIterator.next();
				Vertex other=edge.getEndVertex();
				if(!other.isVisited()){
					queue.offer(other);
					//将这个顶点设置为已访问,访问状态在加入队列时设置
					vertex.visit();
					System.out.println("从顶点:"+vertex.getLabel()+" ,找到未访问过的顶点:"+other.getLabel());
				}
			}
			
		}				
		return result;
	}

深度优先算法

深度优先与广度优先不同的是

广度优先遇到一个顶点,让它出队列,然后把它所有的未被访问的节点入队列

而深度优先,遇到一个顶点,只是先获得它,并不出栈(因为要多次利用它),然后把它的第一个未被访问的节点入队列。

如果没有相邻的未被访问的顶点,才把这个顶点出栈

	
	/** 深度优先遍历图,以origin作为标识的顶点作为起始点
	 * @param origin
	 * @return  返回一个list,其中元素为广度遍历到的顶点,顺序为深度遍历的顺序
	 * 如果队列中没有以origin作为标识的顶点,返回一个空的list
	 */
	public List<Vertex<T>> depthFirstTraversal(T origin){		
		//清除状态
		clearVisitStatus();
		//这个是深度遍历的栈
		Stack<Vertex<T>> stack=new Stack<>();
		//这个是返回的list,用来存储深度遍历到的顶点
		List<Vertex<T>> result=new LinkedList<>();
		Vertex<T> vertex=vertexMap.get(origin);
		if(vertex==null){
			//如果队列中没有以origin作为标识的顶点,返回一个空的list
			System.out.println("没有找到顶点"+origin);
			return result;
		}
		//以origin作为标识的顶点作为起始点
		stack.push(vertex);
		//result里用来存储深度遍历到的顶点,由于深度遍历,一个节点可能被访问多次,所以在入栈时操作
		result.add(vertex);
		//将这个顶点设置为已访问,访问状态在加入队列时设置
		vertex.visit();
		while(!stack.isEmpty()){
			vertex=stack.peek();			
			System.out.println("遍历到顶点:"+vertex.getLabel());
			//获得以这个顶点为出发点,相邻的第一个没有被访问的顶点
			Vertex other=vertex.getUnvisitedVertex();
			if(other==null){
				//如果没找到,那么vertex就退出栈,让下一个被访问
				stack.pop();
				System.out.println("顶点:"+vertex.getLabel()+" ,没有找到未访问过的顶点,出栈");
			}
			else{
				//如果找到了,就让other顶点入栈,vertex还留在这里,下一个被访问的是other
				other.visit();
				stack.push(other);
				//result里用来存储深度遍历到的顶点,由于深度遍历,一个节点可能被访问多次,所以在入栈时操作
				result.add(other);
				System.out.println("从顶点:"+vertex.getLabel()+" ,找到未访问过的顶点:"+other.getLabel());
			}
			
		}	
		System.out.println();
		return result;
	}

测试

package datastructure.graph.adjacencymatrixgraph;

public class Main {

	public static void main(String[] args) {
		Graph<String> graph=new Graph<>(false);
		graph.addVertex("first", 0);
		graph.addVertex("second", 0);
		graph.addVertex("third", 0);
		graph.addVertex("fourth", 0);
		graph.addEdge("first", "second", 1);
		graph.addEdge("first", "third", 2);
		graph.addEdge("fourth", "third", 2);
		
		graph.printGraph();
		
		graph.breathFirstTraversal("first");
		
		graph.depthFirstTraversal("first");
	}

}