2016华为软件精英挑战赛:初期算法设计方案及其代码实现

注:此算法存在诸多不严谨的地方,同时也存在bug,最严重的问题是无法解决规模比较大的案例。


一,算法设计步骤

本设计思想主要参考的文献【1】(见文末尾)。

第一步:建图(邻接矩阵):运用官方提供的函数接口实现读取,图的建立自己实现。

1) 有一个特殊情况:根据说明3,如果顶点A到达B有多条边,总是取最小边建图,其他重复的边舍去(因为我们一定不会选择大的边,并且邻接矩阵也无法存储两点间多条边)。

有向图的建立,http://jingyan.baidu.com/article/95c9d20da3e35fec4e75618b.html

 

第二步:进行简单预处理:对图剪枝,缩小图的规模,

1) 从S(起点)出发,深度优先法遍历全图,没有遍历到的节点肯定不会出现在最终路径上,从而形成新的、较小规模的图。

 

第三步:判断S(起点)、V’(中间点集)、T(终点)是否能够获得最短路径?

1) 从S点开始深度优先遍历图(邻接矩阵),判断是否联通了S、V’、T中的每个顶点,如果有点无法通达,即可判断无法获得最短路劲。

2) 判断V’ (中间点集)是否具有出度和入度(出度的终点和入度的起点不得是同一个点),如果满足,即可断言无法获得最短路径。(见说明6)

第三步这个判断可能不严谨!具有联通性和满足入度出度条件一定能求出最短路径吗?

 

第四步:分成三个子问题,分别求取最短路径

预处理,将中间点集形成一个序列比如V1’,V2’,…..,Vn’

1,求S(起点) >V1’的最短路径,接着求取V1’>V2’的最短路径…..,Vn-1’>Vn’的最短路径,最后求取Vn’到T(终点)的最短路径。最终获取沿途路径和此种情况下的而最短路径值dst1

2,接着变换中间点集的顺序V2’,V3’,…..,Vn’,V1’,求S(起点) >V2’的最短路径,接着求取V2’>V3’的最短路径…..,Vn’>V1’的最短路径,最后求取V1’到T(终点)的最短路径。最终获取沿途路径和此种情况下的而最短路径值dst2

3同理求取dst3….dstn,这些“最短路径”中谁最小谁就是真正的最短路径。

注意:每一次求两点之间的最短路径都会遍历全图(所以必须预处理)才能得到此两点的最短路径

 

第五步:关于优化

1)  对Dijkstra算法优化,传统Dijkstra算法时间复杂度为O(V^2),V为顶点个数,据说可以优化到O(V*lg(V))。

2)  优化搜索算法

3)当中间指定点太多时(最多有50个),12的阶乘=4.79亿,50的阶乘=???,V1’,V2’,…..,Vn’的所有序列情况我们不一定要计算完。我个人的方案是随机选取其中三种序列计算最短路径,从而获取最短的一条,这样也能适当得到优秀解(几乎不太可能获得最优解)

 

二,简单算法分析:

关于算法性能O(|V|^2*m!)

其中V为顶点的个数,m为中间点集的个数,

1) m!的形成是因为V1’,V2’,…..,Vn’到Vn’,Vn-1’,…..,V1’有m!种情况。

2) |V|^2是因为Dijkstra算法的时间复杂度。



三,程序的测试以及说明:

以下所有话语,程序等不一定严谨,需要推敲尽管所有的简单测试案例能通过!

测试代码中所用的图(附录中的图有所区别):

                                                        图(1)

 

1,一般情况测试结果:

(见图1)

1)起点,2;终点,6;必过顶点集,4,5(按照这个顺序寻找的最短路径,以下同理)


2)起点,2;终点,7;必过顶点集,4,5


3)起点,2;终点,7;必过顶点集,4,0

 

4)起点,2;终点,7;必过顶点集,0,4(颠倒了3)的遍历顺序)

 

 

 

2,特殊情况测试:

此时所用的图与图1有点区别,三个红圈处的权值不一样,其他都一样


图(2)

本图的特殊情况是:把未来的指定点访问了

比如起点2,终点7,必须经过点3,4,5,

按计划,我们先计算2到顶点3,则可以得到当前最短路径:2->4->3,然而4是未来要计算的指定点,所以程序中在选取下一个起点终点时智能跳过被计算了得指定点!程序的计算结果,符合预期!

 

在上次的基础上再多添加一个必经点0

即起点2,终点7,必须经过点3,4,5,0(遍历的时候是以2-3,3-4,4-5,5-0,0-7,这里再多说一次)

在求2-3时会把未来的点4计算出来,在计算3-5(实际上这是第二次计算)时会把未来的必经点0计算出来:

见证结果的时刻到来了:




3,官方case1案例实测:

该测试案例的实际图效果:



1), 指定点集,specified_node[]={2,3,5,7,11,13,17,19};

//case1必经点,2为起点,19为终点.3,5,7,11,13,17为必经点集,会被随机调换位置.官方指定的最短路径为71,尽然有比71的最短路径存在,但是它出现的概率太小了!.




此案例最短路径是71,结果出现了60.........找了很久,这个程序问题出现在那里也没有发现,即使是现在!



四,参考代码:

(仅仅是一份测试程序,非正式提交程序)

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<windows.h>
#include<vector>
#include<queue>
#include<set>

using namespace std;
#define vertex_nums 20
int dijkstra(vector<vector<int>> &graph, int s_v, int e_v, int *path, int *ban_visit);//求两点间的最短路径算法
bool isConnected(vector<vector<int>> &graph, int *mustpass, int
	mustsize);//检查是否联通所有必经点(包括起点和终点)
bool isValidNode(vector<vector<int>> &graph, int*mustpass, int mustsize);//是否每一个点都是有效的(指的是出入度的合法性)
void myswap(int*specified_node, int rand_posa, int rand_posb)
{
	int tmp = specified_node[rand_posa];
	specified_node[rand_posa] = specified_node[rand_posb];
	specified_node[rand_posb] = tmp;
}
void nextPermutation(int *specified_node, int mustsize){
	int rand_posa = rand() % (mustsize - 2) + 1;
	int rand_posb = rand() % (mustsize - 2) + 1;
	while (rand_posa == rand_posb)
	{
		rand_posa = rand() % (mustsize - 2) + 1;
		rand_posb = rand() % (mustsize - 2) + 1;
	}
	myswap(specified_node, rand_posa, rand_posb);
}
int _tmain(int argc, _TCHAR* argv[])
{
	system("color0A");
	vector<int>row(vertex_nums, -1);
	vector<vector<int>>graph(vertex_nums, row);
	for (int i = 0; i<vertex_nums; i++)
		graph[i][i] = 0;
	//官方提供的简单图case1
	graph[0][13] = 15;
	graph[0][8] = 17;
	graph[0][19] = 1;
	graph[0][4] = 8;
	graph[1][0] = 4;
	graph[2][9] = 19;
	graph[2][15] = 8;
	graph[3][0] = 14;
	graph[3][11] = 12;
	graph[4][1] = 15;
	graph[4][5] = 17;
	graph[5][8] = 18;
	graph[5][9] = 14;
	graph[5][6] = 2;
	graph[6][17] = 4;
	graph[7][13] = 1;
	graph[7][16] = 19;
	graph[8][6] = 1;
	graph[8][12] = 17;
	graph[9][14] = 11;
	graph[10][12] = 1;
	graph[11][7] = 12;
	graph[11][4] = 7;
	graph[12][14] = 5;
	graph[13][17] = 12;
	graph[13][4] = 2;
	graph[14][19] = 9;
	graph[15][10] = 14;
	graph[15][18] = 2;
	graph[16][8] = 1;
	graph[17][9] = 14;
	graph[17][19] = 3;
	graph[17][18] = 10;
	graph[18][15] = 8;
	graph[18][3] = 8;
	graph[19][18] = 12;
	graph[2][3] = 20;
	graph[3][5] = 20;
	graph[5][7] = 20;
	graph[7][11] = 20;
	graph[11][13] = 20;
	graph[17][11] = 20;
	graph[11][19] = 20;
	graph[17][5] = 20;
	graph[5][19] = 20;
	/*
	//官方提供的简单图case0
	graph[0][1]=1;
	graph[0][2]=2;
	graph[0][3]=1;
	graph[2][1]=3;
	graph[2][3]=1;
	graph[3][1]=1;
	graph[3][2]=1;
	*/
	/*
	//自定义简单图
	graph[3][0]=1;
	graph[0][5]=1;
	graph[1][2]=3,graph[2][1]=3;
	graph[1][6]=1,graph[6][1]=1;
	graph[2][3]=5;
	graph[2][4]=1;
	graph[3][5]=3;
	graph[3][6]=1;
	graph[4][5]=1;
	graph[4][3]=1;
	graph[5][7]=1;
	graph[6][7]=3;
	graph[7][3]=3;
	*/
	int specified_node[] = { 2, 3, 5, 7, 11, 13, 17, 19 };//case1必经点,2为起点,19为终点
		//intspecified_node[]={0,3,1,2};//case0的必经点,0为起点,2为终点
	int mustsize = sizeof(specified_node) / sizeof(specified_node[0]);//必经点集的规模
	int count_round = 0;
	int rounds = 100;//循环次数
	int minpath = 999999;
	int count_0 = 0;//统计rounds次循环中,无解的次数
	int count_low = 0;//统计rounds次循环中,比71小的次数
	int count_71 = 0;//统计rounds次循环中,结果为71的次数
	int count_95 = 0;//统计rounds次循环中,结果为95的次数
	int count_99 = 0;//统计rounds次循环中,结果为99的次数
	int count_high = 0;//统计rounds次循环中,比99大的次数
	while (count_round<rounds)
	{
		int record_path[vertex_nums] = { 0 };//记录最短路径过程中的路径
		int dist = 0, shortdist = 0;
		bool flagcon = isConnected(graph, specified_node, mustsize);//从起点起是否连接了必经点
		bool flagnode = isValidNode(graph, specified_node, mustsize);//是否为出入度有效的节点
		if (!flagcon || !flagnode)
		{
			if (!flagcon)
				cout << "起点s无法到达其他所有必经点." << endl;
			else
				cout << "无效图,必经节点不满足出入度条件。" << endl;
			system("pause");
		}
		int ban_visit[vertex_nums] = { 0 };//禁止访问的点
		unsigned result[vertex_nums] = { -1 };//存储访问的节点的结果
		int idx = 0;
		bool isAnswer = true;//无解的标志,true为有解需要继续计算,false则断言无解,不需要再判断了
		int start_vertex = 0, end_vertex = 0;
		int start_pos = 0, end_pos = start_pos + 1;
		for (; end_pos<mustsize&&isAnswer; start_pos++, end_pos++)
		{
			while (start_pos < (mustsize - 1) &&
				ban_visit[specified_node[start_pos]] == 1)//起点一定是一个未被访问过的点
				start_pos++;
			start_vertex = specified_node[start_pos];
			while (end_pos < (mustsize - 1) &&
				ban_visit[specified_node[end_pos]] == 1)//终点一定是一个未被访问过得点
				end_pos++;
			end_vertex = specified_node[end_pos];
			if (start_pos>end_pos)
				isAnswer = false;//无解,退出
			if (isAnswer)
				dist = dijkstra(graph, start_vertex, end_vertex, record_path,
				ban_visit);//在图g中获得start_vertex和end_vertex的最短路径,并且禁止访问ban_visit中的点
			if (dist != -1)
			{
				//记录已经被访问过得点,未来将不得再访问(防止环的产生)
				for (int k = record_path[end_vertex]; record_path[k] != -1; k =
					record_path[k])
					ban_visit[k] = 1;
				ban_visit[start_vertex] = 1;
				//记录遍历结果
				vector<int> tmp;
				for (int k = record_path[end_vertex]; record_path[k] != -1; k =
					record_path[k])
					tmp.push_back(k);
				result[idx++] = start_vertex;
				for (int j = 0; j<tmp.size(); j++)
					result[idx++] = tmp[tmp.size() - 1 - j];
				shortdist += dist;
			}
			else
			{
				isAnswer = false;//无解,退出
				break;
			}
		}
		result[idx++] = end_vertex;
		/*
		if(!isAnswer)//没有解
		{
		cout<<"必过点为: ";
		for(intn=1;n<mustsize-1;n++)
		cout<<specified_node[n]<<"";
		cout<<endl<<"shortestpath,from "<<specified_node[0]<<"to
		"<<specified_node[mustsize-1]<<"isnotexist."<<endl;
		}
		else//有解
		{
		cout<<"shortestpath,from "<<specified_node[0]<<"to"<<
		specified_node[mustsize-1]<<"is:"<<shortdist<<endl;
		cout<<"必过点为: ";
		for(intn=1;n<mustsize-1;n++)
		cout<<specified_node[n]<<"";
		cout<<endl<<"path:";
		for(intk=0;k<idx;k++)
		cout<<result[k]<<"--->";
		cout<<endl;
		}
		*/
		if (shortdist<71)
			count_low++;
		if (shortdist == 71)
			count_71++;
		if (shortdist == 95)
			count_95++;
		if (shortdist == 99)
			count_99++;
		if (shortdist>99)
			count_high++;
		if (!isAnswer)
			count_0++;
		if (minpath>shortdist)
			minpath = shortdist;
		count_round++;
		nextPermutation(specified_node, mustsize);
	}
	cout << "在" << rounds << "次不同随机序列中," << endl;
	cout << "无路径的次数为" << count_0 << endl;
	cout << "结果小于71的次数为" << count_low << endl;
	cout << "结果为71的次数为" << count_71 << endl;
	cout << "结果为95的次数为" << count_95 << endl;
	cout << "结果为99的次数为" << count_99 << endl;
	cout << "结果大于99的次数为" << count_high << endl;
	cout << rounds << "次循环中,最最最最........短路径是: " << minpath <<
		endl;
	system("pause");
	return 0;
}
//最短路径:Dijkstra,dist[j]=min{dist[j],dist[i]+graphy[i][j]}
//graphy:G=(V,E),V:顶点集,E:边集
//e∈ E&e>0表示顶点邻接,权值为e,e=-1表示两顶点不邻接
int dijkstra(vector<vector<int>>&graph, int start_vertex, int end_vertex, int
	*path, int*ban_visit)
{
	int shortpath[vertex_nums] = { -1 };//存储起点到各终点的最短路径//初始化,-1代表max_value
	int visited[vertex_nums] = { 0 };//已访问过的顶点
	int min;
	//初始化,-1代表max_value
	memset(shortpath, -1, sizeof(int)*vertex_nums);
	//初始化起点到自身的最短距离为0
	shortpath[start_vertex] = 0;
	memset(visited, 0, sizeof(int)*vertex_nums);
	memset(path, -1, sizeof(int)*vertex_nums);
	int num = vertex_nums - 1;
	while (num)
	{
		//贪心策略:从访问过的顶点中,找出最短路径,从已知的最短路径开始延伸
			//寻找新的中间点k,依据就是数组中权值最小的那个点的位置(且没被访问过)
			min = -1;
		int k = -1;
		for (int i = 0; i<vertex_nums; i++)
		{
			if (ban_visit[i] != 1 && visited[i] != 1 && shortpath[i] != -1 &&
				(min != -1 && shortpath[i]<min || min == -1))
			{
				min = shortpath[i];
				k = i;
			}
		}
		//终点最短路径已找到或所有顶点最短路径都已找到
		if (end_vertex == k || min == -1)
			break;
		//标记访问过的顶点
		visited[k] = 1;
		num--;
		//dist[j]=min{d[j],dist[i]+graphy[i][j]}
		//更新未访问过邻接顶点的最短路径
		for (int i = 0; i<vertex_nums; i++)
		{
			if (ban_visit[i] != 1 && visited[i] != 1 && graph[k][i] != -1 &&
				(shortpath[i] != -1 && shortpath[i]> min + graph[k][i] ||
				shortpath[i] == -1))
			{
				shortpath[i] = min + graph[k][i];
				path[i] = k;//更新记录前驱顶点,供最后回溯最短路径
			}
		}
	}
	min = shortpath[end_vertex];
	return min;
}
//广度优先检查图是否连接了所有必经点(包括起点和终点)情况
//时间复杂度O(v^2)(且有适当剪枝),v为顶点个数
//连接表的速度是O(v+e),确实,对于稀疏图而言,邻接表更有优势!
bool isConnected(vector<vector<int>> &graph, int *specified_node, int
	mustsize)
{
	int start_vertex = specified_node[0];
	set<int>setting(specified_node, specified_node + mustsize);
	int dfs_visited[vertex_nums] = { 0 };//通过dfs_visited数组来标记这个顶点是否被访问过,0表示未被访问,1表示被访问
		queue<int>que;
	int cnt = mustsize - 1;
	//cout<<"顶点"<<start_vertex<<"已经被检查"<<endl;
	dfs_visited[start_vertex] = 1;//标记顶点start_vertex被访问
	que.push(start_vertex);
	bool linkflag = false;//用于剪枝,判断是否可以停止判断
	while (!que.empty() && !linkflag)
	{
		int k = que.front();
		que.pop();
		//cout<<que.size();
		for (int j = 0; j<vertex_nums&&!linkflag; j++)
		{
			if (graph[k][j]>0 && dfs_visited[j] == 0)
			{
				//cout<<"顶点"<<j<<"已经被检查"<<endl;
				if (setting.find(j) != setting.end())
					cnt--;
				if (cnt == 0)//剪枝
					linkflag = true;
				dfs_visited[j] = 1;//标记为已经被访问过
				que.push(j);//弹出
			}
		}
	}
	return cnt == 0;
}
//时间复杂度O(k*v)(且有适当剪枝),k为必经点的个数,v为顶点个数
bool isValidNode(vector<vector<int>>&graph, int*mustpass, int mustsize)//是否每一节点都是有效的(指的是出入度的合法性)
{
	bool flagrow = true, flagcol = true;
	int cnt = mustsize - 2;
	for (int nsize = 1; nsize<mustsize - 1; nsize++)
	{
		flagrow = true, flagcol = true;
		for (int row = 0; row<vertex_nums&&flagrow; row++)
		{
			if (graph[mustpass[nsize]][row]>0)//判断顶点mustpass[nsize]所在行中是否有出度
				flagrow = false;
		}
		for (int col = 0; col<vertex_nums&&flagcol; col++)
		{
			if (graph[col][mustpass[nsize]]>0)//判断顶点mustpass[nsize]所在列中是否有入度
				flagcol = false;
		}
		if (!flagcol&&!flagrow)
			cnt--;
	}
	return cnt == 0;
}






注:本博文为EbowTang原创,后续可能继续更新本文。如果转载,请务必复制本条信息!

原文地址:http://blog.csdn.net/ebowtang/article/details/51212846

原作者博客:http://blog.csdn.net/ebowtang


参考文献:

【1】黄书力, 胡大裟, 蒋玉明. 经过指定的中间节点集的最短路径算法[J]. 计算机工程与应用, 2015, 51(11):41-46.



  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值