校园导航-南邮仙林校区


本课题采用C++语言,利用图形化工具Qt5.8.0设计,完整代码在 github上,请下载后查看。除代码外,github上的资源还包括:

  1. 含有可独立运行的exe可执行文件的Navigate文件夹
  2. 程序可能用到的原始数据

课题内容和要求

根据南京邮电大学仙林校区的校内主要建筑物和道路,构建一张带权无向图。完成校内导航模式设计和游览模式设计。具体要求如下:
(1)构建校内带权无向图,并用图形界面进行显示。
(2)导航模块设计:输入起始位置和终点位置,给出最优路线及两条备选路线。
(3)游览模式设计:构建算法,分析是否能设计遍览校内景点一次且仅一次的路线图和遍历校内所有路径一次且仅一次的路线图。

课题需求分析

本课题目标系统“校园导航、游览模式设计”的功能框架图如图1所示。
图1 功能框架图
(1)支持选择起点和终点,并给出最短路径。
(2)给出两条备选路径,并用不同颜色显示;为方便区别,给出备选路径的时候清空推荐路径。
(3)设计一次游园模式,即从一个顶点出发,能遍历所有的点一次且仅一次。
(4)支持地图选点,查看详细信息,并设置为起点或终点。
(5)设计一次遍历所有路径一次且仅一次模式,若不能遍历完所有路径,则转到游园模式。

相关数据结构及算法设计

主要的数据结构

采用邻接表存储带权无向图的信息,数据结构如下:

typedef struct  ENode{
    int adjVex;//任意顶点u相邻接的顶点
    int w;//边的权值
    struct ENode* nextArc;//指向下一个边结点
}ENode;
typedef struct{
    int n;//图的当前顶点数
    int e;//图的当前边数
    ENode **a;//指向一维指针的数组
}LGraph;

主要算法流程

(1)路径规划算法:使用Dijkstra算法求最短路径,先初始化数组s,d,path,然后求最短路径,更新d和path并把顶点k加入s中,循环再求最短路径。其算法的流程图如下:
图2 Dijkstra算法流程图
(2)备选路径算法:求备选路径时,先将Dijkstra算法求出的最短路径存入isChosen数组中,然后删除数组中权值最大的边,再用Dijkstra算法求最短路径,此时求得第一条备选路径。再从备选路径数组中删除权值最小的边,用Dijkstra算法求最短路径,此时求得第二条备选路径。
(3)设计一次游园路径:利用图的深度优先遍历,从一个顶点出发,调用递归算法,求能否经过所有的点一次且仅一次。其算法流程图如下:
图3 深度优先遍历流程图
(4)设计一次遍历所有的路:从某一结点出发,遍历所有的路一次且仅一次,即判断图是不是“欧拉(半)图”。对于连通图来说,如果结点没有奇数度节点,则其是欧拉图;若有两个奇数度节点,则其是欧拉半图。因此只要数奇数度节点即可判断能不能遍历所有的路一次且仅一次。
(5)路径展示算法:根据路径规划算法中求得的结点序列,在背景图上依次连接各个结点。
(6)地图选点:捕捉鼠标左键单击位置,判断其与图上点的位置关系,以此来区分不同的点。

源程序代码 (部分)

  1. map.h定义Dijkstra算法,用于求最短路径
void Dijkstra(int v);//dijkstra算法求最短路径

map.cpp实现如下:

void Map::Dijkstra(int v)
{
        ENode *q;
        if (v<0 || v>lg->n)
            return ;
        int *s = (int *)malloc(sizeof(int)*lg->n);
        int i;
        ENode *p;
        for (i = 0; i < lg->n; i++)						//初始化
        {
            d[i] = INFTY;
            s[i] = 0;
            path[i] = -1;
        }
        p=lg->a[v];
        while (p)									//顶点v为源点
        {
            d[p->adjVex] = p->w;
            if (i != v && d[p->adjVex] < INFTY)
                path[p->adjVex] = v;
            p = p->nextArc;
        }
        s[v] = 1;
        d[v] = 0;
        int k;
        for (i = 1; i <= lg->n-1; i++){
            k = Choose(d, s, lg->n);
            s[k] = 1;									//k加入s中
            q = lg->a[k];
            while (q)									//更新d和path
            {
                if (s[q->adjVex] == 0 && d[k] + q->w < d[q->adjVex]){
                    d[q->adjVex] = d[k] + q->w;
                    path[q->adjVex] = k;
                }
                q = q->nextArc;
            }
        }
        free(s);
}

2)map.h定义SecondDijkstra算法,用于求备选路径

void SecondDijkstra(int u, int v);    //dijkstra算法求第二路径

map.cpp实现如下:

void Map::SecondDijkstra(int u,int v)//删除权值最大的边,重新求最短路径
{
    int k=0;
    ENode *p,*q;
    int t=0;
    int maxEdge=0;
    for(int i=0;i<lg->n;i++) {
        p=lg->a[i];
    while(p){
        if(maxEdge<=p->w&&isChosen[i][p->adjVex]==true&&OutEdge(i)>1&&OutEdge(p->adjVex)>1){
            maxEdge=p->w;
            k=i;
            t=p->adjVex;
        }
        p=p->nextArc;
    }
    }
    Remove(k,t);
    Remove(t,k);
    if (v<0 || v>lg->n)
        return;
    int *s = (int *)malloc(sizeof(int)*lg->n);
    int i;
    for (i = 0; i < lg->n; i++)							    	//初始化
    {
        Secondd[i] = INFTY;
        s[i] = 0;
        SecondPath[i] = -1;
    }
   p = lg->a[u];
    while (p)												//顶点u为源点
    {
        Secondd[p->adjVex] = p->w;
        if (i != u && Secondd[p->adjVex] < INFTY)
            SecondPath[p->adjVex] = u;
        p = p->nextArc;
    }
    s[u] = 1;
    Secondd[u] = 0;
    for (i = 1; i <= lg->n-1; i++){
        k = Choose(Secondd, s, lg->n);
        s[k] = 1;											//k加入s中
        q = lg->a[k];
        while (q)											//更新d和path
        {
            if (s[q->adjVex] == 0 && Secondd[k] + q->w < Secondd[q->adjVex]){
                Secondd[q->adjVex] = Secondd[k] + q->w;
                SecondPath[q->adjVex] = k;
            }
            q = q->nextArc;
        }
    }
    free(s);
    //选择矩阵归零,以备一下次选择
    for(int i=0;i<Max_Vex;i++)
        for(int j=0;j<Max_Vex;j++)
            isChosen[i][j]=false;
}

3)map.h定义欧拉(半)图的判断和输出:

int JudgeEulur();                 //判断欧拉图
void DFSEdge(int v);             //深度优先遍历边

map.cpp实现如下:

int Map::JudgeEulur()
{
    int count=0;
    for(int i=0;i<Max_Vex;i++){
        if(OutEdge(i)%2==1)   //数奇数度节点
            count++;
    }
    return count;
}

void Map::DFSEdge(int v)
{
    DFSedgevisited.push_back(v);
    ENode*w;
    int q;
    for(w=lg->a[v];w;w=w->nextArc){
        q=w->adjVex;
        if(Exist(v,q)){
            Remove(v,q);
            Remove(q,v);
            DFSEdge(q);
        }
    }
}

4)mainwindow.h定义FindPath()函数,用于找到最短路径并在地图上画出来。

void FindPath();   //用于寻找最短路径并在图上显示,响应开始导航按钮和切换路线按钮

mainwindow.cpp实现如下:

void MainWindow::FindPath()
{
    page=0;
    Clear();
    AddEdge();
    QPainter paint(&paintMap->map);
    m->Dijkstra(StartComboBox->currentIndex());
    QVector<int> nextPath;
    nextPath=get_Path(EndComboBox->currentIndex());
    paint.setBrush(Qt::red);
    paint.drawEllipse(paintMap->qpointF[nextPath[0]]-QPointF(50,150),5,5);
    paint.setBrush(Qt::blue);
    paint.drawEllipse(paintMap->qpointF[nextPath[nextPath.size()-1]]-QPointF(50,150),5,5);
    QPen pen;
    pen.setWidth(3);
    pen.setColor(QColor(50,205,50));
    paint.setPen(pen);
    for(int i=1;i<nextPath.size();i++){
       qDebug()<<nextPath[i]<<",";
       m->setIsChosen(nextPath[i-1],nextPath[i],true);
    }
    //开始画路径
    QPainterPath path;
    path.moveTo(StartPos.x()-50,StartPos.y()-150);
    for(int i=1;i<nextPath.size();i++){
        path.lineTo(paintMap->qpointF[nextPath[i]].x()-50,paintMap->qpointF[nextPath[i]].y()-150);
        paint.drawPath(path);
        path.moveTo(paintMap->qpointF[nextPath[i]].x()-50,paintMap->qpointF[nextPath[i]].y()-150);
    }
    this->update();          //调用PaintEvent重画事件
    page++;
}

5)mainwindow.h定义函数PaintEvent

void paintEvent(QPaintEvent *);//重画事件

mainwindow.cpp重新实现此虚函数如下:

void MainWindow::paintEvent(QPaintEvent *event)
{
    QPainter paint(this);
    paintMap->drawBackground(&paint);//画背景地图
    //画图例
    paint.setBrush(Qt::red);
    paint.drawEllipse(QPointF(625,55),5,5);
    paint.drawText(QPointF(635,60),tr("起点"));
    paint.setBrush(Qt::blue);
    paint.drawEllipse(QPointF(625,75),5,5);
    paint.drawText(QPointF(635,80),tr("终点"));
    QPen pen;
    pen.setWidth(3.5);
    pen.setColor(QColor(50,205,50));
    paint.setPen(pen);
    paint.drawLine(QPointF(600,95),QPointF(625,95));
    pen.setColor(Qt::black);
    paint.setPen(pen);
    paint.drawText(QPointF(635,100),tr("推荐路线"));
    pen.setWidth(3.5);
    pen.setColor(QColor(173,255,47));
    paint.setPen(pen);
    paint.drawLine(QPointF(600,115),QPointF(625,115));
    pen.setColor(Qt::black);
    paint.setPen(pen);
    paint.drawText(QPointF(635,120),tr("备选路线1"));
    pen.setWidth(3.5);
    pen.setColor(QColor(144,238,144));
    paint.setPen(pen);
    paint.drawLine(QPointF(600,135),QPointF(625,135));
    pen.setColor(Qt::black);
    paint.setPen(pen);
    paint.drawText(QPointF(635,140),tr("备选路线2"));
}

6)mainwindow.cpp构造函数中,实现关联切换路径与函数的代码如下:

connect(ReverseButton,&QPushButton::released,
            [=]()mutable{
                   if(page%3==1){
                       emit FindSecondPath();
                       page++;
                   }else if(page%3==2){
                       emit FindThirdPath();
                       page++;
                   }else{
                       emit FindPath();
                   }
            });

程序所有的源代码已上传github:
https://github.com/FFtu-ChrisKun/Navigate_in_NJUPT

测试数据及其结果

1)测试导航模块:
测试用例1:起点:南门 终点:北门
测试用例2:起点:教2 终点:桂苑
测试结果:
图4 开始导航测试1
图5 开始导航测试2
2) 测试地图选点:
测试用例:起点:青教 终点:南一
测试结果:
图6 地图选点测试
3)测试切换路线:
测试用例:起点:青教 终点:南一
测试结果:
图7 切换路线测试1
图8 切换路线测试2
4)测试我要游园:
测试结果:
图9 我要游园测试1
图10 我要游园测试2
5)测试我想走路:
测试结果:
图11 我想走路测试

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现简单的查询,各风景的查询,调用各函数,实现课程设计的目标。其中包含三个功能,一个是直接进入导航系统,利用主函数中已有的数据,进行查询:一个是进行创建数据,本程序中初始数据为农大的导航数据,如果需要也可以自己建立一个;最后一个是退出功能。设计该函数的目的是为了能够多次得应用dijkstra函数进行查询最短路径。同时该函数可以列出各景点的代号和对应的名称,这样大家只要输入代号就行了。方便进行查询。下面分别描述这些函数,建立它们函数原型。 1、主函数 函数原型:void main(void) 功 能:控制程序。 参 数:void 返 回 值:void 要 求:管理菜单命令并完成初始化。 2、菜单选择和处理函数 函数原型:int menu() 功 能:处理选择的菜单命令并接收用户选择的命令代码。 参 数:int 返 回 值:int 工作方式:返回命令代码的整数值,根据命令,调用相应函数。 要 求:只允许选择规定键,如果输入不合要求,则提醒用户重新输入。 3、建立邻接矩阵函数 函数原型:void createadj() 功 能:重新建立一个学生信息的记录。 参 数:void 返 回 值:void 工作方式:在需要的时候就可以有主菜单中调用 void createadj()函数。 要 求:必需输入信息记录,然后才能调用出search()函数进行查询。 4、dijkstra函数 函数原型:void dijkstra(intx,inty) 功 能:求两点间的最短路径 参 数:void 返 回 值:void 工作方式: 该函数被其它一些函数调用。 5、结束程序 函数原型:int Exit() 功 能:使程序正常结束运行 参 数:int 返 回 值:1 工作方式:在操作都完成后,可以调用int Exit()函数,使函数最终返回 1 运行exit(1),程序正常结束。 要 求:运行Exit()函数后可以选择是否要保存,选择y则先保存再返 回1值;如果选择n直接返回1值。详细的程序设计应从下到上,在本设计中就要先设计createadj函数;然后设计dijkstra函数;接着是search函数;menu函数;最后才是main函数。如此设计能大大提升设计速度,因为从下往上使编程时的高试过程简单许多,而做课程设计花费时间最多的就是调试过程。对于各函数的详细设计,各函数的N—S图如下: (1)Createadj函数 (2)Dijkstra函数          (3)Search函数          (4)Menu函数          (5)main函数          2.4 程序编码   把详细设计的结果进一步求精为程序设计语言程序。同时加入一些注解和断言,使程序中逻辑概念清楚;编写过程中参考各种的教材和材料,使程序编写的正确性大有提高,同时也许到许多实践知识,增加了实践经验。 2.5 程序调试与测试    程序编写总是出现各种各样的错误,但是难点不是修改错误,而是找出错误。在大量的源程序中找出错误难度很大,但有了一定的方法,就能节省大量的时间,在这次课程设计中我运用的调试方法主要有2种:     一是借助调试工具。利用Turbo C中提供的程序专门调试工具Debugger程序,可以很容易找出程序中的各种语法错误。但是遇到一些逻辑错误时却又无从着手。这时我就用下面一种方法。     二是在程序中插入打印语句。猜测出大致的错误位置,选则一些主要变量,在关键部位插入打印语句,打印出这个主要变量,看其是否与理论上的一样,在多个位置插入,如果有个位置的值与理论值一样,另一个位置与理论值不一样,则错误就落在这两个位置之间,然后再多测试几个位置缩小范围,直到找出错误。  例如;我在调试main()主函数时,程序能够运行,三个选项都能选择,创建函数能够正常运行,也能正常退出,但在选第一条进入校园导航后,打印出来的列表却是空的,源程序中的初始化数据没有显示出来,我又尝试输入两个结点进行查找,发现没有输出路线,所以我猜测初始化数据没有被正常写入。但不知道为何没有被正常写入,首先怀疑是初始化时附值发生错误,查阅各种资料进行校验,发现没有错误。后来经过综合分析,发现最有可能是n值在search()函数中发生错误,于是我在search()函数中插入打印n 的语句,运行后发现输出的n为0,初始化数据中有11个结点,n应该为11,所以n 在这个地方发生错误,我又在main()主函数中打印出n 的值,n=11,是正确的。所以错误就在search()函数中,也就说是当运行case1,运行到search()函数时,n从11变为0,针对这个错误,我把变量n改为宏定义,因为n 是代表结点个数,不管在哪个函数中它的值都是一样的才对。改完后运行程序,成功! 本设计文件的注释如上,已给出详细的说明,下面仅以文件为单位说明各个函数的作用和特点,在必要的地方给予一定说明。  3.1、guide.h文件 使用条件编译。以下是头文件中语句 /********************************************* *头文件(.h) ********************************************/ #include "stdio.h" #include "conio.h" #include "alloc.h" #define n0 100 #define infi 32767 /*“无穷大*/ int adjmatrix[n0+1][n0+1];     /*邻接矩阵*/ int n=11; struct node             /*表结点*/ { char name[20];       /*下一个表结点与它们之间的距离*/ }place[12]={{"ShiDiGongYuan"},   /*表结点初始化,即写各景点名称*/    {"CangRongGongYu"},    {"YinHuiLou"},    {"TuoHuanGuanChang"},    {"DiBaShiTang"},    {"XiaoYiYuan"},    {"TuShuGuan"},    {"TiYuGuan"},    {"ZhongHuaGuanChang"},    {"ChuangXinLou"},    {"YiFuTuShuGuan"},    {"BoXueLou"}};  void createadj()          /*建立邻接表*/  void dijkstra( int x,int y)     /*dijkstra求最小生树*/  void search()            /*搜索最短路径*/  menu()               /*菜单函数*/          /********************************************* *建立邻接表 ********************************************/ void createadj()  

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值