数据结构与算法(java):图的基本概念、无向图和有向图的实现、图的搜索/遍历

图的基本概念

为什么要用到图:
数处理一对多的关系,用图可以处理多对多的关系。

1、图的定义

图由一组顶点和一组能够将两个顶点相连的边组成。图是一种数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接成为边,结点在图中也称为顶点。

例如:下面三幅都是图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、特殊的图

(1)自环:即一条连接一个顶点和其自身的边;
(2)平行边:连接同意对顶点的两条边
在这里插入图片描述

3、图的分类

按照连接两个顶点的边的不同,可以把图分为以下两种
无向图:边仅仅连接两个顶点,无任何指向
有向图:边连接两个顶点,并且具有方向

4、图的相关术语

相邻顶点:当两个顶点通过一条边相连时,称之为相邻,并且称这条边依附于这两个顶点
子图:一副图的所有边的子集(包含边所依附的顶点)组成的图
:某个顶点的度就是依赖于该顶点的边的个数
路径:是由边顺序连接的一系列的顶点组成
:是一条至少含有一条边且终点和起点相同的路径
连通图:如果途中任何一个顶点都存在一条路径到达另外一个顶点,那么==这幅图就称之为连通图。
连通子图:一个非连通图由若干连通的部分组成,每一个连通的部分都可以成为该图的连通子图。
权(Weight):有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权(Weight)。

在这里插入图片描述
在这里插入图片描述

5、图的存储结构

要表示一幅图,只需要表示清楚以下两部分即可:
1、图中所有的结点
2、所有连接顶点的边

常见的图的存储结构有如下两种:邻接矩阵和邻接表

5.1 邻接矩阵

邻接矩阵是表示图形中顶点间相邻关系的矩阵,对于n个顶点的图而言,可以使用二维数组的横坐标和纵坐标表示图中的某个顶点,同时根据带不带权,有向图还是无向图,又可以分为很多类别:邻接矩阵实现不带权无向图,带权无向图,不带权有向图,带权有向图。

不带权邻接矩阵中二维数组存储的值分别为1和0。1代表两个顶点之间存在边,0代表两个顶点间不存在边。
带权邻接矩阵中二位数组存储的值表示权值,大于0表示两个顶点之间存在边,并且这个边的大小等于权值的大小,等于0时表示没边。

这是不带权无向图的邻接矩阵,例如:A与B之间存在边,所以arr[ A ][ B ] = 1;而又因为是无向图,所以arr[ A ] [ B ] = 1(通常邻接表竖直方向上的数值表示二维数组中的横坐标,水平方向上表示纵坐标)
在这里插入图片描述
这个是带权无向图的邻接矩阵,顶点A和B之间存在边,并且边的大小为5,所以arr[ A ][ B ] = 5,因为是无向图,所以互通的两顶点之间的边是一样的。
在这里插入图片描述

分析:无向图含边顶点间的指向是双向的,也就是说共享一条边。所以在矩阵中,是按照右对角线对称的。
在这里插入图片描述

下面是不带权有向图的邻接矩阵,例如:A和B之间是有边的,但指向却只有A顶点指向B顶点,所以在矩阵中,只有arr[ A ][ B ] = 1;而arr[B] [A ] 不等于1;
在这里插入图片描述
下面是带权有向图的邻接矩阵,A和B之间是有边的,但指向却只有A顶点指向B顶点,所以在矩阵中,只有arr[ A ][ B ] = 5;而arr[B] [A ] 不等于5;
在这里插入图片描述

使用邻接矩阵实现图的缺点:
使用邻接矩阵这种弄存储方式的空间复杂度是N^2,所以当处理的问题规模比较大的话,内存空间极有可能不够用,可以发现,当很多边不存在的时候,内存空间同样需要存储数据,这样会造成空间的一定损失。

5.2 邻接表

顺序存储结构就存在预先分配内存可能造成存储空间浪费的问题,于是引出了链式存储的结构。同样的,可以考虑对边或弧使用链式存储的方式来避免空间浪费的问题。 邻接表的实现只关心存在的边,不关心不存在的边,因此没有空间的浪费,邻接表由数组+链表组成。
邻接表由表头节点和表节点两部分组成,图中每个顶点均对应一个存储在数组中的表头节点。如果这个表头节点所对应的顶点存在邻接节点,则把邻接节点依次存放于表头节点所指向的单向链表中。

构建图的邻接表实现的关键点在于“:你所建立的图的邻接表的对象是什么!
邻接链表的创建首先我们需要确定邻接表的对象,可以用顶点作为邻接链表的对象,也可以用边作为邻接链表的对象。

无向图
在这里插入图片描述

表头结点用数组来存储,并且表头结点中的数据域表示顶点,表头结点中的指针域指向表结点中的第一个结点。表结点中的数据域存储的是与头结点有边关系的结点的下表,表结点中的指针与指向下一个表结点。
补充:对于无向图来说,使用邻接表进行存储也会出现数据冗余的现象。例如上图中的A头结点指向了B结点,但B结点也能够指向A结点。

有向图
在这里插入图片描述

表头结点用数组来存储,并且表头结点中的数据域表示顶点,表头结点中的指针域指向表结点中的第一个结点。表结点中的数据域存储的是与头结点有边关系的结点的下表,表结点中的指针与指向下一个表结点。
有向图中就没有出现数据冗余的情况。

带权图
在这里插入图片描述

对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可

图的实现

邻接矩阵方式实现

在这里插入图片描述
无向图的邻接矩阵实现

/**
 * 邻接矩阵实现无向图(带权或者不带权)
 */
public class MatrixGraph1<T> {
    //集合存储所有顶点
    private ArrayList<T> vertexList;
    //二维数组存储顶点对应的邻接矩阵
    private int[][] matrix;
    //边的数量
    private int numOfEdges;

    /**
     * 初始化图
     * @param numOfVertex 构成图的顶点数量
     */
    public MatrixGraph1(int numOfVertex){
        //初始化集合,也就是里面的顶点
        this.vertexList = new ArrayList<T>();
        //初始化数组,也就是初始化邻接矩阵
        this.matrix = new int[numOfVertex][numOfVertex];
        //初始化边的数量,默认为0
        this.numOfEdges = 0;
    }

    /**
     * 添加顶点;将顶点存储集合中
     * @param vertex 要添加的顶点(构成图的顶点)
     */
    public void insertVertex(T vertex){
        vertexList.add(vertex);
    }

    /**
     * 往两个顶点间添加边
     * @param vertex1 矩阵的横坐标
     * @param vertex2 矩阵的纵坐标
     * @param weight 两个顶点间的边的大小(权值)
     *
     */
    public void insertEdges(T vertex1, T vertex2, int weight){
        int index1 = getIndex(vertex1);
        int index2 = getIndex(vertex2);
        matrix[index1][index2] = weight;
        matrix[index2][index1] = weight;
        numOfEdges++;
    }

    /**
     * 返回顶点个数
     * @return 返回结点个数
     */
    public int getNumOfVertex(){
        return vertexList.size();
    }

    /**
     * 获取边的数目
     * @return 返回边的数目
     */
    public int getNumOfEdges(){
        return numOfEdges;
    }

    /**
     * 获取指定下标对应的顶点
     * @param i 指定顶点下标
     * @return 返回指定下标对应的顶点
     */
    public T getVertex(int i){
        return vertexList.get(i);
    }

    /**
     * 根据指定顶点获取对应的顶点下标
     * @param vertex 指定的顶点
     * @return 返回对应的顶点下标
     */
    public int getIndex(T vertex){
        return vertexList.indexOf(vertex);
    }

    /**
     * 获取两个顶点间的权值
     * @param vertex1 第一个顶点
     * @param vertex2 第二个顶点
     * @return 返回权值
     */
    public int getWeight(T vertex1, T vertex2){
        int index1 = getIndex(vertex1);
        int index2 = getIndex(vertex2);
        return matrix[index1][index2];
    }

    /**
     * 显示矩阵
     */
    public void showMatrix(){
        for (int[] edges : matrix){
            System.out.println(Arrays.toString(edges));
        }
    }

    /**
     * 深度优先搜索遍历图的递归实现
     * @param i 表示第i个顶点
     * @param visited 判断是否访问过,true表示访问过,false表示未访问过
     */
    private void DFS(int i, boolean[] visited){
        //给被访问的节点做标记
        visited[i] = true;
        //打印当前被访问的节点
        System.out.print(vertexList.get(i));
        //遍历该节点的所有邻接顶点,若是没有被访问过,则继续往下找
        for(int j = getFirstRelativeVertex(i); j>=0; j = getNextRelativeVertex(i,j)) {
            if(!visited[j]){
                DFS(j, visited);
            }
        }
    }

    //邻接矩阵深度优先搜索遍历图
    public void DFS(){
        //将访问过的顶点标记
        boolean[] visited = new boolean[vertexList.size()];
        //初始化未被访问的节点,默认为false
        for (int i = 0; i < vertexList.size(); i++) {
            visited[i] = false;
        }
        System.out.println("深度优先遍历后的结果:");
        for (int i = 0; i < vertexList.size(); i++) {
            if (!visited[i]) {
                DFS(i, visited);
            }
        }
    }

    //邻接矩阵广度度优先搜索遍历图
    public void BFS(){
        int head = 0;
        int rear = 0;
        int[] queue = new int[vertexList.size()]; //可以看成是一个队列
        boolean[] visited = new boolean[vertexList.size()];

        //初始化标记,所有顶点均为false,表示未访问
        for (int i = 0; i < vertexList.size(); i++) {
            visited[i] = false;
        }

        System.out.println("广度优先遍历后的结果:");
        for (int i = 0; i < vertexList.size(); i++) {
            if(!visited[i]){
                visited[i] = true;
                System.out.print(vertexList.get(i));
                queue[rear++] = i; //入队操作
            }

            while(head != rear){
                int j = queue[head++]; //出队操作
                for (int k = getFirstRelativeVertex(j); k>=0; k=getNextRelativeVertex(j,k)) {
                    if(!visited[k]){
                        visited[k] = true;
                        System.out.print(vertexList.get(k));
                        queue[rear++] = k;
                    }
                }
            }
        }

    }

    /**
     * 获取v第一个相邻结点的索引
     * @param v 表示第v个顶点
     * @return 返回第顶点v一个相邻结点的索引
     */
    private int getFirstRelativeVertex(int v){
        if(v<0 || v>(vertexList.size()-1)){
            return -1;
        }
        for (int i = 0; i < vertexList.size(); i++) {
            if(hasEdge(v,i) != false){
                return i;
            }
        }
        return -1;
    }

    /**
     * @param v 第v个开始顶点
     * @param w v顶点的第一个邻接顶点
     * @return v有邻接顶点则返回邻接顶点是第几个,没有则返回-1
     */
    private int getNextRelativeVertex(int v, int w){
        if(v<0 || v>(vertexList.size()-1) || w<0 || w>(vertexList.size()-1)){
            return -1;
        }
        for (int i = w + 1; i < vertexList.size(); i++) {
            if(hasEdge(v,i) != false){
                return i;
            }
        }
        return -1;
    }
    
    /**
     * 判断两个顶点间是否有边
     * @param v1
     * @param v2
     * @return true表示有边,false表示无边
     */
    private boolean hasEdge(int v1, int v2){
        if(matrix[v1][v2]>0){
            return true;
        }
        return false;
    }
    
}

测试代码:

public class MatrixGraph1Test {
    public static void main(String[] args) {
        MatrixGraph1<String> matrixGraph = new MatrixGraph1<>(5);
        //初始化图
        matrixGraph.insertVertex("A");
        matrixGraph.insertVertex("B");
        matrixGraph.insertVertex("C");
        matrixGraph.insertVertex("E");
        matrixGraph.insertVertex("D");
        matrixGraph.insertEdges("A","B",1);
        matrixGraph.insertEdges("A","C",1);
        matrixGraph.insertEdges("B","C",1);
        matrixGraph.insertEdges("B","E",1);
        matrixGraph.insertEdges("B","D",1);
        matrixGraph.insertEdges("C","A",1);
        matrixGraph.insertEdges("E","A",1);
        System.out.println("邻接矩阵:");
        matrixGraph.showMatrix();
        System.out.println("边的数目" + matrixGraph.getNumOfEdges());
        System.out.println("顶点个数" + matrixGraph.getNumOfVertex());
        System.out.println("获取顶点B所对应的索引" + matrixGraph.getIndex("B"));
        System.out.println("获取索引4对应的顶点" + matrixGraph.getVertex(4));
        matrixGraph.DFS();
        matrixGraph.BFS();
    }
}

结果如下:
邻接矩阵:
[0, 1, 1, 1, 0]
[1, 0, 1, 1, 1]
[1, 1, 0, 0, 0]
[1, 1, 0, 0, 0]
[0, 1, 0, 0, 0]
边的数目7
顶点个数5
获取顶点B所对应的索引1
获取索引4对应的顶点D
深度优先遍历后的结果:
ABCED
广度优先遍历后的结果:
ABCED

补充:无论是带权还是不带权只需要更改insertEdges()方法中的weight参数即可,不带权,1表示有边,0表示无边;带权,0表示无边,其他数值表示边的大小。

有向图的邻接矩阵实现
在这里插入图片描述
有向图时,只要将上面无向图的实现方法中的insertEdges()方法改为如下,表示两顶点之间的指向是单向的:

 /**
     * 往两个顶点间添加边
     * @param vertex1 矩阵的横坐标
     * @param vertex2 矩阵的纵坐标
     * @param weight 两个顶点间的边的大小(权值)
     *
     */
    public void insertEdges(T vertex1, T vertex2, int weight){
        int index1 = getIndex(vertex1);
        int index2 = getIndex(vertex2);
        matrix[index1][index2] = weight;
//        matrix[index2][index1] = weight;
        numOfEdges++;
    }

测试代码:

public class MatrixGraph1Test {
    public static void main(String[] args) {
        MatrixGraph1<String> matrixGraph = new MatrixGraph1<>(5);
        //初始化图
        matrixGraph.insertVertex("A");
        matrixGraph.insertVertex("B");
        matrixGraph.insertVertex("C");
        matrixGraph.insertVertex("E");
        matrixGraph.insertVertex("D");
        matrixGraph.insertEdges("A","B",1);
        matrixGraph.insertEdges("A","C",1);
        matrixGraph.insertEdges("B","C",1);
        matrixGraph.insertEdges("B","E",1);
        matrixGraph.insertEdges("B","D",1);
        matrixGraph.insertEdges("C","A",1);
        matrixGraph.insertEdges("E","A",1);
        System.out.println("邻接矩阵:");
        matrixGraph.showMatrix();
        System.out.println("边的数目" + matrixGraph.getNumOfEdges());
        System.out.println("顶点个数" + matrixGraph.getNumOfVertex());
        System.out.println("获取顶点B所对应的索引" + matrixGraph.getIndex("B"));
        System.out.println("获取索引4对应的顶点" + matrixGraph.getVertex(4));
        matrixGraph.DFS();
        matrixGraph.BFS();
    }
}

结果如下:
邻接矩阵:
[0, 1, 1, 0, 0]
[0, 0, 1, 1, 1]
[1, 0, 0, 0, 0]
[0, 0, 0, 0, 0]
[1, 0, 0, 0, 0]
边的数目7
顶点个数5
获取顶点B所对应的索引1
获取索引4对应的顶点E
深度优先遍历后的结果:
ABCED
广度优先遍历后的结果:
ABCED

邻接表方式实现

第一种方式:将顶点作为图的对象

  • 顶点类
/**
 * 图的顶点类
 */
public class Vertex {
    String vertexData; //顶点数据域 
    Vertex nextNode; //顶点指针域

    public Vertex(){

    }
}
  • 图类
import java.util.Scanner;

public class Graph {
    private Vertex[] vertexArray; //结点数组,存储图的所有顶点
    private int numOfVertex; //顶点数量
    private int numOfEdges; //边的数量

    public Graph(){
        //初始化定点数和边数
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入你要添加的顶点数和边数");
        this.numOfVertex = sc.nextInt();
        this.numOfEdges = sc.nextInt();

        //初始化顶点
        System.out.println("请依次输入顶点");
        vertexArray= new Vertex[numOfVertex];
        for (int i = 0; i < numOfVertex; i++) {
            vertexArray[i] = new Vertex();
            vertexArray[i].vertexData = sc.next();
            vertexArray[i].nextNode = null;
        }
    }

    //创建图
    public void createGraph(){
        Scanner sc = new Scanner(System.in);
        System.out.println("请依次输入构成边的两个顶点:");
        for (int i = 0; i < numOfEdges; i++) {
            System.out.println("第"+(i+1)+"对");
            String v1 = sc.next();
            String v2 = sc.next();
            addEdge(v1,v2);
        }
        System.out.println("全部输入完成");
    }

    //给两顶点间添加边
    public void addEdge(String v1, String v2){
        int i1 = getIndexOfVertexByName(v1);
        int i2 = getIndexOfVertexByName(v2);

        Vertex vNode1 = new Vertex();
        vNode1.vertexData = v2;
        vNode1.nextNode = vertexArray[i1].nextNode;
        vertexArray[i1].nextNode = vNode1;

        Vertex vNode2 = new Vertex();
        vNode2.vertexData = v1;
        vNode2.nextNode = vertexArray[i2].nextNode;
        vertexArray[i2].nextNode = vNode2;

    }

    //展示邻接表
    public void showGraph(){
        System.out.println("该图的邻接表为:");
        for (int i = 0; i < numOfVertex; i++) {
            System.out.print(vertexArray[i].vertexData);
            Vertex nextNode = vertexArray[i].nextNode;
            while(nextNode != null){
                System.out.print("-->" + nextNode.vertexData);
                nextNode = nextNode.nextNode;
            }
            System.out.println();
        }
    }

    //通过输入的顶点名获取顶点的下标
    private int getIndexOfVertexByName(String vertexName){
        for (int i = 0; i < numOfVertex; i++) {
            if(vertexName.equals(vertexArray[i].vertexData)){
                return i;
            }
        }
        return -1;
    }
    
}

  • 测试类
public class GraphTest {
    public static void main(String[] args) {
        Graph graph = new Graph();
        graph.createGraph();
        graph.showGraph();
    }
}
  • 测试过程与结果
请输入你要添加的顶点数和边数
5 7
请依次输入顶点
v1 v2 v3 v4 v5
请依次输入构成边的两个顶点:
第1对
v1 v2
第2对
v1 v5
第3对
v2 v3
第4对
v2 v4
第5对
v2 v5
第6对
v3 v4
第7对
v4 v5
全部输入完成
该图的邻接表为:
v1-->v5-->v2
v2-->v5-->v4-->v3-->v1
v3-->v4-->v2
v4-->v5-->v3-->v2
v5-->v4-->v2-->v1

第二种方式:将边作为图的对象
同样的需要设置定点类,不过这里的顶点类主要用来辅助构建图。

import java.util.Scanner;

public class Graph<T>{

    /**
     * VertexNode类是表头结点;
     * VertexData: 表示的是表头结点中的数据域
     * firstEdge: 表示的是表头结点中的指针域,指向边表的第一个结点
     */
    private class VertexNode<T> {
        T VertexData;
        EdgeNode firstEdge;
    }

    /**
     * EdgeNode是边表结点;
     * EdgeData:邻接点域,存储某顶点的邻接点在顶点表中的下标
     * nextEdge:存储指向边表中下一个结点的指针
     */
    private class EdgeNode {
        int EdgeData; //边结点数据域,存储数组下标
        EdgeNode nextEdge; //边结点指针域,指向下一个边结点
    }

    //表头结点数组
    public VertexNode[] headVertex;
    public int numOfVertex;

    /**构造方法
     * 初始化无向图
     */
    public Graph(){
        Scanner sc = new Scanner(System.in);
        //输入顶点数和边数
        System.out.println("请输入你要输入的顶点数和边数:");
        int vertexNum = sc.nextInt();
        int edgeNum = sc.nextInt();

        this.numOfVertex = vertexNum;

        //初始化顶点
        System.out.println("请输入你设置的顶点:");
        headVertex = new VertexNode[vertexNum];
        for(int i = 0; i<vertexNum; i++){
            headVertex[i] = new VertexNode();
            headVertex[i].VertexData = sc.next();
            headVertex[i].firstEdge = null;
        }

        //初始化边
        System.out.println("请依次输入构成边的两个顶点:");
        for(int i = 0; i<edgeNum; i++){
            //输入构成边的的顶点
            System.out.println("第" + (i+1) + "对:");
            String startVertex = sc.next();
            String endVertex = sc.next();
            //获取输入的顶点的下标
            int i1 = getIndexOfVertex(startVertex);
            int i2 = getIndexOfVertex(endVertex);
            //初始化EdgeVertex
            EdgeNode edgeNode1 = new EdgeNode();
            //给边表结点数据域赋值
            edgeNode1.EdgeData = i2;
            if(headVertex[i1].firstEdge == null){
                headVertex[i1].firstEdge = edgeNode1;
            }else {
                EdgeNode eNode1 = headVertex[i1].firstEdge;
                while(eNode1.nextEdge != null){
                    eNode1 = eNode1.nextEdge;
                }
                eNode1.nextEdge = edgeNode1;
            }

            //如果是无向图加上下面这段代码:互相指向;如果是有向图,则不加上下面这段代码
            EdgeNode edgeNode2 = new EdgeNode();
            edgeNode2.EdgeData = i1;
            if(headVertex[i2].firstEdge == null){
                headVertex[i2].firstEdge = edgeNode2;
            }else{
                EdgeNode eNode2 = headVertex[i2].firstEdge;
                while(eNode2.nextEdge != null){
                    eNode2 = eNode2.nextEdge;
                }
                eNode2.nextEdge = edgeNode2;
            }
        }
        /**
         * 输出图的邻接表
         */
        System.out.println("该图的邻接表为:");
        for(int j = 0; j<numOfVertex; j++){
            System.out.print(headVertex[j].VertexData);
            EdgeNode nextNode = headVertex[j].firstEdge;
            while(nextNode != null){
                System.out.print("-->" + nextNode.EdgeData);
                nextNode = nextNode.nextEdge;
            }
            System.out.println();
        }
    }

    /**
     * 获取指定顶点的下标
     * @param str 传入指定的顶点
     * @return 返回指定下标
     */
    private int getIndexOfVertex(String str){
        for(int i = 0; i<headVertex.length; i++){
            if(headVertex[i].VertexData.equals(str)){
                return i;
            }
        }
        return -1;
    }
}

测试类

public class GraphTest {
    public static void main(String[] args) {
        Graph<String> graph = new Graph<String>();
    }
}

测试及结果如下:
在这里插入图片描述

个人觉得图的构建的所有过程写在构造函数内不好看,于是拆分到了各个方法中,如下:

  • 头节点类
 *
 * @param <T> 让顶点结点存储任何类型数据
 *  vertexData 表头结点中的数据域
 *  firstEdge 表头结点中的指针域,指向边表第一个结点
 */
public class VertexNode<T>{
    String vertexData;
    EdgeNode firstEdge;

    public VertexNode(){

    }
}
  • 边表结点类
/**
 * EdgeData 边结点数据域,存储数组下标
 * nextEdge 边结点指针域,指向下一个边结点
 */
public class EdgeNode {
    int EdgeData;
    EdgeNode nextEdge;

    public EdgeNode(){

    }
}
  • 图类
import java.util.Scanner;

public class Graph {

    private VertexNode[] headVertex; //结点类型数组,用于存储图的所有顶点
    private int numOfVertex; //顶点的个数

    //构造方法:构造图的顶点
    public Graph() {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入你要添加的顶点数:");
        this.numOfVertex = sc.nextInt();
        headVertex = new VertexNode[numOfVertex];
        System.out.println("请依次插入顶点:");
        for (int i = 0; i < numOfVertex; i++) {
            headVertex[i] = new VertexNode();
            headVertex[i].vertexData = sc.next();
            headVertex[i].firstEdge = null;
        }
    }

    //创建整个图:给顶点添加边
    public void createGraph() {
        Scanner sc = new Scanner(System.in);
        System.out.println("请问你要输入几条边?");
        int numOfEdges = sc.nextInt();
        for (int i = 0; i < numOfEdges; i++) {
            System.out.println("第" + (i+1) + "对:");
            String v1 = sc.next();
            String v2 = sc.next();
            addEdges(v1,v2);
        }
    }

    //添加边(不含权值)
    private void addEdges(String v1, String v2){
        int i1 = getIndexByString(v1);
        int i2 = getIndexByString(v2);
        EdgeNode edgeNode1 = new EdgeNode();
        edgeNode1.EdgeData = i2;
        if(headVertex[i1].firstEdge == null){
            headVertex[i1].firstEdge = edgeNode1;
        }else{
            EdgeNode eNode1 = headVertex[i1].firstEdge;
            while(eNode1.nextEdge != null){
                eNode1 = eNode1.nextEdge;
            }
            eNode1.nextEdge = edgeNode1;
        }
        EdgeNode edgeNode2 = new EdgeNode();
        edgeNode2.EdgeData = i1;
        if(headVertex[i2].firstEdge == null){
            headVertex[i2].firstEdge = edgeNode2;
        }else{
            EdgeNode eNode2 = headVertex[i2].firstEdge;
            while(eNode2.nextEdge != null){
                eNode2 = eNode2.nextEdge;
            }
            eNode2.nextEdge = edgeNode2;
        }
    }

    //输出邻接表:用下标表示边表结点
    public void showGraphWithIndex(){
        System.out.println("该图的邻接表为:");
        for(int j = 0; j<numOfVertex; j++){
            System.out.print(headVertex[j].vertexData);
            EdgeNode nextNode = headVertex[j].firstEdge;
            while(nextNode != null){
                System.out.print("-->" + nextNode.EdgeData);
                nextNode = nextNode.nextEdge;
            }
            System.out.println();
        }
    }

    //输出邻接表:用顶点值表示边表结点
    public void showGraphWithVertex(){
        System.out.println("该图的邻接表为:");
        for(int i = 0; i<numOfVertex; i++){
            System.out.print(headVertex[i].vertexData);
            EdgeNode nextNode = headVertex[i].firstEdge;
            while(nextNode!=null){
                System.out.print("-->" + getNameOfVertexByIndex(nextNode.EdgeData));
                nextNode = nextNode.nextEdge;
            }
            System.out.println();
        }
    }

    //深度优先搜索DFS遍历图
    public void DFS(){
        //标记
        boolean[] visited = new boolean[numOfVertex];

        //初始化所有顶点未被访问过,默认未false
        for (int i = 0; i < numOfVertex; i++) {
            visited[i] = false;
        }

        System.out.println("深度优先遍历图的结果为");
        for (int i = 0; i < numOfVertex; i++) {
            if(!visited[i]){
                DFS(i, visited);
            }
        }
        System.out.println();
    }
    
    //深度优先搜索遍历图的递归实现
    private void DFS(int i, boolean[] visited){
        EdgeNode node;
        visited[i] = true;
        System.out.print(headVertex[i].vertexData);
        node = headVertex[i].firstEdge;
        while(node != null){
            if(!visited[node.EdgeData]){
                DFS(node.EdgeData,visited);
            }
            node = node.nextEdge;
        }
    }
    //广度优先遍历BFS
    public void BFS(){
        int head = 0;
        int rear = 0;
        int[] queue = new int[numOfVertex];
        boolean[] visited = new boolean[numOfVertex];

        for (int i = 0; i < numOfVertex; i++) {
            visited[i] = false;
        }

        System.out.println("广度优先遍历图的结果为");
        for (int i = 0; i < numOfVertex; i++) {
            if(!visited[i]){
                visited[i] = true;
                System.out.print(headVertex[i].vertexData);
                queue[rear++] = i;
            }
            while(head != rear){
                int j = queue[head++];
                EdgeNode node = headVertex[j].firstEdge;
                while(node != null){
                    int k = node.EdgeData;
                    if(!visited[k]){
                        visited[k] = true;
                        System.out.print(headVertex[k].vertexData);
                        queue[rear++] = k;
                    }
                    node = node.nextEdge;
                }
            }
            System.out.println();
        }
    }
    
    //通过传进来的字符获取对应顶点的索引
    private int getIndexByString(String v){
        for (int i = 0; i < numOfVertex; i++) {
            if(v.equals(headVertex[i].vertexData)){
                return i;
            }
        }
        return -1;
    }

    //通过传进来的索引获取顶点名
    private String getNameOfVertexByIndex(int index){
        return headVertex[index].vertexData;
    }
}


  • 测试类
public class GraphTest {
    public static void main(String[] args) {
        Graph graph = new Graph();
        graph.createGraph();
        graph.showGraphWithIndex();
        graph.showGraphWithVertex();
        graph.DFS();
        graph.BFS();
    }
}
  • 测试及结果

请输入你要添加的顶点数:
3
请依次插入顶点:
A B C
请问你要输入几条边?
3
第1对:
A B
第2对:
A C
第3对:
B C
该图的邻接表为:
A-->1-->2
B-->0-->2
C-->0-->1
该图的邻接表为:
A-->B-->C
B-->A-->C
C-->A-->B
深度优先遍历图的结果为
ABC
广度优先遍历图的结果为
ABC

图的搜索/遍历

深度优先搜索方法(DFS)

深度优先搜索(DFS)

深度优先搜索主要思想

假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。 若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

无向图深度优先图解:
在这里插入图片描述

分析:访问顺序和过程如下:
A =>C =>B => D =>F =>G =>E
访问过程:
(1)访问A。
(2)访问C。访问的是A的邻接点,并且由添加顶点顺序决定是访问临界点中的哪个点,因为C排在D、F之前,所以先访问C。
(3)访问B。访问的是C的临界点,同上,因为B排在D之前,所以先访问B,而A已经被访问过了,所以不会再访问。
(4)访问D。访问B后,B无任何邻接点,所以递归返回至C点,开始访问未被访问过的D点。
(5)访问F。D无邻接点,递归返回至C,而C点的所有邻接点都被访问过了,所以再次返回值A,A的邻接点中有F点未被访问过,所以访问F点。
(6)接下来就是依次访问G,E点。

有向图深度优先图解:
在这里插入图片描述

分析:从顶点A开始。
A=>B =>C =>E =>D =>F =>G
访问过程如下:
(1)访问A。
(2)访问B。A的唯一一个邻接点
(3)访问C。访问B点之后,按顶点顺序,先访问C。
(4)访问E。C的为一个有指向的邻接点。
(5)访问D。访问E后,因为B已经访问过了,所以访问还未被访问到的D点
(6)访问F。再访问完D点后,D点的邻接点C被访问过了,递归回到E,再递归回到C,再递归回到B点,访问未被访问过的F点
(7)访问G。访问完F后,访问G点。

广度优先搜索方法(BFS)

广度优先搜索算法(Breadth First Search),又称为"宽度优先搜索"或"横向优先搜索",简称BFS。

广度优先搜索主要思想

从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

简单来说就是以最开始的顶点为起点,依次访问预期路径相通且路径长度为1,2,3…的顶点。

无向图广度优先图解
在这里插入图片描述

分析:
A =>C =>F =>B =>D =>G =>E
(1)访问A。
(2)依次访问C、D、F。A有两个邻接点,C和F,C排在F前面,所以先访问C,再访问F。
(3)依次访问B、G。在访问完C、D、F之后,再次访问他们的邻接点。
(4)最后访问E。

有向图广度优先图解
在这里插入图片描述

分析:A =>B =>C =>E =>E =>F =>D =>G
(1)访问A。
(2)访问B。
(3)依次访问C、E、F。
(4)依次访问D、G。


两种遍历的具体实现均在图的实现那节

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值