2017-2018-1 20162330 实验四 图的实现与应用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2017-2018-1 20162330 实验四 图的实现与应用相关的知识,希望对你有一定的参考价值。











 课程名称:《程序设计与数据结构》

 学生班级:1623班

 学生姓名:刘伟康

 学生学号:20162330

 实验时间:2017年11月20日—2017年11月24日

 实验名称:图的实现与应用

 指导老师:娄嘉鹏、王志强老师


目录




实验要求:

实验四 图的实现与应用

  • 1.用邻接矩阵实现无向图(边和顶点都要保存),实现在包含添加和删除结点的方法,添加和删除边的方法,size(),isEmpty(),广度优先迭代器,深度优先迭代器。
    给出伪代码,产品代码,测试代码(不少于5条测试);

  • 2.用十字链表实现有向图(边和顶点都要保存),实现在包含添加和删除结点的方法,添加和删除边的方法,size(),isEmpty(),广度优先迭代器,深度优先迭代器。
    给出伪代码,产品代码,测试代码(不少于5条测试);

  • 3.实现PP19.9。
    给出伪代码,产品代码,测试代码(不少于5条测试)。

【返回目录】


实验步骤及代码实现:


  • 1. 图的实现与应用-1:

    用邻接矩阵实现无向图:
    (边和顶点都要保存),实现在包含添加和删除结点的方法,添加和删除边的方法,size(),isEmpty(),广度优先迭代器,深度优先迭代器。

  • 关于邻接矩阵实现无向图中边和顶点的存储策略,可以将图的顶点用一个一维数组存放,将图的边用一个二维数组存放,其中这个边集合就相当于邻接矩阵了:

    private Object[] vexs;  //顶点集合
    private int[][] arcs;  //边集合,邻接矩阵

    另外我设定了顶点数目和边数目,其中我将顶点数目定义为决定图的大小的变量:

    private int gSize, arcNum;  //顶点数目、边数目

    于是,判断一个图是否为空的 isEmpty() 方法和返回图的大小的 size() 方法就可以根据顶点数目定义了:

    //判断图的大小(顶点个数)
    public int size() {
        return gSize;
    }
    
    //判断图是否为空
    public boolean isEmpty() {
        return gSize == 0;
    }

    关于添加一条边和删除一条边我还没有考虑那么多种情况(还会补充),所以只实现了基本的功能,即邻接矩阵对角线上对应的两边数值添加时都变为1,删除时都变为0:

    //添加一条边
    public void addArc(int x, int y) {
        arcs[x][y] = 1;
        arcs[y][x] = 1;
        arcNum++;
    }
    
    //删除一条边
    public void removeArc(int x, int y) {
        arcs[x][y] = 0;
        arcs[y][x] = 0;
        arcNum--;
    }

    关于添加和删除顶点,我参考了网上的一些思路,然而还没有测试成功,所以这部分内容待补充,实现代码如下:

    //添加一个顶点
    public void addVex() {
        for (int i = 0; i < gSize; i++) {
            arcs[i][gSize] = INFINITY;
        }
        for (int i = 0; i < gSize; i++) {
            arcs[gSize][i] = INFINITY;
        }
        arcs[gSize][gSize] = 0;
        gSize++;
        System.out.println("Insert successfully.");
    }
    
    //删除一个顶点
    public void removeVex(int v) {
        if (v >= gSize) {
            System.out.println("The graph doesn't have this vertex.");
        }
        for (int i = 0; i < gSize; i++) {
            arcs[v][i] = 0;
            arcs[v][i] = 0;
        }
        if (v == gSize - 1) {
            gSize--;
        }
        for (int i = v + 1; i < gSize; i++) {
            for (int j = 0; j < gSize; j++) {
                arcs[i - 1][j] = arcs[i][j];
            }
        }
        gSize--;
    }
  • 之后来说说图的遍历方法的实现,首先常见的图的遍历方法有两种:① 广度优先遍历;② 深度优先遍历。(代码就先不贴出来了)
    对于广度优先遍历,先访问某个顶点,再依次访问每一个未被访问过的邻接点,然后按照这个顺序访问其他顶点,之后访问各个还未被访问过的邻接点,以此类推,直到所有顶点都被访问过为止,可以参考下图:

    技术分享图片

    使用队列实现广度优先遍历的具体思路如下:
    (1)顶点v入队列。
    (2)当队列非空时则继续执行,否则算法结束。
    (3)出队列取得队头顶点v;访问顶点v并标记顶点v已被访问。
    (4)查找顶点v的第一个邻接顶点w。
    (5)若v的邻接顶点w未被访问过的,则w入队列。
    (6)继续查找顶点v的另一个新的邻接顶点w,转到步骤(5)。
    直到顶点 v 的所有未被访问过的邻接点处理完。转到步骤(2)。

  • 实现广度优先遍历的关键就在于要设立一个访问标志数组,初值为0,某顶点被访问后,相应下标元素置为1。

  • 对于深度优先遍历,先从图的某个顶点 v 开始访问,然后访问它的任意一个邻接点w1,再从w1出发,访问与w1邻接但未被访问的顶点w2,然后从w2出发,依次访问,直至所有的邻接点被访问过。之后,退到前一次访问过的顶点,看是否还有其他未被访问过的邻接点。如果有,则访问此顶点,没有再退到前一次访问过的顶点,重复这一过程,直到所有顶点都被访问过为止。(递归)

    使用队列实现深度优先遍历的具体思路如下:
    (1)输入要访问的结点Vi;
    (2)访问顶点vi;visited[vi]=1;
    (3)在邻接矩阵的第i行中查找,若vi有邻接点vj,且vj未被访问过,则设 i=j;
    (4)重复步骤1至3,直到所有结点均被访问到。

  • 和广度优先遍历相同的是,深度优先遍历也要设立一个访问标志数组visited[N],初值为0,某点被访问,则相应下标变量置为1。

  • 在测试时,我直接使用了根据输入的边和顶点创建邻接矩阵的方法,测试运行截图如下:(部分)
    技术分享图片

    技术分享图片

    技术分享图片


  • 2. 图的实现与应用-2:

    用十字链表实现有向图:
    (边和顶点都要保存),实现在包含添加和删除结点的方法,添加和删除边的方法,size(),isEmpty(),广度优先迭代器,深度优先迭代器。

    技术分享图片

  • 实现这个就有点难了,原因是十字链表是针对有向图的,这需要考虑到所有边对应的权值,我参考了网上相关资料的代码,关于顶点和边,可以单独定义两个类:
    在定义十字链表的边的类时,要设定入弧顶点和出弧顶点两个形参:

    public class CrossEdge<E> {
    E data;
    int fromVertexIndex;
    int toVertexIndex;
    CrossEdge<E> nextSameFromVertex;
    CrossEdge<E> nextSameToVertex;
    
    public CrossEdge(E data, int fromVertexIndex, int toVertexIndex) {
        this.data = data;
        this.fromVertexIndex = fromVertexIndex;
        this.toVertexIndex = toVertexIndex;
    }
    }

    在定义十字链表的顶点的类时,需要定义顶点的data形参:

    public class CrossVertex<E, T> {
    E data;
    CrossEdge<T> firstIn;
    CrossEdge<T> firstOut;
    
    public CrossVertex(E data) {
        this.data = data;
    }
    }

    我还没太搞懂方法类中的代码,方法类中的设计思路之后会有补充。

  • 测试运行截图如下:
    技术分享图片


  • 3. 图的实现与应用-3:

    实现PP19.9(最短路径)。

  • 对于如何解决有向图中顶点间的最短路径问题,我在这里使用了戴克斯特拉算法,具体实现如下:

    public void DIJ(MGraph G, int v0){
        int vexNum = G.getVexNum(); // 顶点数
        this.P = new boolean[vexNum][vexNum];
        this.D = new int[vexNum];
        //finish[v]为true当且仅当v属于S,即已经求得从v0到v的最短路径
        boolean[] finish = new boolean[vexNum];
    
        //初始化所有数据
        for(int v = 0; v < vexNum; v++){
            finish[v] = false;
            D[v] = G.getArcs()[v0][v];
            for(int w = 0; w < vexNum; w++){
                P[v][w] = false;
            }
            if(D[v] < INFINITY){
                P[v][v0] = true;
                P[v][v] = true;
            }
        }
    
        D[v0] = 0;  //从v0开始,并入S集
        finish[v0] = true;
    
        int v = -1 ;
        //开始主循环,每次求得v0到某个v顶点的最短路径,并将v加入到S集.循环n-1次
        for(int i = 1; i < vexNum; i++){
            int min = INFINITY; //当前所知离v0最近的距离
            for(int w = 0; w < vexNum; w++){
                if( !finish[w]){
                    if(D[w] < min){
                        v = w;
                        min = D[w];
                    }
                }
            }
            finish[v] = true; //离v0最近的v并入S
    
            //更新当前最短路径和距离
            for(int w = 0; w < vexNum; w++){
                if( !finish[w] && G.getArcs()[v][w] < INFINITY && (min + G.getArcs()[v][w] < D[w])){
                    D[w] = min + G.getArcs()[v][w];
                    //下面两句这么理解,现在路径是v0-v-w,所以经过了v点,那么v0到v的最小路径自然要给w,同时再加上w点(P[W][W] = true)
                    System.arraycopy(P[v], 0, P[w], 0, P[v].length);
                    P[w][w] = true;
                }
            }
        }
    }
  • 测试运行截图如下:(其中,vo表示网络中的点,分别计算vo到各个结点的最短路径)
    技术分享图片

【返回目录】


测试过程及遇到的问题:

  • 1. 在做第一个实验时为什么无向图的边会出现输入错误,导致抛出下标越界的异常?

    技术分享图片

  • 解决办法:(使用debug单步跟踪进行调试)
    调试之后我发现是我的输入格式出了问题,各个边之间是要加一个空格的,因为我在调用了 Scanner 中的 next 方法依次检测输入的边,而在各个边的间隔之间,是要留出一个空格的距离的:

    技术分享图片

    知道了这一“特定格式”之后,再运行时就没有出错了。

【返回目录】


分析总结:

  • 本周的实验主要是对于图的应用,这次的实验比较难,第一个实验就有点无从下手,第二个实验更是参考了网上的代码,很勉强地做了出来,这让我对此感到一些无力。不过,图的设计思路值得学习,因为图在生活中的应用不少,大概也正是因为图和树的密切联系以及其在生活中的应用(网络爬虫等),才诞生了这么多相关的算法,这大概也是很多程序员喜欢研究图的原因。这篇博客写得不全,有待完善。


PSP(Personal Software Process)时间统计:

  • 步骤 耗时 百分比
    需求分析 60min 15%
    设计 60min 15%
    代码实现 120min 30%
    测试 80min 20%
    分析总结 80min 20%

【返回目录】


参考资料:

【返回目录】

以上是关于2017-2018-1 20162330 实验四 图的实现与应用的主要内容,如果未能解决你的问题,请参考以下文章

2017-2018-1 20162306 实验四 实验报告

2017-2018-1 20155314《信息安全系统设计基础》实验四 外设驱动程序设计

2017-2018-1 20155227 20155318 实验四 外设驱动程序设计

2017-2018-1 20155216 实验四:外设驱动程序设计

2017-2018-1 20155307 20155335 20155338 实验四 外设驱动程序设计

2017-2018-1 20155218 20155205 实验四 外设驱动程序设计