武汉大学 遥感院 数据结构实习

实习内容

在这里插入图片描述

1.CSV格式数据文件的读写

1)CSV文件格式:

cities.csv与routes.csv文件如下:
在这里插入图片描述
其中,第一列为国家名,第二列为城市名,第三列为城市所在纬度,第四列为城市所在经度。
在这里插入图片描述
其中,第一列为出发城市,第二列为目的城市,第三列为交通方式,含飞机、火车、巴士三种,第四列为路程时间,第五列为路程费用,第六列为附加信息。

2)问题分析及数据结构:

针对该问题,首先需要建立世界城市网络模型,并且由于城市间往返交通属性不同,且同意线路可能具有多种方式,因此,可以使用带权有向图的模型来解决。其中,顶点即为城市,边(弧)即为两城市之间的交通线路。

由于城市数量较大,采用邻接矩阵的方法存储会消耗过多的内存,故在此采用邻接表的方式进行存储。

第一个结构体定义了城市信息,并重命名为CITIES,包含国家cNation,cCity,dLongitude, dLatitude。

typedef struct CITIES {
	char cNation[64];
	char cCity[64];
	double dLongitude;
	double dLatitude;
}CITIES;

第二个结构体定义了路径上的信息,并重命名为ROUTES,包含交通方式cTripMode,所需时间dTime,所需费用dCost 以及附加信息InfoType,城市起点以及终点cStartCity与cEndCity。

typedef struct ROUTES {
	char cStartCity[64];
	char cEndCity[64];
	double dTime;
	char cTripMode[16];
	double dCost;
	char InfoType[1024];
}ROUTES;

第三个结构体定义了边节点的信息,并重命名为
ArcNode,包含邻接的顶点nAdjvex,下一个邻接边指针 nextarc,路径信息结构体Information。

typedef struct ArcNode {
	int nAdjvex;
	struct ArcNode* nextArc;
	ROUTES Information;
}ArcNode;

第四个结构体定义了顶点节点(头节点)的信息,并重命名为VNode以及一个顶点向量AdjList[MAX_VERTEX_NUM]。其中包含了顶点信息City,该顶点指向的第一个边节点 firstarc。

typedef struct VNode {
	CITIES City;
	ArcNode* Firstarc;
}AdjList[MAX_VERTEX_NUM];

最后,给出图的定义,命名为ALGraph,其中包含该图的顶点向量vertices,顶点个数nVexnum,边个数nArcnum。

typedef struct {
	AdjList vertices;
	int nVexnum, nArcnum;
	int Kind;
	bool bFinal[MAX_VERTEX_NUM];
}ALGraph;

3)创建城市链表

首先,先建立城市信息数组,由于城市个数不确定,且城市数量较大,采用数组表示法并不方便,而对于STL中的vector模板可较方便的存储未知数目的元素信息,因此,本算法中采取vector存储的方式存储城市信息。

同时注意到,csv文件本质上为逗号分隔值得文件,即两列信息中间以逗号进行分隔,故考虑采用 strtok()函数进行取值。

CITIES* CreateCitiesArray(FILE* Fp) {//该函数用于制作城市表,从city.csv中读取城市信息
	if (SuccessfulOpen(Fp)) {
		cout << "failed to open file for reading" << endl;
		return NULL;
	}
	int nLine = FindLinesInCSV(Fp);
	char* pcLine=NULL;
	char* cFinal = NULL;
	struct CITIES* pCity = new CITIES[nLine];
	char cLine[256] = {0};
	fseek(Fp, 0, 0);
	for (int j = 0; j < nLine; j++) {
		pcLine = fgets(cLine, 256, Fp);
		cFinal = strtok(pcLine, ",");
		while (cFinal != NULL) {
			strcpy(pCity[j].cNation, cFinal);//国家
			cFinal = strtok(NULL, ",");
			strcpy(pCity[j].cCity, cFinal);//首都
			cFinal = strtok(NULL, ",");
			pCity[j].dLatitude = atof(cFinal);//纬度
			cFinal = strtok(NULL, ",");
			pCity[j].dLongitude = atof(cFinal);//经度
			cFinal = strtok(NULL, ",");
		}
	}
	return pCity;
}

建立好城市信息表后,将城市之间的交通信息以带权有向图的方式存入G中。由于route.csv文件中给出的是具体的城市名,不便于在图G中存储信息,故先创建一个函数SearchCity()读取某城市在城市表city中的下标,从而将图中的城市信息与city中的城市信息相对应。

int SearchCitiesSerialNumber(char* Temp, CITIES* pCity, FILE* Fp) {
	fseek(Fp, 0, 0);//将文件指针重新指向文件开头
	int nLine = FindLinesInCSV(Fp);//得到文件中共有几行数据
	for (int i = 0; i < nLine; i++) {
		if (!strcmp(pCity[i].cCity, Temp))return i;
	}
	return -1;
}

2.创建图

此时根据边信息创建网图G。同样,由于routes.csv文件也是逗号分隔值文件,故采用strtok()获取值。

void CreateGraph(ALGraph& G, CITIES* pCity,FILE*pCitiesFile,FILE*pRoutesFile) {
	G.nVexnum = FindLinesInCSV(pCitiesFile);//得到城市的个数即为图顶点的个数
	G.nArcnum = 0;//将图的边的个数初始化为0
	for (int i = 0; i < G.nVexnum; i++) {
		G.vertices[i].City = pCity[i];//将城市的信息传给图
		G.vertices[i].Firstarc = NULL;//将头节点初始化为NULL
	}
	if (SuccessfulOpen(pRoutesFile)) {
		cout << "Filed to open this File" << endl;
		return;
	}
	char* pcRecord = NULL;
	char* pcLine = NULL;
	char cBuffer[1024];//缓冲区域
	int nLine = FindLinesInCSV(pRoutesFile);//得到边的个数
	fseek(pRoutesFile, 0, 0);//将文件指针重新指向文件头
	for (int j = 0; j < nLine; j++) {//依次向图中录入信息
		pcLine = fgets(cBuffer, 1024, pRoutesFile);
		pcRecord = strtok(cBuffer, ",");
		int nStrat, nEnd;
		ArcNode* pArc = new ArcNode();
		pArc->nextArc = NULL;
		char cTemp[1024];
		strcpy(cTemp, pcRecord);
		nStrat = SearchCitiesSerialNumber(cTemp, pCity, pCitiesFile);
		pcRecord = strtok(NULL, ",");
		strcpy(cTemp, pcRecord);
		nEnd = SearchCitiesSerialNumber(cTemp, pCity, pCitiesFile);
		pArc->nAdjvex = nEnd;
		pcRecord = strtok(NULL, ",");
		strcpy(cTemp, pcRecord);
		strcpy(pArc->Information.cTripMode, cTemp);
		pcRecord = strtok(NULL, ",");
		strcpy(cTemp, pcRecord);
		pArc->Information.dTime = atof(cTemp);
		pcRecord = strtok(NULL, ",");
		strcpy(cTemp, pcRecord);
		pArc->Information.dCost = atof(cTemp);
		pcRecord = strtok(NULL, "\n");
		strcpy(cTemp, pcRecord);
		strcpy(pArc->Information.InfoType, cTemp);
		pcRecord = strtok(NULL, "\n");
		if (G.vertices[nStrat].Firstarc == NULL) G.vertices[nStrat].Firstarc = pArc;
		else {//创建邻接表
			ArcNode* pTemp = G.vertices[nStrat].Firstarc;
			while (pTemp->nextArc)
				pTemp = pTemp->nextArc;
			pTemp->nextArc = pArc;
			G.nArcnum++;
		}

	}
	for (int i = 0; i < MAX_VERTEX_NUM; i++) {
		G.bFinal[i] = false;
	}
	return;
}

3.寻找最短路径

1)Dijkstra算法寻找最短路径

首先引入辅助变量D,其每个分量D[i]表示当前所找到的由起点v0到其他顶点vi的最短路径长度。
在本例中,其定义为:

typedef double dShortPath[MAX_VERTEX_NUM];

值得注意的是,D的值是在不断更新并且逼近最终结果的,过程中并不一定等于最短路径。
D[i]的初始状态为从v0到vi弧的权值,如果该两顶点间无弧,则D[i] = ∞。
此时,长度为D[j] = Min{D|vi∈v}的路径就是从 v0出发到vj的长度最短的一条路径,此路径为(v,vj)。下一条长度次短的是从v0到下一个顶点的最短路径所对应的顶点,且这条最短路径长度仅次于从v0 到vj的最短路径长度。
假设该路径次短路径的终点为vk,则该路径只可能为(v0,vk)或者(v0,vj,vk)。
根据以上算法过程可以证明,假设S为已求得从 v0出发的最短路径长度的顶点的集合,则下一条终点为v的次短路径,其路径要么是弧(v0,v),要么是从v0出发只经过S中的若干顶点最后到达v,则该最短路径长度D[j] = Min{D[i]|vi∈v-S},其中D[i]是弧(v,vi)
的权值或Dk和弧(vk,vi)权值之和的较小值。由于需要记录最短路径,故建立一个数组,在每次求得最短路径值时,保存上一个顶点的下标值。若
无上一个顶点,即为起点或不经过该点,则保存为-1。

#define MAX_VERTEX_NUM 199
typedef double dShortPath[MAX_VERTEX_NUM];
int nPath[MAX_VERTEX_NUM];

2)算法实现

由于两个城市间的交通线路权值有多样性包括时间time和费用money,且两个城市间交通方式的多样性,例如飞机plane和火车train均能到达,则必然有最优路径的选择,故创建一个函数GetWeight(),根据用户不同的需求,返回不同的权值。

double GetWeight(ALGraph G, int nStart, int nEnd, char cKind) {
	double dMin = INFINITY*1.0;
	if (cKind == 'T') {
		ArcNode* pT = G.vertices[nStart].Firstarc;
		for (; pT != NULL; pT = pT->nextArc) {
			if (pT->nAdjvex == nEnd) {
				if (pT->Information.dTime < dMin) dMin = pT->Information.dTime;
			}
		}
		return dMin;
	}
	if (cKind == 'M') {
		ArcNode* pM = G.vertices[nStart].Firstarc;
		for (; pM != NULL; pM = pM->nextArc) {
			if (pM->nAdjvex == nEnd) {
				if (pM->Information.dCost < dMin) dMin = pM->Information.dCost;
			}
		}
		return dMin;
	}
}

在此基础之上,进行迪杰斯特拉算法。代码如下:

void ObtainOptimalPath(ALGraph G,int nStart,dShortPath &dSP,char cKind) {
	double dMin;
	int nVex;
	cout << G.vertices[nStart].City.cCity << "-->";
	for (nVex = 0; nVex < G.nVexnum; nVex++) {//初始化各项的值
		G.bFinal[nVex] = false;
		dSP[nVex] = GetWeight(G, nStart, nVex, cKind);
		nPath[nVex] = -1;
	}
	dSP[nStart] = 0;
	G.bFinal[nStart] = true;
	for (int k = 1; k < G.nVexnum; k++) {
		dMin = INFINITY;//当前所知离v0顶点的最近距离
		for (int j = 0; j < G.nVexnum; j++) {
			if (!G.bFinal[j] && dSP[j] < dMin) {//w顶点离v0顶点更近
				nVex = j;
				dMin = dSP[j];
			}
		}
		G.bFinal[nVex] = true;//离v0顶点最近的v的final[v]置为1
		for (int j = 0; j < G.nVexnum; j++) {//更新当前最短路径及距离
			if (!G.bFinal[j] && (dMin + GetWeight(G, nVex, j, cKind) < dSP[j])) {
				dSP[j] = dMin + GetWeight(G, nVex, j, cKind);
				nPath[j] = nVex;//记录到达w路径的上一个顶点v值
			}
		}
	}
	return;
}

4.递归遍历图

递归深度优先遍历图:

简述算法过程

1、任选一顶点作始点 v ,访问该顶点
2、沿深度方向,依次遍历 v 的未访问邻接点——直到本次遍历结束
3、一次遍历完时,若有未访问顶点:任选一个未访问顶点作起始点,GOTO第二步

void DFS_AL(ALGraph &G, int v) {//深度优先搜索
	cout << G.vertices[v].City.cCity;
	if (!G.bFinal[v]) cout << "-->";
	G.bFinal[v] = true;
	ArcNode* p;
	p = G.vertices[v].Firstarc;
	while (p != NULL) { //遍历节点的所有相邻点
		int w = p->nAdjvex;
		if (!G.bFinal[w])
			DFS_AL(G, w);//递归遍历
		p = p->nextArc;
	}
}

输出路径:

void GetArc(ALGraph G, int w1, int w2, ArcNode& Arc) {
	ArcNode* pT = G.vertices[w1].Firstarc;
	for (; pT != NULL; pT = pT->nextArc) {
		if (pT->nAdjvex == w2) {
			Arc = *pT;
			cout << G.vertices[w2].City.cCity<<"--->";
		}
	}
}

5.可视化输出

可视化输出用到html语言

根据htm文件格式,可以格式化输出htm文件,以
可视化的输出最短路径。
由上述分析可知,Path[]中存储的是每个节点在该路径中上一个结点的下标,即倒序存储。而我们需要顺序的输出其路径,故在此采用栈的存储方式,栈底为终点,栈顶为起点,并依次pop()出来。代码如下:

void MapVisualOutPut(char* cStart,char* cDest,CITIES*pCity,FILE*pCitiesFile,ALGraph G,char cKind) {
	FILE* fP = fopen(FILEVISUALOUTPUTPATH, "w+t");
	fprintf(fP, "<!DOCTYPE html><html><head><style type ='text/css'>body, html{width: 100%%; height: 100%%;margin : 0; font-family:'微软雅黑';}#allmap{height:100%%;width:100%%;}#r-result{width:100%%;}</style><script type = 'text/javascript' src = 'http://api.map.baidu.com/api?v=2.0&ak=nSxiPohfziUaCuONe4ViUP2N'></script><title>Shortest path from %s to %s</title></head><body><div id = 'allmap'></div></div></body></html><script type = 'text/javascript'>var map = new BMap.Map('allmap'); var point = new BMap.Point(0,0); map.centerAndZoom(point,2); map.enableScrollWheelZoom(true); ", cStart, cDest);
	int j = 0;
	int nDest = SearchCitiesSerialNumber(cDest, pCity, pCitiesFile);
	int nStart = SearchCitiesSerialNumber(cStart, pCity, pCitiesFile);
	stack<int>qStack;
	while (nPath[nDest]!=-1)
	{
		qStack.push(nDest);
		nDest = nPath[nDest];
	}
	qStack.push(nDest);
	qStack.push(nStart);
	cout << "The shortest path from " << cStart << " to " << cDest << " is :";
	cout << cStart << "--->";
	while (qStack.size() != 1) {
		int w1 = qStack.top();
		qStack.pop();
		int w2 = qStack.top();
		ArcNode Arc;
		GetArc(G, w1, w2, Arc);
		fprintf(fP, "var marker%d = new BMap.Marker(new BMap.Point(%.4f, %.4f));map.addOverlay(marker%d);\n", j, G.vertices[w1].City.dLongitude, G.vertices[w1].City.dLatitude, j);
		fprintf(fP, "var infoWindow%d = new BMap.InfoWindow(\"<p style = 'font-size:14px;'>country: %s<br/>city : %s</p>\");marker%d.addEventListener(\"click\",function(){this.openInfoWindow(infoWindow%d);});var marker%d = new BMap.Marker(new BMap.Point(%.4f, %.4f));map.addOverlay(marker%d);\n", j, G.vertices[w1].City.cNation, G.vertices[w1].City.cCity, j, j, j + 1, G.vertices[w2].City.dLongitude, G.vertices[w2].City.dLatitude, j + 1);
		fprintf(fP, "var infoWindow%d = new BMap.InfoWindow(\"<p style ='font-size:14px;'>country: %s<br/>city : %s</p>\");marker%d.addEventListener(\"click\",function(){this.openInfoWindow(infoWindow%d);});var contentString%.2d = '%s, %s --> %s, %s (%s - %.2f hours - $%f - \"%s\")';var path%d = new BMap.Polyline([new BMap.Point(%.4f, %.4f),new BMap.Point(%.4f, %.4f)], {strokeColor:'#18a45b',strokeWeight:8, strokeOpacity:0.8});map.addOverlay(path%d);path%d.addEventListener(\"click\",function(){alert(contentString%.2d);});", j + 1, G.vertices[w2].City.cNation,G.vertices[w2].City.cCity, j + 1, j + 1, j + 1, G.vertices[w1].City.cCity, G.vertices[w1].City.cNation, G.vertices[w2].City.cCity, G.vertices[w2].City.cNation, Arc.Information.cTripMode, Arc.Information.dTime, Arc.Information.dCost, Arc.Information.InfoType, j + 1, G.vertices[w1].City.dLongitude, G.vertices[w1].City.dLatitude, G.vertices[w2].City.dLongitude, G.vertices[w2].City.dLatitude, j + 1, j + 1, j + 1);
		j++;
	}
	cout << "END" << endl;
	fprintf(fP, "</script>");
}

6.结果

程序运行结果:
在这里插入图片描述
可视化输出结果:
在这里插入图片描述

7.时间复杂度与空间复杂度分析:

1)时间复杂度

理论上,由时间复杂度的计算公式可得
𝑓(𝑛) =𝑛+𝑛+𝑛∗3𝑛+𝑛∗𝑛+𝑛+𝑛+𝑛∗2𝑛∗𝑛
=2𝑛3+4𝑛2+4𝑛
故时间复杂度为𝑇(𝑛)=𝑂(𝑛3)。

2)空间复杂度

空间复杂度:在main函数中创建了城市表pCity,网图Graph,辅助数组D,起点名数组nStart,终点名数组nEnd,路径数组 Path,以及其他函数中运行时的temp[1024]和 buf[1024]等。

	int size = sizeof(pCity) + sizeof(Graph) + sizeof(nStart) + sizeof(nEnd) + sizeof(cStart) + sizeof(cDes) + sizeof(dShortPath) + sizeof(char) * 1024 * 2 + sizeof(char) * 1024 * 2;
	cout << endl << endl << "Using space :" << size << endl;
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值