旅游景点咨询系统的设计与实现(最短路径算法及应用)

(这个是程序设计的课程任务之一,基本上就是老师给PPT,给出基本的数据结构和相应的关键算法,然后学生想办法去实现,下面是我所实现的代码)

基本任务是:

1、创建一个有向图表示的黄山旅游景点的导游图。

2、输入两个景点名,就可以得到其最短路径,如果两者无路径可通,就得出“两景点不可达的信息”

为了实现这些任务,我们需要用到图这一数据结构类型,我所用到的是图--邻接矩阵这一形式。代码如下,首先是结构体的定义。

typedef struct {//创建带有邻接矩阵的图
    VerTexType Vertex[MAXSIZE];    //顶点名称
    MatrixType Matrix[MAXSIZE][MAXSIZE];   //邻接矩阵
    int EdgeNum, VexNum;  //边的个数和顶点的个数
}MGraph;

 然后,我们就需要对图进行一个初始化,包括输入各项结点的名称和结点权值来初始化一个邻接矩阵,代码如下。

bool InitMGraph(MGraph &M){
    cout<<"\n请输入顶点个数:"; //初始化顶点个数
    cin>>M.VexNum;
    cout<<"请输入边的个数:";   //初始化边个数
    cin>>M.EdgeNum;
    cout<<"请输入顶点名称:"<<endl; //初始化顶点名称
    for (int i=0;i<M.VexNum;i++)
        cin>>M.Vertex[i];
    for (int i=0;i<M.VexNum;i++)
    {
        for (int j=0;j<M.VexNum;j++)
            M.Matrix[i][j]=MaxInt; //将初始边权置为最大值
    }
    cout<<"请输入顶点与边权:"<<endl; //初始化每两个顶点的边权
    for (int k=0;k<M.EdgeNum;k++)
    {
        VerTexType v1,v2;
        MatrixType w;
        cin>>v1>>v2>>w;
        int i=LocateVertex(M,v1);
        int j=LocateVertex(M,v2);
        M.Matrix[i][j]=w;
        M.Matrix[j][i]=M.Matrix[i][j];//无向图
    }
    return true;
}

 其中输入顶点个数和边数以及顶点名称,是对图的初始化,顶点和边权则是对邻接矩阵进行一个初始化,其中需要用到LocatVertex这一函数来遍历图找出对应名称的结点对应数组下标,并将权值赋给对应矩阵。函数代码如下:

int LocateVertex(MGraph M, VerTexType v){//遍历图,返回该名称在图中的位置
    for (int i =0;i<M.VexNum;i++){
        if(v == M.Vertex[i]) {
            return i;
        }
    }
    return -1;
}

至此,邻接矩阵形式的图的构成结束,任务1创建一个有向图表示的黄山旅游景点的导游图已经完成。接下来为了完成任务2,需要用到最短路径相关算法,我这里用到的是Floyd算法,Floyd算法是一种多源最短路径算法,用于计算任意两点之间的最短路径。该算法核心思想为动态规划,分多个阶段解决问题。若图G有n个顶点(V1~Vn),求图中每一对顶点之间的最短路径分n个阶段:0)初始化,在没有其它顶点中转的情况下,求得各顶点间的最短路径。

1)如果在各顶点间增加V1作为中转点,求得各顶点间新的最短路径。

2)再增加V2作为中转点,求得各顶点间新的最短路径。

……

n)最后增加Vn作为中转点,求得各顶点间最终的最短路径。

算法实现代码如下:

bool Floyd(MGraph M, VerTexType v1,VerTexType v2){
    MatrixType dis[MAXSIZE][MAXSIZE];
    MatrixType path[MAXSIZE][MAXSIZE];
    for (int i=0;i<M.VexNum;i++)
    {
        for (int j=0;j<M.VexNum;j++){
            dis[i][j]=M.Matrix[i][j]; //将图边权赋给距离矩阵
            path[i][j] = -1; //初始化path数组,置-1代表两点之间不可达或可直达
        }
    }
    int v;
    for(v=0;v<M.VexNum;v++){                      //遍历整个图,完成距离矩阵和path矩阵
        for(int u = 0;u<M.VexNum;u++){
            for (int w = 0; w <M.VexNum ; w++) {
                if (v!=u&&v!=w&&w!=u) {
                    if (dis[u][w] > (dis[u][v] + dis[v][w])) {  //从u经v到w的一条路径更短
                        dis[u][w] = dis[u][v] + dis[v][w];
                        path[u][w] = v;
                    }
                }
            }
        }
    }
    int i=LocateVertex(M,v1);//计算所给景点名在图的邻接矩阵中的位置
    int j=LocateVertex(M,v2);
    SqStack S;
    InitSqStack(S);
    if(i==-1 || j==-1){//判断景点名是否合法
        cout << "请输入正确的景点名称" << endl;
    }
    else if(i==j){
        cout << "请不要输入两个相同结点" << endl;
    }
    else{//输出最短路径和其对应权值
        cout << "最短路径的权值为: " << dis[i][j] << endl;
        Push(S,M.Vertex[j]);//尾结点入栈
        while(path[i][j]!=-1){
            Push(S,M.Vertex[path[i][j]]);//递归计算中转结点并将其入栈
            j = path[i][j];
        }
        Push(S,M.Vertex[i]);//头结点出栈
        cout << "最短路径为:" << endl;
        VerTexType x;
        while (S.top != -1){
            Pop(S,x);//弹出栈中所有元素,完成逆序输出最短路径
        }
    }
    return true;
}

至此,全部任务已经完成,为了方便调试和观察,我还写了一个用于打印邻接矩阵的函数。该系统全部代码如下

/* 用于输入
 * 顶点个数:13
 * 边的个数:18
 * 景点名称:
 * 玉屏索道入口 玉屏索道出口 天都峰 迎客松 排云溪站 探海亭 排云亭 光明顶 白鹤岭 始信峰 松谷庵站 云谷索道入口 云谷索道出口
 * 用于输入的边和对应权值:
玉屏索道入口 玉屏索道出口 6
玉屏索道出口 天都峰 6
玉屏索道出口 迎客松 1
天都峰 迎客松 2
迎客松 排云溪站 12
排云溪站 探海亭 1
探海亭 排云亭 2
排云溪站 排云亭 2
排云亭 光明顶 2
光明顶 白鹤岭 1
排云亭 白鹤岭 2
排云亭 始信峰 2
排云亭 松谷庵站 9
松谷庵站 始信峰 9
白鹤岭 云谷索道入口 1
云谷索道入口 始信峰 2
始信峰 云谷索道入口 2
云谷索道入口 云谷索道出口 5
 */
#include <iostream>
#include <string>
#define MAXSIZE 100
#define MaxInt 32767
#define VerTexType string
#define MatrixType int

using namespace std;

typedef struct {//创建带有邻接矩阵的图
    VerTexType Vertex[MAXSIZE];    //顶点名称
    MatrixType Matrix[MAXSIZE][MAXSIZE];   //邻接矩阵
    int EdgeNum, VexNum;  //边的个数和顶点的个数
}MGraph;

typedef struct {//创建一个顺序栈用于存储并逆向输出读取到的最短路径
    VerTexType data [MAXSIZE];//静态数组存放栈中元素
    int top;   //栈顶指针
}SqStack;//定义顺序栈结构体

//初始化顺序栈
void InitSqStack(SqStack &S){
    S.top = -1; //初始化栈顶指针
}

//新元素入栈操作
bool Push(SqStack &S, VerTexType x){
    if(S.top == MAXSIZE-1)//栈满,报错
        return false;
    //由于是先进性top指针+1再进行操作,所以等价于++i操作
    //可以用S.data[++S.top] = x; 这个操作替换下面两行代码
    S.top = S.top + 1;//栈顶指针加1
    S.data[S.top] = x;//新元素入栈
    return true;
}

//出栈操作
bool Pop(SqStack &S, VerTexType x){
    if(S.top == -1)//栈空,无元素可弹出,报错
        return false;
    x = S.data[S.top];//栈顶元素先出栈
    cout << "->" << x << endl;
    S.top = S.top - 1;//栈顶指针减一
    return true;
}

int LocateVertex(MGraph M, VerTexType v){//遍历图,返回该名称在图中的位置
    for (int i =0;i<M.VexNum;i++){
        if(v == M.Vertex[i]) {
            return i;
        }
    }
    return -1;
}

bool InitMGraph(MGraph &M){
    cout<<"\n请输入顶点个数:"; //初始化顶点个数
    cin>>M.VexNum;
    cout<<"请输入边的个数:";   //初始化边个数
    cin>>M.EdgeNum;
    cout<<"请输入顶点名称:"<<endl; //初始化顶点名称
    for (int i=0;i<M.VexNum;i++)
        cin>>M.Vertex[i];
    for (int i=0;i<M.VexNum;i++)
    {
        for (int j=0;j<M.VexNum;j++)
            M.Matrix[i][j]=MaxInt; //将初始边权置为最大值
    }
    cout<<"请输入顶点与边权:"<<endl; //初始化每两个顶点的边权
    for (int k=0;k<M.EdgeNum;k++)
    {
        VerTexType v1,v2;
        MatrixType w;
        cin>>v1>>v2>>w;
        int i=LocateVertex(M,v1);
        int j=LocateVertex(M,v2);
        M.Matrix[i][j]=w;
        M.Matrix[j][i]=M.Matrix[i][j];//无向图
    }
    return true;
}

bool Floyd(MGraph M, VerTexType v1,VerTexType v2){
    MatrixType dis[MAXSIZE][MAXSIZE];
    MatrixType path[MAXSIZE][MAXSIZE];
    for (int i=0;i<M.VexNum;i++)
    {
        for (int j=0;j<M.VexNum;j++){
            dis[i][j]=M.Matrix[i][j]; //将图边权赋给距离矩阵
            path[i][j] = -1; //初始化path数组,置-1代表两点之间不可达或可直达
        }
    }
    int v;
    for(v=0;v<M.VexNum;v++){                      //遍历整个图,完成距离矩阵和path矩阵
        for(int u = 0;u<M.VexNum;u++){
            for (int w = 0; w <M.VexNum ; w++) {
                if (v!=u&&v!=w&&w!=u) {
                    if (dis[u][w] > (dis[u][v] + dis[v][w])) {  //从u经v到w的一条路径更短
                        dis[u][w] = dis[u][v] + dis[v][w];
                        path[u][w] = v;
                    }
                }
            }
        }
    }
    int i=LocateVertex(M,v1);//计算所给景点名在图的邻接矩阵中的位置
    int j=LocateVertex(M,v2);
    SqStack S;
    InitSqStack(S);
    if(i==-1 || j==-1){//判断景点名是否合法
        cout << "请输入正确的景点名称" << endl;
    }
    else if(i==j){
        cout << "请不要输入两个相同结点" << endl;
    }
    else{//输出最短路径和其对应权值
        cout << "最短路径的权值为: " << dis[i][j] << endl;
        Push(S,M.Vertex[j]);//尾结点入栈
        while(path[i][j]!=-1){
            Push(S,M.Vertex[path[i][j]]);//递归计算中转结点并将其入栈
            j = path[i][j];
        }
        Push(S,M.Vertex[i]);//头结点出栈
        cout << "最短路径为:" << endl;
        VerTexType x;
        while (S.top != -1){
            Pop(S,x);//弹出栈中所有元素,完成逆序输出最短路径
        }
    }
    return true;
}

bool PrintMGraph(MGraph &M) {    //打印无向邻接矩阵
    cout << "----------------------------------------------------------------------------------------------" << endl;
    cout << "邻接矩阵展示:" << endl;
    for (int i = 0; i < M.VexNum; i++) {
        cout << M.Vertex[i] << ":";          //输出矩阵行名
        for (int j = 0; j < M.VexNum; j++) {      //循环输出各点与其他点的边权,0为无边
            if (M.Matrix[i][j] != MaxInt) cout << M.Matrix[i][j] << "\t";
            else cout << "0" << "\t";
        }
        cout << endl;
    }
    cout << "----------------------------------------------------------------------------------------------" << endl;
    return true;
}

int main() {
    MGraph M;
    InitMGraph(M);
    PrintMGraph(M);
    VerTexType v1;
    VerTexType v2;
    cout<<"\n请输入两个景点名,我将给出最短路径以及最短路径的长度" << endl;
    cin>>v1>>v2;
    Floyd(M,v1,v2);
    return 0;
}

同时由于老师要求,还需要通过ArGis来实现web端的地图最短路径分析(大部分代码老师已经给出,只需要自己操作ArGis来发布地图,并拿到发布地图数据即可)完整代码如下

<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>Arcgis API for JS实现最短路径</title>
    <style>
        /*设置网页样式*/
        html, body, #viewDiv {
            padding: 0;
            margin: 0;
            height: 100%;
            width: 100%;
        }

        /*设置按钮框样式*/
        #panelDiv {
            position: absolute;
            top: 30px;
            right: 30px;
            z-index: 50;
            background-color: white;
            border: 1px solid gray;
            padding: 10px;
            max-width: 200px;
        }

        /*设置按钮样式*/
        #panelDiv button {
            margin: 5px;
            padding: 5px;
            display: block;
        }
    </style>
    
    <!--https://js.arcgis.com/4.26/为API的地址  -->
    <!--https://js.arcgis.com/4.26/esri/themes/light/main.css 为样式的地址  -->
    <link rel="stylesheet" href="https://js.arcgis.com/4.26/esri/themes/light/main.css">
    <script src="https://js.arcgis.com/4.26/"></script>
    <div id="panelDiv">
        <button id="startBtn">请设置路径起点</button>
        <button id="endBtn">请设置路径终点</button>
        <button id="clearBtn">清除路径</button>
    </div>
    <script>

        //设置标记
        first_click = false
        sec_click = false
        clear_click = true

        require([
            "esri/Map",//实例地图组件
            "esri/views/MapView",
            "esri/Graphic",
            "esri/rest/route",
            "esri/rest/support/RouteParameters",
            "esri/rest/support/FeatureSet",
            "esri/layers/MapImageLayer",
            "esri/layers/GraphicsLayer",


        ], function(
            Map,
            MapView,
            Graphic,
            route,
            RouteParameters,
            FeatureSet,
            MapImageLayer,
            GraphicsLayer,
            ) {

            // ————————————————————加载图层,GraphicsLayer是一种客户端图层(根据坐标以及形状生成相应的图形然后在地图显示出来),为了把我们的图层加载进去,看起来更方便——————————————————
            const routeLayer = new GraphicsLayer();

            // 我们自己的图层,这个url可以在我们发布的服务中点击服务的图像查看(把后面的?f=jsapi删掉),还可以在服务-功能-底图中复制REST URL————————————————————————————————
            const layer = new MapImageLayer({
                url: "http://zcz:6080/arcgis/rest/services/TEXT/MapServer",
            })

            // 创建map,创建一个地图,为了一会在地图容器中显示出来,basemap选择底图是什么样式的

            const mymap = new Map({
                // 可选择的属性有 arcgis-navigation,topo,streets,satellite,hybrid,dark-gray,gray,national-geographic,
                // oceans,osm,terrain,dark-gray-vector,gray-vector,streets-vector,streets-night-vector,
                // streets-navigation-vector,topo-vector,streets-relief-vector
                basemap: "satellite",  //Basemap layer service
                layers: [routeLayer, layer]
            });

            // 创建地图容器,为了在网页中能显示我们的地图 视图有两种类型:MapView和SceneView。MapView以2D方式呈现地图及其图层。SceneView将这些元素呈现为3D。View是MapView和SceneView的基类,没有构造函数
            const view = new MapView({
                container: "viewDiv",
                map: mymap,
                // center: [-118.24532,34.05398], //Longitude, latitude
                center: [117.19181 , 31.77169 ], //Longitude, latitude
                zoom: 15 //缩放层级
            });

            // 这里是我们自己发布的arcgis Server 中的路网
            const routeUrl = " http://zcz:6080/arcgis/rest/services/TEXT/NAServer/Route";

            
            // 监听button,设置点击按钮并添加监听事件
            btn_start = document.getElementById("startBtn")
            btn_end = document.getElementById("endBtn")
            btn_clear = document.getElementById("clearBtn")
            btn_start.addEventListener("click", function(event) {
                first_click = true
            })

            btn_end.addEventListener("click", function(event) {
                sec_click = true
            })

            btn_clear.addEventListener("click", function(event) {
                view.graphics.removeAll();
                first_click = false
                sec_click = false
                clear_click = true
            })

            // 在单击的位置搜索图形。视图事件可以用作屏幕位置
            // 在实例上注册一个事件处理程序。调用此方法将事件与侦听器挂钩
            view.on("click", function(event){
                if (view.graphics.length === 0 && first_click === true && clear_click === true) {
                    addGraphic("origin", event.mapPoint);
                } else if(view.graphics.length === 1 && sec_click === true && clear_click === true) {
                    addGraphic("destination", event.mapPoint);

                    getRoute(); // Call the route service
                    // 初始化门
                    first_click = false
                    sec_click = false
                    clear_click = false
                }

            });

            // ——————————————————————这个代码块写的是添加一个点要素的功能,可以设置样式大小颜色等——————————
            function addGraphic(type, point) {
                const graphic = new Graphic({
                    symbol: {                           //symbol的实例是唯一的不可变的, 用于确保对象的属性不重复/要素符号化信息,点线面的颜色大小形状等控制
                        type: "simple-marker",
                        color: (type === "origin") ? "white" : "black",
                        size: "8px"
                    },
                    geometry: point                     //地理信息
                });
                // 允许直接向视图中的默认图形添加图形
                view.graphics.add(graphic);
            }

            // ————————这个代码块写的是获取路径功能,获取路径要素的信息并转成Array,然后把我们的路网信息routeUrl和路径要素信息输入到arcgis API中的solve函数中,获取最短路径
            function getRoute() {
                const routeParams = new RouteParameters({
                    stops: new FeatureSet({
                        features: view.graphics.toArray()
                    }),
                    returnDirections: false
                });
                route.solve(routeUrl, routeParams)
                    .then(
                        function(data) {
                            data.routeResults.forEach(
                                function(result) {
                                    result.route.symbol = {
                                        type: "simple-line",
                                        color: [5, 150, 255],
                                        width: 3
                                    };
                                    view.graphics.add(result.route);
                                }
                            );
                        }
                    )

                    //如果遇到错误的话,通过console.log(error)打印错误
                        .catch(function(error){
                        console.log(error);
                    })
            }
        });
    </script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>

至此,全部任务已完成!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值