图的深度优先遍历算法

                       

前言

图的遍历与前面文章中的二叉树遍历还是存在很大区别的。所谓图的遍历指的是从图中的某一个顶点出发访问图中的其余顶点,并且需要保证每个顶点只被访问一次。由于图比二叉树复杂得多,所以前面二叉树的遍历算法在图中是行不通的。因为对于任意一个顶点来讲,都可能与其余的顶点发生连接。如果不对访问的顶点做一些处理,出发重复访问的几率是很高的。因此,一个基本思想是设置一个标记数组,主要用于标记已经被访问过的顶点。图的遍历算法主要有两种:深度优先遍历和广度优先遍历。本篇文章主要介绍的是深度优先遍历算法。

深度优先遍历的具体过程

深度优先遍历,简称DFS。具体思想是不放过任何一个死角。在图的遍历中就是从图的某个顶点v出发,访问此顶点,然后从v的未被访问过的邻接点出发深度优先遍历图,直至图中的所有和v有路径相通的顶点都被访问到(对于连通图来讲)

为了更好说明深度优先遍历的过程,以下面的图为例:

图

上图的邻接表定义如下:

邻接表

注意:顶点0的第一个元素是2而不是5,其顶点类似。

  1. 起点是顶点0,后面的遍历过程从顶点0开始,把顶点0标记为已访问
  2. 因为顶点2是顶点0的邻接表的第一个元素,所以下一次递归从顶点2开始,同时把顶点2标记为已访问
  3. 顶点2的递归遍历开始,由于顶点2的邻接表的第一个元素是0,但是0已经被访问过了,所以访问顶点1,1没有被访问,于是将1标记为已访问,递归继续从顶点1开始
  4. 查找上表中顶点1的第一个元素,是顶点0,由于已经被访问过,所以访问顶点2,2也被访问过了,于是从顶点1的递归遍历结束,返回到顶点2继续递归。
  5. 查找顶点2的下一个元素,顶点3,没有被访问,于是将顶点3标记为已访问,递归于是从顶点3开始
  6. 查找顶点3邻接表的第一个元素,是顶点5,没有被访问,于是将顶点5标记为已访问,递归从顶点5开始
  7. 顶点5从其邻接表查找第一个元素,是顶点3,已被访问过,继续查找顶点0,也被访问,于是递归从5结束,返回到顶点3继续递归
  8. 查找顶点3邻接表的下一个元素,是顶点4,没有被访问过,于是将顶点4标记为已访问,递归从顶点4继续开始
  9. 顶点4查找其邻接表的第一个元素,发现顶点3已被访问过,继续查找其下一个元素,发现顶点2也被访问过,于是递归从顶点4结束,返回到顶点3继续递归
  10. 顶点查找下一个元素是顶点2了,也是顶点3邻接表的最后一个元素,发现顶点2已经被访问过了,所以递归从顶点3结束,返回到顶点2继续递归
  11. 顶点查找其邻接表的下一个元素,是顶点4,也是其邻接表最后一个元素,发现顶点已被访问过,所以递归从顶点2结束,返回到顶点0继续递归
  12. 顶点0继续查找其邻接表的下一个元素,发现顶点1余顶点5都被访问过了,所以递归结束。总的遍历结束。

从以上过程来看,上图的顶点访问次序依次是:0,2,1,3,5,4。

深度优先遍历算法的实现

首先需要定义一个存储图的数据结构,在Java中可以使用邻接表来实现图存储。具体而言就是,图的顶点用一维数组存储,每个顶点的邻接顶点用一个单链表进行存储。

图的数据结构:邻接表的实现

package com.rhwayfun.algorithm.graph;public class MyGraph {    //顶点数目    private int V;    //边的数目    private int E;    //邻接表    private Bag<Integer>[] adj;    public MyGraph(int V){        this.V = V;        this.E = 0;        //创建邻接表        adj = (Bag<Integer>[])new Bag[V];        //将所有链表初始化        for(int v = 0; v < V; v++){            adj[v] = new Bag<Integer>();        }    }    public int V(){        return V;    }    public int E(){        return E;    }    public void addEdge(int v,int w){        //将w添加到v的链表中        adj[v].add(w);        //将v添加到w的链表中        adj[w].add(v);        E++;    }    //获取顶点v的邻接表顶点列表    public Iterable<Integer> adj(int v){        return adj[v];    }}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

深度优先遍历算法的实现代码:

package com.rhwayfun.algorithm.graph;/** * 深度优先搜索 * <p>Title:DepthFirstSearch</p> * <p>Description:</p> * @author rhwayfun * @date Dec 22, 2015 8:04:23 PM * @version 1.0 */public class DepthFirstSearch {    //创建一个标记数组    private boolean[] marked;    //访问计数器    private int count;    /**     * 构造一幅图并进行深度优先遍历     * <p>Description: </p>     * @param G 读入的图     * @param s 开始遍历的顶点     */    public DepthFirstSearch(MyGraph G,int s) {        marked = new boolean[G.V()];        dfs(G,s);    }    private void dfs(MyGraph G, int s) {        marked[s] = true;        count++;        System.out.print(s + " ");        for(int w : G.adj(s)){            //如果没有被访问过就继续遍历            if(!marked[w]) dfs(G, w);        }    }    public boolean[] getMarked() {        return marked;    }    public int getCount() {        return count;    }}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

深度优先遍历算法的非递归实现方式

使用非递归的方式与递归的思想是一致的,不同的在于需要使用一个栈保存遍历的顶点。下面是具体的实现代码:

package com.rhwayfun.algorithm.graph;import java.util.Iterator;import java.util.Stack;/** * 使用非递归方式对图进行深度优先遍历 * <p>Title:NonrecursiveDFS</p> * <p>Description:</p> * @author rhwayfun * @date Dec 22, 2015 10:43:35 PM * @version 1.0 */public class NonrecursiveDFS {    //创建一个标记数组标记访问过的元素    private boolean[] marked;    @SuppressWarnings("unchecked")    public NonrecursiveDFS(MyGraph G, int s){        marked = new boolean[G.V()];        //创建一个迭代器迭代每个顶点的邻接表        Iterator<Integer>[] adj = (Iterator<Integer>[])new Iterator[G.V()];        //获得每个顶点的邻接表        for(int v = 0; v < G.V(); v++){            adj[v] = G.adj(v).iterator();        }        //创建一个栈用户存放遍历的顶点        Stack<Integer> stack = new Stack<Integer>();        marked[s] = true;        System.out.print(s + " ");        stack.add(s);        while(!stack.isEmpty()){            int v = stack.peek();            //如果有邻接表的话,就继续遍历            if(adj[v].hasNext()){                int w = adj[v].next();                //判断是否已被访问过                if(!marked[w]){                    //没访问过就将器标记为已访问过,下次不会再访问了                    marked[w] = true;                    System.out.print(w + " ");                    stack.push(w);                }            }else{                //如果没有邻接表的话就将该顶点弹出栈顶                stack.pop();            }        }    }    public boolean marked(int v) {        return marked[v];    }}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

2. 系统设计 1.用到的抽象数据类型的定义 的抽象数据类型定义: ADT Graph{ 数据对象V:V是具有相同特性的数据元素的集合,称为顶点集 数据关系R: R={VR} VR={<v,w>|v,w∈V且P(v,w),<v,w>表示从v到w的弧, 谓词P(v,w)定义了弧<v,w>的意义或信息} 基本操作P: CreatGraph(&G,V,VR) 初始条件:V是的顶点集,VR是中弧的集合 操作结果:按V和VR的定义构造G DestroyGraph(&G) 初始条件:G存在 操作结果:销毁G InsertVex(&G,v) 初始条件:G存在,v和中顶点有相同特征 操作结果:在G中增添新顶点v …… InsertArc(&G,v,w) 初始条件:G存在,v和w是G中两个顶点 操作结果:在G中增添弧<v,w>,若G是无向的则还增添对称弧<w,v> …… DFSTraverse(G,Visit()) 初始条件:G存在,Visit是顶点的应用函数 操作结果:对进行深度优先遍历,在遍历过程中对每个顶点调用函数Visit一次且仅一次。一旦Visit()失败,则操作失败 BFSTraverse(G,Visit()) 初始条件:G存在,Visit是顶点的应用函数 操作结果:对进行广度优先遍历,在遍历过程中对每个顶点调用函数Visit一次且仅一次。一旦Visit()失败,则操作失败 }ADT Graph 栈的抽象数据类型定义: ADT Stack{ 数据对象:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0} 数据关系:R1={<ai-1,ai>|ai-1,ai∈D,i=2,…,n} 约定an端为栈顶,ai端为栈底 基本操作: InitStack(&S) 操作结果:构造一个空栈S DestroyStack(&S) 初始条件:栈S已存在 操作结果:将S清为空栈 StackEmpty(S) 初始条件:栈S已存在 操作结果:若栈S为空栈,则返回TRUE,否则FALSE …… Push(&S,e) 初始条件:栈S已存在 操作结果:插入元素e为新的栈顶元素 Pop(&S,&e) 初始条件:栈S已存在且非空 操作结果:删除S的栈顶元素,并用e返回其值 StackTraverse(S,visit()) 初始条件:栈S已存在且非空 操作结果:从栈底到栈顶依次对S的每个数据元素调用函数visit(),一旦visit()失败,则操作失效 }ADT Stack 队列的抽象数据类型定义: ADT Queue{ 数据对象:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0} 数据关系:Rl={<ai-1,ai>|ai-1,ai∈D,i=2,…,n} 约定其中ai端为队列头,an端为队列尾。 基本操作: InitQueue(&Q) 操作结果:构造一个空队列Q DestroyQueue(&Q) 初始条件:队列Q已存在 操作结果:队列Q被销毁,不再存在 QueueEmpty(Q) 初始条件:队列Q已存在 操作结果:若Q为空队列,则返回TRUE,否则FALSE …… EnQueue(&Q,e) 初始条件:队列Q已存在 操作结果:插入元素e为Q的新的队尾元素 DeQueue(&Q,&e) 初始条件:Q为非空队列 操作结果:删除Q的队头元素,并用e返回其值 }ADT Queue 2.主程序的流程: 调用CreateDN函数创建的邻接表G; 调用PrintDN函数输出邻接表G; 调用DFSTraverse函数深度优先遍历; 调用BFSTraverse函数广度优先遍历
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值