数据结构基础(五)图以及DFS、BFS

概念

定义

图是一种较线性表和树更为复杂的数据结构
相较于线性表的一对一(每个结点只有一个前驱后驱)和树的一对多(层级结构,上层结点可以与多个下层结点相关),图形结构的元素关系是多对多(即结点之间的关系可以是任意的)

图可分为有向图和无向图

术语

连通图:对于无向图,如果任意两个结点之间都是通的,则称之为连通图
连通分量:对于无向非连通图,极大连通子图称为其连通分量
强连通图:对于有向图,任意两个结点有路径,则称之为强连通图
强连通分量:对于有向图非连通图,极大连通子图称为其强连通分量
连通图的生成树:该图的极小连通子图,它含有图中全部n个顶点和只有足以构成一棵树的n-1条边,称为图的生成树


例如上图a中无向图G3,图b便为其三个连通分量


上图为连通分量图的生成树

图的存储结构

图的常用的存储结构有:邻接矩阵邻接链表十字链表邻接多重表边表,其中邻接矩阵和邻接链表是较常用的表示方法。

邻接矩阵

即用一维数组放置其顶点,二维数组放置顶点之间的关系
例如对于无向图,A[i][j]=0表示i结点和j结点之间不连通,A[i][j]=1表示连通;对于有向图,A[i][j]表示i结点和j结点之间的权值。
该二维数组又称为邻接矩阵

邻接链表

即用一个数组存储所有顶点,而每个顶点有一个域指向一个单链表,该单链表存储该顶点所有邻接顶点及其相关信息。

如上图,头结点(顶点)中data存储该顶点相关信息,firstarc存储单链表第一个结点的位置;
表结点(链表结点)中adjvex存储该领接顶点位置,nextarc存储链表下一个结点位置,info存储边相关信息(例如有向图中的权值)
下图为邻接链表的图解
20130708131719421.png

实现代码

给出手动实现的邻接矩阵代码:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 弧
 */
class Arc{
    //弧连接的两个顶点
    Object v1,v2;
    //图:-1 无连接  >=0有连接
    int Type;
    //弧的信息
    String info;

    public Arc() {}

    public Arc(Object v1, Object v2, int type, String info) {
        super();
        this.v1 = v1;
        this.v2 = v2;
        Type = type;
        this.info = info;
    }

    @Override
    public String toString() {
        return v1+" "+v2+" "+Type+" "+info;
    }

}

/**
 * 领接矩阵图
 */
public class MatrixGraph {
    /**默认顶点最大个数*/
    final static int defaultSize=10;
    /**顶点集合*/
    List<Object> vertexs;
    /**边的关系矩阵*/
    Arc[][] arcs;
    /**最大顶点个数*/
    int maxLen;
    /**弧的个数*/
    int arcNum;


    /**
     * 默认构造函数
     */
    public MatrixGraph() {
        this.maxLen = defaultSize;
        this.vertexs = new ArrayList<Object>(maxLen);
        this.arcs = new Arc[maxLen][maxLen];
        this.arcNum = 0;
    }

    /**
     * 带参构造函数
     * @param vers 顶点数组
     */
    public MatrixGraph(Object[] vers) {
        this.maxLen=vers.length;
        this.vertexs=new ArrayList<Object>(Arrays.asList(vers));
        this.arcs = new Arc[maxLen][maxLen];
        this.arcNum = 0;
    }

    /**
     * 添加顶点
     * @param v
     */
    public void addVertex(Object v) {
        vertexs.add(v);
        if(vertexs.size()>maxLen)
            extendSize();
    }

    /**
     * 扩展最大顶点个数和领接矩阵
     */
    public void extendSize() {
        maxLen*=2;
        arcs=Arrays.copyOf(arcs, maxLen);
    }

    /**
     * 添加一条弧
     * @param a
     * @param b
     */
    public void addArc(Object a,Object b) {
        addArc(a, b, 0);
    }

    /**
     * 添加一条带权弧
     * @param a
     * @param b
     * @param weight
     */
    public void addArc(Object a,Object b,int weight) {
        int i=vertexs.indexOf(a);
        int j=vertexs.indexOf(b);

        if(i==-1||j==-1){
            throw new ArrayIndexOutOfBoundsException("该顶点不存在"); 
        }

        arcs[i][j]=new Arc(a, b, weight, "");
        arcNum++;

    }

    /**
     * 删除a到b的弧
     * @param a
     * @param b
     */
    public void removeArc(Object a,Object b) {
        int i=vertexs.indexOf(a);
        int j=vertexs.indexOf(b);
        if(i==-1||j==-1){
            throw new ArrayIndexOutOfBoundsException("该顶点不存在"); 
        }

        if(arcs[i][j]!=null){
            arcs[i][j]=null;
            arcNum--;           
        }
        else {
            System.out.println("该弧已经不存在");
        }
    }

    /**
     * 删除一个顶点
     * @param a
     */
    public void removeVertexs(Object a) {
        int i=vertexs.indexOf(a);

        if(i==-1)
            throw new ArrayIndexOutOfBoundsException("该顶点不存在");
        vertexs.remove(i);
        //重新生成邻接矩阵
        int length=arcs.length;
        for (int j = 0; j < length-1; j++) {
            for (int z = 0; z < length-1; z++) {
                if(z>=i)
                    arcs[j][z]=arcs[j][z+1];
            }
            arcs[j][length-1]=null;
            if(j>=i)
                arcs[j]=arcs[j+1];
        }
        arcs[length-1]=new Arc[length];
    }

    /**
     * 清空图
     */
    public void clear() {
        vertexs=null;
        arcs=null;
        arcNum=0;
        maxLen=defaultSize;
    }

    /**
     * 打印图的信息
     */
    public void printGraph() {
        for (int i = 0; i < arcs.length; i++) {
            for (int j = 0; j < arcs[i].length; j++) {
                if(arcs[i][j]!=null)
                System.out.println(arcs[i][j]);
            }
        }
    }

    public static void main(String[] args) {
        Object obj[] = { 'A', 'B', 'C', 'D', 'E', 'F' };  

        MatrixGraph graph = new MatrixGraph(obj);  

        graph.addArc('A','C',5);  
        graph.addArc('B','A',2);  
        graph.addArc('C','B',15);  
        graph.addArc('E','D',4);  
        graph.addArc('F','E',18);  
        graph.addArc('A', 'F', 60);  
        graph.addArc('C', 'F', 70); 

        graph.printGraph();
        System.out.println("--------------");
        graph.removeVertexs('A');
        graph.printGraph();
        System.out.println("--------------");
        graph.removeArc('C', 'B');
        graph.printGraph();
    }

}/**Output:
A C 5 
A F 60 
B A 2 
C B 15 
C F 70 
E D 4 
F E 18 
--------------
C B 15 
C F 70 
E D 4 
F E 18 
--------------
C F 70 
E D 4 
F E 18 */

图的遍历

图的遍历有两种:DFS(Deep First Search)和BFS(Breadth First Search)
这两个算法算是我在ACM呆的时候做题最多的算法了。

以下内容部分引自:深度优先遍历与广度优先遍历

DFS(深度优先算法)

即每次遍历时偏向纵深方向搜索,类似树的先序遍历,可用递归或者栈实现。

递归实现:

1. 访问顶点vvisited[v]=1;//算法执行前visited[n]=0
2. w=顶点v的第一个邻接点;
3. whilew存在)  
    ifw未被访问)
                从顶点w出发递归执行该算法;  
                w=顶点v的下一个邻接点;

栈实现:

 1.S初始化;visited[n]=0;
 2. 访问顶点vvisited[v]=1;顶点v入栈S
 3. while(栈S非空)
        x=栈S的顶元素(不出栈);
            if(存在并找到未被访问的x的邻接点w)
                    访问wvisited[w]=1;
                    w进栈;
            else
                     x出栈;

BFS(广度优先算法)

即每次遍历时分层搜索,先搜索完每个顶点的领接结点,再对领接结点重复这一步。可用队列实现

队列实现

1. 初始化队列Qvisited[n]=0;
2. 访问顶点vvisited[v]=1;顶点v入队列Q;
3.  while(队列Q非空)   
             v=队列Q的对头元素出队;
              w=顶点v的第一个邻接点;
             whilew存在) 
                     如果w未访问,则访问顶点wvisited[w]=1;
                     顶点w入队列Qw=顶点v的下一个邻接点。



下面是两种遍历的具体代码

    /**
     * 深度优先遍历
     * @param o
     * @return
     */
    public String dfs(Object o) {
        StringBuilder result=new StringBuilder();
        Stack<Object> stack=new Stack<Object>();
        //访问标记数组
        Boolean[] visit=new Boolean[vertexs.size()];
        //初始化所有顶点为未访问
        for (int i = 0; i < visit.length; i++) {
            visit[i]=false;
        }
        //放入起始结点入栈
        stack.push(o);
        visit[vertexs.indexOf(o)]=true;
        result.append(o);
        //利用栈进行深度优先遍历
        while (!stack.isEmpty()) {
            //访问栈的顶点
            Object out=stack.peek();
            Object next=getNext(out, visit);
            if(next!=null){
                stack.push(next);
                visit[vertexs.indexOf(next)]=true;
                result.append("->"+next);
            }
            else{
                stack.pop();
            }
        }

        return result.toString();
    }

    /**
     * 广度优先遍历
     * @param o
     * @return
     */
    public String bfs(Object o) {
        StringBuilder result = new StringBuilder();
        Queue queue = new LinkedList<Object>();
        // 访问标记数组
        Boolean[] visit = new Boolean[vertexs.size()];
        // 初始化所有顶点为未访问
        for (int i = 0; i < visit.length; i++) {
            visit[i] = false;
        }
        //放入起始结点入队列
        queue.add(o);
        visit[vertexs.indexOf(o)]=true;
        result.append(o);
        //利用队列进行广度优先遍历
        while (!queue.isEmpty()) {
            //访问堆顶点
            Object out=queue.peek();
            Object next=getNext(out, visit);
            if(next!=null){
                queue.add(next);
                visit[vertexs.indexOf(next)]=true;
                result.append("->"+next);
            }else{
                queue.poll();
            }   
        }
        return result.toString();
    }

    /**
     * 获取o结点的下一个未被访问的领接结点
     * @param o
     * @param visit
     * @return
     */
    private Object getNext(Object o,Boolean[] visit){

        int index=vertexs.indexOf(o);
        for (int j = 0; j < arcs.length; j++) {
            if(arcs[index][j]!=null&&visit[j]==false)
                return vertexs.get(j);
        }   
        return null;
    }

参考
图(2)—— 邻接矩阵表示法
图(3)——邻接链表法
深度优先遍历与广度优先遍历

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值