图的创建(邻接表/邻接矩阵)、遍历(DFS、BFS)与最短路径(Dijkstra/Floyd)算法

目录

一、图的创建

1.邻接矩阵

2.邻接表

3.其他存储方式

二、图的遍历

1.深度优先DFS(使用递归)

2.广度优先BFS(使用队列)

3.DFS与BFS的比较

三、最短路径求解

1.迪杰斯特拉算法(Dijkstra)

2.弗洛伊德算法(Floyd)

3.Dijkstra与Floyd的比较


抛出问题:为什么会有图这种数据结构?
因为线性表表示的是一对一的关系,树表示了一对多的关系。当我们需要使用到多对多关系时,就需要使用图这种数据结构。

图的创建

1.邻接矩阵(二维数组表示)

n个顶点,数组就是【n】【n】
图有N个顶点,邻接矩阵就用N*N的二维数组表示。matrix[i][j] 的值为顶点i到顶点j的权值。比较简单,直接上java代码写的邻接矩阵(核心代码如下:)

public class Graph2 {
    ArrayList<Vertex> vertexList;	//顶点加入到List集合
    int[][] edgeMatrix; //初始化后都为0
    int vertexNum;      //顶点数量
    int edgeNum;        //边的数量

    /**
     * 插入顶点
     * @param vertex
     */
    public void insertVertex(Vertex vertex){
        vertexList.add(vertex);
        //this.vertexNum++;
    }

    /**\
     * 添加边
     * @param v1 第一个顶点的下标
     * @param v2    第二个顶点的下标
     * @param weight    边的权值
     */
    public void insertEdge(int v1,int v2,int weight){
        edgeMatrix[v1][v2] =weight;
        edgeMatrix[v2][v1] =weight; //无向图就加上这一句
        edgeNum++;
    }
    }

/**
 * 顶点类
 */
class Vertex {
    String vertexName;

    public Vertex(String vertexName) {
        this.vertexName = vertexName;
    }

    @Override
    public String toString() {
        return  vertexName ;
    }
}

2.邻接表(一维数组加链表)

这里有两种实现情况:(这里数组都用ArrayList实现)

  • 使用顶点链接顶点的方式构建图,这种方法比较low,头结点后链接的每一条边都是新new的顶点结点,这样做会避免指针紊乱,但会占用很多额外的空间。
  • 使用边(边是由两个顶点V1 V2构成的)的第二个顶点V2所在ArayList中的索引位置作为边对象构建图(这种方法好些、李春葆教材使用的方式)

2.1使用顶点链接顶点的方式构建图

package vertex;

import java.util.Scanner;

/**
 * 数组加链表形式  数组是vertexList ,用ArrayList来代替
 * 邻接链表使用顶点作为对象构建图
 */

public class CreateGraph3 {

    /**
     * 根据用户输入的string类型的顶点返回该顶点
     * @param graph 图
     * @param str 输入顶点名称
     * @return返回一个顶点
     */
    public Vertex1 getVertex(Graph1 graph,String str){
        for(int i=0;i<graph.verNum;i++){
            if(graph.vertexArray[i].verName.equals(str)){
                return graph.vertexArray[i];
            }
        }
        return null;
    }

    /**
     * 根据用户输入的数据初始化一个图,以邻接表的形式构建!
     * @param graph 生成的图
     */
    public void initialGraph(Graph1 graph){
        @SuppressWarnings("resource")
        Scanner scan=new Scanner(System.in);
        System.out.println("请输入顶点数和边数:");
        graph.verNum=scan.nextInt();
        graph.edgeNum=scan.nextInt();

        System.out.println("请依次输入定点名称:");
        for(int i=0;i<graph.verNum;i++){
            Vertex1 vertex=new Vertex1();
            String name=scan.next();
            vertex.verName=name;
            vertex.nextNode=null;
            graph.vertexArray[i]=vertex;
        }

        System.out.println("请依次输入图的边(两个顶点):");
        for(int i=0;i<graph.edgeNum;i++){
            String preV=scan.next();
            String folV=scan.next();

            Vertex1 v1=getVertex(graph,preV);
            if(v1==null)
                System.out.println("输入边存在图中没有的顶点!");

//下面代码是图构建的核心:链表操作    头插法
            // !!!注意  这里使用新new一个V2结点的方式  作为要连接的对象   不能链接VertexList中的V2对象,那样会使链表指向发生变化,导致整个链表发生错误紊乱
            Vertex1 v2=new Vertex1();
            v2.verName=folV;
            v2.nextNode=v1.nextNode;
            v1.nextNode=v2;

//紧接着下面注释的代码加上便是构建无向图的,不加则是构建有向图的!
//          Vertex1 reV2=getVertex(graph,folV);
//          if(reV2==null)
//              System.out.println("输入边存在图中没有的顶点!");
//          Vertex1 reV1=new Vertex1();
//          reV1.verName=preV;
//          reV1.nextNode=reV2.nextNode;
//          reV2.nextNode=reV1;
        }
    }

    /**
     * 输入图的邻接表
     * @param graph 待输出的图
     */
    public void outputGraph(Graph1 graph){
        System.out.println("输出图的邻接链表为:");
        for(int i=0;i<graph.verNum;i++){
            Vertex1 vertex=graph.vertexArray[i];
            System.out.print(vertex.verName);

            Vertex1 current=vertex.nextNode;
            while(current!=null){
                System.out.print("-->"+current.verName);
                current=current.nextNode;
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        Graph1 graph=new Graph1();
        CreateGraph3 createGraph=new CreateGraph3();
        createGraph.initialGraph(graph);
        createGraph.outputGraph(graph);
    }
}

------------------------------------------------------
package vertex;

/**
 * 自定义图类
 * @author King
 */
public class Graph1 {
    Vertex1[] vertexArray=new Vertex1[100];
    int verNum=0;
    int edgeNum=0;
}
-------------------------------------------------------
package vertex;

/**
 * 顶点类
 * @author King
 */
public class Vertex1 {
    String verName;
    Vertex1 nextNode;
}

下面我们说一说这种方式为什么会占用很多额外空间
如下图与他的邻接表所示
在这里插入图片描述

对于B结点,如果数组中的结点和链表中的结点都是用同一个对象,那么B到底是指向C呢还是指向D呢? 为了避免这种指针紊乱,所以每个边对象都需要新new一个顶点结点,有多少条边就会需要多少额外对象空间

2.2使用边(边是由两个顶点V1 V2组成的)的第二个顶点V2所在索引位置作为边对象构建图(教材中普遍使用的方式)

这样,边是边对象,顶点是顶点对象,不会发生上述情况。虽然说每个边对象也得重新new,但是边里面存放的只是下标与next指针,数据量比较小(假如顶点里面包含的信息很多,水平不够,暂时只能这么理解···)

package vertex;

import java.util.Scanner;

/**
 * 数组加链表形式  数组是vertexList ,用ArrayList来代替
 * 邻接链表使用顶点作为对象构建图
 */

public class CreateGraph3 {

    /**
     * 根据用户输入的string类型的顶点返回该顶点
     * @param graph 图
     * @param str 输入顶点名称
     * @return返回一个顶点
     */
    public Vertex1 getVertex(Graph1 graph,String str){
        for(int i=0;i<graph.verNum;i++){
            if(graph.vertexArray[i].verName.equals(str)){
                return graph.vertexArray[i];
            }
        }
        return null;
    }

    /**
     * 根据用户输入的数据初始化一个图,以邻接表的形式构建!
     * @param graph 生成的图
     */
    public void initialGraph(Graph1 graph){
        @SuppressWarnings("resource")
        Scanner scan=new Scanner(System.in);
        System.out.println("请输入顶点数和边数:");
        graph.verNum=scan.nextInt();
        graph.edgeNum=scan.nextInt();

        System.out.println("请依次输入定点名称:");
        for(int i=0;i<graph.verNum;i++){
            Vertex1 vertex=new Vertex1();
            String name=scan.next();
            vertex.verName=name;
            vertex.nextNode=null;
            graph.vertexArray[i]=vertex;
        }

        System.out.println("请依次输入图的边(两个顶点):");
        for(int i=0;i<graph.edgeNum;i++){
            String preV=scan.next();
            String folV=scan.next();

            Vertex1 v1=getVertex(graph,preV);
            if(v1==null)
                System.out.println("输入边存在图中没有的顶点!");

//下面代码是图构建的核心:链表操作    头插法
            // !!!注意  这里使用新new一个V2结点的方式  作为要连接的对象   不能链接VertexList中的V2对象,那样会使链表指向发生变化,导致整个链表发生错误紊乱
            Vertex1 v2=new Vertex1();
            v2.verName=folV;
            v2.nextNode=v1.nextNode;
            v1.nextNode=v2;

//紧接着下面注释的代码加上便是构建无向图的,不加则是构建有向图的!
//          Vertex1 reV2=getVertex(graph,folV);
//          if(reV2==null)
//              System.out.println("输入边存在图中没有的顶点!");
//          Vertex1 reV1=new Vertex1();
//          reV1.verName=preV;
//          reV1.nextNode=reV2.nextNode;
//          reV2.nextNode=reV1;
        }
    }

    /**
     * 输入图的邻接表
     * @param graph 待输出的图
     */
    public void outputGraph(Graph1 graph){
        System.out.println("输出图的邻接链表为:");
        for(int i=0;i<graph.verNum;i++){
            Vertex1 vertex=graph.vertexArray[i];
            System.out.print(vertex.verName);

            Vertex1 current=vertex.nextNode;
            while(current!=null){
                System.out.print("-->"+current.verName);
                current=current.nextNode;
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        Graph1 graph=new Graph1();
        CreateGraph3 createGraph=new CreateGraph3();
        createGraph.initialGraph(graph);
        createGraph.outputGraph(graph);
    }
}
------------------------------------------------------
package vertex;

/**
 * 自定义图类
 * @author King
 */
public class Graph1 {
    Vertex1[] vertexArray=new Vertex1[100];
    int verNum=0;
    int edgeNum=0;
}
-------------------------------------------------------
package vertex;

/**
 * 图的顶点类
 * @author King
 */
public class Vertex1 {
    String verName;
    Vertex1 nextNode;
}

3.其他存储方式

3.1逆邻接表

由于在有向图的邻接表中只存放了一个以顶点为起点的边,所以不易找到 指向该顶点的边 ,所以有了有向图的逆邻接表.就是在有向图的邻接表中对每个顶点链接的是 指向该顶点的边。

3.2十字链表(有向图的另一种存储方式)

这种方式综合了邻接表与逆邻接表的优势,将两者结合 。既容易找到一顶点v为起点的所有边(方便求其出度),也容易找到以顶点v为终点所有边(方便求其入度)

3.3邻接多重表

无向图的另一种存储方式,与十字链表类似。


图的遍历在下一篇:图的遍历

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值