数据结构课程设计——项目2:校园导游咨询

一、实验内容及实验要求

【问题描述】

设计一个校园导游程序,为来访的客人提供各种信息查询服务。

【基本要求】

  1. 设计你所在学校的校园平面图,所含景点不少于10个.以图中顶点表示校 内各景点,存放景点名称、代号、简介 等信息;以边表示路径,存放路径长度等相关信息。
  2. 为来访客人提供图中任意景点相关信息的查询。
  3. 为来访客人提供图中任意景点的问路查询,即查询任意两个景点之间的一条最短的简单路径。

【测试数据】

以江苏科技大学长山校区为例。

【实现提示】

一般情况下,校园的道路是双向通行的,可设校园平面图是一个无向网,顶点和边均含有相关信息.


二、问题分析

概述

  • 本实验的编程语言采用Java,并用Swing实现了GUI。用户可以点击校内景点的图片来查询该景点的详细信息,程序支持查询任意两景点间的最短路径,并可以根据初始点的位置提供推荐游览路径。
  • 数据结构采用了图的邻接矩阵存储,并且为了更加方便的实现Floyd算法,还提供了图的前趋结点数组。
  • 最短路径的查询采用了Floyd算法
  • 推荐游览路径即是从一个指定景点开始,不重复的游览完所有景点的路径,这一路径的计算采用了马踏棋盘算法,本质上采用了DFS图的深度优先遍历,另外还采用了贪心思想进行了优化。

最短路径——Floyd算法

Floyd算法可以给出网络中任意两个结点之间的最短路径,因此它是比Dijkstra更一般的算法。Floyd算法的思想是将n个结点的网络表示为n行n列的矩阵,而矩阵中的元素Di,j表示从结点i到结点j的距离,如果两点直接没有边相连,则相应的元素就是无穷∞。
具体过程描述如下:

a.从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。   
b.对于每一对结点 i 和 j,看看是否存在一个结点 k 使得从 i 到 k 再到 j 比己知的路径更短。如果是更新它,直到所有的结点都已经尝试过。

伪代码描述如下:

枚举顶点k ∈ [1,n]
	以顶点k为中介点,枚举所有顶点对i和j(i ∈ [1,n],j ∈1[1,n])
		如果dis[i][k] + dis[k][j] <dis[i][j]成立
			赋值dis[i][j] = dis[i][k] + dis[k][j]

注意Floyd算法为了能回溯得到最短路径的结果,需要声明一个前趋结点数组,用来保存所有点的前一个结点,每次找到更短的路径时,需要更新这一数组。

推荐游览路径——马踏棋盘算法

推荐游览路径是从一个指定景点开始,不重复的游览完所有景点的路径,即在图中寻找一条由指定结点开始的哈密尔顿路径。这一问题可以通过类似马踏棋盘问题的算法来解决,实质上是采用了图的深度优先遍历和贪心算法的优化。
该算法具体过程描述如下:

图的数据结构与前一个算法相同,采用的还是邻接矩阵。
先将当前位置结点设置为已访问,然后根据当前的结点位置,计算下一步还能走哪些位置,并把这些位置放入到一个集合ArrayList中,每走一步,step+1。
遍历ArrayList中存放的所有位置,看看哪个可以走通,如果走通就继续,走不通就回溯。
最后判断是否完成了任务:使用step和应该走的步数比较,如果没有达到数量,则表示没有完成任务,将整个结果置0。

注意这样的搜索结果会根据策略的不同而改变,不同的策略执行的时间快慢也不一样,在这里,我们可以通过贪心算法对它进行优化,具体过程如下

在马踏棋盘算法获取下一步可以走的位置集合ArrayList之前,我们需要对ArrayList集合中点的下一步的所有集合的数目进行非递减排序
这样在ArrayList中第一个结点就是下一步应该走的结点,我们需要优先选择它,因为它的后续结点最少,代表着需要回溯的点也越少。

采用了贪心算法优化之后,程序执行的时间效率会大大提高。


三、逻辑设计

程序有三个包分别为:domain、service、 ui
domain包下提供了两个类:

  • View类是景点的抽象,生成的对象代表学校里的景点,具有私有属性:景点的编号、名称以及景点的介绍。
  • MGraph类是图的数据结构,采用邻接矩阵表示,顶点数组中存放每一个景点的对象,该类的构造方法初始化了图的结点和边。

Service包下提供了三个类:

  • ShortestPathService类提供了求图中任意两个景点之间的最短路径的Floyd算法。
  • TourPathService类提供了求推荐游览路径的马踏棋盘算法。
  • ViewsService类用来展示各景点的详细信息。

ui包中是Swing的一些组件类,用于实现GUI,同时其中的MainInterface类也是程序的入口。

以下是程序的流程图:
程序流程图


四、物理设计

本实验的数据结构采用图的邻接矩阵各算法也依据邻接矩阵来实现。图中每个结点都是一个景点View类的对象。
下图是景点的带权无向图:
在这里插入图片描述

以下是图以及景点类的数据结构:

package com.domain;


import java.util.Arrays;

/*
    类MGraph:用于表示学校景点的无向网
*/
public class MGraph {
    //最大顶点数
    public static final int MAX_SIZE = 20;
    //最大权值,即∞
    public static final int MAX_INT = 65535;
    //顶点数组
    private View[] vertex = new View[MAX_SIZE];
    //图的邻接矩阵
    private int[][] adj = new int[MAX_SIZE][MAX_SIZE];
    //图的前趋结点数组
    private int[][] pre = new int[MAX_SIZE][MAX_SIZE];
    //图的顶点数和边数
    private int vertexNum, edgeNum;


    //初始化图结构
    public MGraph() {
        //设置结点数与边数
        vertexNum = 12;
        edgeNum = 17;
        //初始化顶点数组
        initialVertex(vertex);
        //初始化邻接矩阵
        initialAdj(adj);
        //初始化前趋结点数组
        initializePre(pre);
    }


    //初始化顶点数组
    private void initialVertex(View[] vertex) {
        vertex[0] = new View(1, "行政楼", "办公主楼,校领导办公的地方~");
        vertex[1] = new View(2, "海韵湖", "海韵湖连通着长江,景色优美,是饭后散步的好地方,外围有红蓝塑胶跑道,学校时常举办环湖跑比赛");
        vertex[2] = new View(3, "图书馆", "图书馆总共五层,室内环境优雅,藏书众多,更有窗边观湖雅座,是各大卷王的必争之地");
        vertex[3] = new View(4, "东食堂", "东食堂有很多特色菜肴,食堂对面就是图书馆");
        vertex[4] = new View(5, "东操场", "体育锻炼的好地方,时常举办足球比赛");
        vertex[5] = new View(6, "南门", "校门庄严气派,毗邻长晖路,马路对面有烧烤和麻辣烫卖");
        vertex[6] = new View(7, "文体中心", "一楼有乒乓球馆、健美操馆、健身房,二楼有室内篮球场和羽毛球场,是锻炼身体的好去处,旁边还有船苑剧场可以看电影");
        vertex[7] = new View(8, "西操场", "平时举办运动会的地方");
        vertex[8] = new View(9, "经世楼", "学校四座教学楼中的一座,其它几个分别为笃学楼、明德楼以及文理大楼,教师多媒体讲台、多功能显示屏、打印机、自动贩卖机等基础设施完善");
        vertex[9] = new View(10, "文理大楼", "长山校区的地标建筑,共21层,到天台可以俯瞰整个校园景色,气势磅礴");
        vertex[10] = new View(11, "西食堂", "西食堂有三层,第三层是民族食堂,可以品尝各地的特色菜肴");
        vertex[11] = new View(12, "西宿舍区", "西片区学生住宿的地方,分为7个组团,从宿舍到教学楼要跑好多路QAQ");
    }

    //初始化邻接矩阵
    private void initialAdj(int[][] adj){
        //先初始化
        for (int i = 0; i<vertexNum;i++){
            for (int j = 0; j<vertexNum;j++){
                adj[i][j] = MAX_INT;
            }
            adj[i][i] = 0;
        }
        adj[0][1]=300;      //行政楼-海韵湖
        adj[1][0]=300;
        adj[0][9]=700;      //行政楼-文理大楼
        adj[9][0]=700;
        adj[9][8]=100;      //文理大楼-经世楼
        adj[8][9]=100;
        adj[2][9]=400;      //文理大楼-图书馆
        adj[9][2]=400;
        adj[1][2]=600;      //海韵湖-图书馆
        adj[2][1]=600;
        adj[7][8]=100;      //西操场-经世楼
        adj[8][7]=100;
        adj[2][7]=550;      //西操场-图书馆
        adj[7][2]=550;
        adj[7][10]=250;     //西食堂-西操场
        adj[10][7]=250;
        adj[6][7]=100;      //文体中心-西操场
        adj[7][6]=100;
        adj[2][3]=100;      //图书馆-东食堂
        adj[3][2]=100;
        adj[6][10]=300;     //西食堂-文体中心
        adj[10][6]=300;
        adj[3][6]=550;      //文体中心-东食堂
        adj[6][3]=550;
        adj[10][11]=200;        //西宿舍区-西食堂
        adj[11][10]=200;
        adj[5][6]=250;      //文体中心-南门
        adj[6][5]=250;
        adj[3][4]=100;      //东食堂-东操场
        adj[4][3]=100;
        adj[5][11]=700;     //西宿舍区-南门
        adj[11][5]=700;
        adj[4][5]=250;      //南门-东操场
        adj[5][4]=250;
    }

    //初始化前趋结点数组
    private void initializePre(int[][] pre){
        for (int i = 0; i<vertexNum;i++){
            Arrays.fill(pre[i],i);
        }
    }

    public View[] getVertex() {
        return vertex;
    }

    public void setVertex(View[] vertex) {
        this.vertex = vertex;
    }

    public int[][] getAdj() {
        return adj;
    }

    public void setAdj(int[][] adj) {
        this.adj = adj;
    }

    public int getVertexNum() {
        return vertexNum;
    }

    public void setVertexNum(int vertexNum) {
        this.vertexNum = vertexNum;
    }

    public int getEdgeNum() {
        return edgeNum;
    }

    public void setEdgeNum(int edgeNum) {
        this.edgeNum = edgeNum;
    }

    public int[][] getPre() {
        return pre;
    }

    public void setPre(int[][] pre) {
        this.pre = pre;
    }
}

package com.domain;
/*
    类View:用于表示每一个景点
*/
public class View {
    //景点的编号
    private int id;
    //景点的名称
    private String name;
    //景点的介绍
    private String introduction;

    public View(int id, String name, String introduction) {
        this.id = id;
        this.name = name;
        this.introduction = introduction;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getIntroduction() {
        return introduction;
    }

    public void setIntroduction(String introduction) {
        this.introduction = introduction;
    }
}

下面是最短路径Floyd算法的代码:

//Floyd算法
    private void floyd() {
        //变量保存距离
        int len = 0;
        //对中间结点遍历,k就是中间结点的下标
        for (int k = 0; k < mGraph.getVertexNum(); k++) {
            //从i结点开始出发
            for (int i = 0; i < mGraph.getVertexNum(); i++) {
                //到达j结点
                for (int j = 0; j < mGraph.getVertexNum(); j++) {
                    //从i结点出发,经过中间结点k,到达j结点的距离
                    len = dis[i][k] + dis[k][j];
                    //如果比原来的距离小,更新距离和前趋结点
                    if (len < dis[i][j]) {
                        dis[i][j] = len;
                        pre[i][j] = pre[k][j];
                    }
                }
            }
        }
    }

下面是推荐游览路径马踏棋盘算法及其使用贪心算法优化的代码:

 /*
        DFS寻找哈密尔顿路径:马踏棋盘算法
        result为图,id为当前点(从0开始),step为步数记录,初始步数为1
    */
    private void traversalMap(int[] result, int id, int step){
        //更新结果图
        result[id] = step;
        //标志该位置已经访问
        visited[id] = true;
        //获取当前位置可以走的下一个位置的集合
        ArrayList<Integer> ps = next(id);
        //利用贪心算法排序优化,优先选择下一步可选择位置最少的点
        sort(ps);
        //遍历ps,递归回溯
        while (!ps.isEmpty()){
            //取出下一个可以走的位置
            Integer p = ps.remove(0);
            //判断该点是否被访问过
            if (!visited[p]){
                //如果没有访问过,进行下一步
                traversalMap(result,p,step+1);
            }
        }
        //判读是否遍历了所有的点,使用step和应该走的步数比较,如果没有到达数量,将整个结果置0
        //step<结点数 成立的情况有两种
        //1.所有结点到目前为止仍然没有走完
        //2.所有结点到目前为止已经走完,处于回溯过程(无路可走)
        if (step<mGraph.getVertexNum() && !finished){
            result[id] = 0;
            visited[id] = false;
        }else {
            //如果完成了
            finished = true;
        }
    }
    
	//根据当前点的位置,计算还能走哪些位置,把这些点的id放入一个ArrayList中,id从0-11
    private ArrayList<Integer> next(int id) {
        ArrayList<Integer> ps = new ArrayList<>();
        for (int j = 0; j<mGraph.getVertexNum(); j++) {
            //如果这条路连通,就把点加入
            if (dis[id][j]<MGraph.MAX_INT){
                ps.add(j);
            }
        }
        return ps;
    }

    //利用贪心算法对DFS优化
    //根据当前这一步的下一步的选择位置,进行非递减排序,减少在DFS时的回溯次数
    private void sort(ArrayList<Integer> ps){
        ps.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return next(o1).size()-next(o2).size();
            }
        });
    }

查询景点详细信息的代码最为简单,但用GUI实现景点的展示对我这个新手来说还是很繁琐的。

//传入景点id,返回景点的介绍(字符串)
    public String getViewInformation(int id){
        return vertex[id-1].getIntroduction();
    }

总结

对于本次实验来说还有许多可以改进的地方

  1. 图的结点是不可以修改的,其实一开始也考虑过通过文件读取图中结点以及边的权值信息,或者用户可以手动修改结点和边的信息。但因为想要用GUI展示,对于结点的修改肯定要动态的改变地图样貌,这样一来实现起来就非常困难,而且用户对结点修改的步骤也较为繁琐,所以索性就把结点和边定死了。
  2. 图不能动态的展示,每次查询最短路径只能通过文字信息展现,不能在地图上画出此次查询的最短路线。因为要实现这一功能需要加上窗口的坐标变换和计算,画出相应的行动轨迹,对我来说过于困难。
  3. 功能较少,只有查询景点、最短路径和推荐路径这三个功能。其实一方面可以根据路况信息、甚至是地形的坡度来动态的改变边的权值。另一方面可以增加每个景点的推荐程度这一信息来对景点进行排序,给用户优先级的推荐等。
  4. 推荐路径只能推荐从当前结点出发一个可行的哈密尔顿道路,并不能求出游览所有景点一次的最短道路。
  • 6
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值