(这个是程序设计的课程任务之一,基本上就是老师给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>
至此,全部任务已完成!