2016华为软件精英挑战赛:高级案例---贪心策略(二)

一,算法设计思想:

实际上本算法是对期初算法思想的优化,主要的优化就在于我们不在盲目的寻找两个必经点的路径,而是总是寻找距离最近的必经点作为新起点去寻找下一个最近的必经点.......然而这样做还不够呢!


1,基于贪心思想的算法

在未把所有必经点访问完之前,终点是禁止访问的。

1)已起点作为“当前原点”,用Dijkstra算法将“当前原点”作为起点,找到起点距离其他未访问的必经点的距离,然后从小到大进行排序,显然我们应该总是寻找距离“当前原点”最近的必经点建立连接,显然选择比较远的必经点作为下一个“当前原点”作为起点很不明智。


2)如此往返,总是以“当前原点”最为起点寻找距离其他未被访问过的必经点的距离,下一个最短必经点最为新起点继续寻找下去


3)如果所有必经点都访问完了,接着就是连接终点的最后时刻!


a)当然前面的算法只是理想的状态,如果“当前原点”无法连接到未被访问的最短必经点呢?这显然有可能。当然,聪明的我们显然就就选择第二短的必经点作为连接点撒!


b)如果第二短,第三短.....均无法连接呢?(这显然也有可能)回退!回退到“当前原点(称呼他为,当前原点吧)”的上一个“当前原点(称呼他为,先前原点吧)”,此时不再选择这个不能建立连接的当前原点,转而去选择先前节点距离当前节点后面的第二短,第三.......短的必经点。





2,算法的优化


1)对Dijkstra算法的优化,因为整个算法的严重依赖该算法,他的算法时间复杂度严重影响找到有效路径的快慢!

2)如果找到一条有效路径后,我们将按照上述规则回退重新寻找路径,并记录最小权路径,如果在重新寻找路径的过程中(在完整的有效路径出来之前)发现大于了先前的有效路径,则直接回退,继续用回退法则寻找下一条有效路径!

3)如果“当前原点”无法到达余下未访问必经点,则按照回退原则进行回退处理!



二,算法分析

实践发现,即使总是在按照上述行为寻找必经点之间的最短路径,这种贪心搜索也很难找到最优解。

本质算该算法是一种贪心算法,实质上是优化的暴力搜索。在比赛中实际的操作方式就是限制程序运行时间为9800ms,如果算法没错,在这段时间内已经找到解,只是权值的很可能不是最优解。



三,完整参考代码:

route.h的实现

#ifndef __ROUTE_H__
#define __ROUTE_H__
#include 
#include 
#include  
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
void search_route(char *graph[5000], int edge_num, char *condition);

///updata 2016/04/03
/*以下为自定义函数****/
namespace cf
{
	#define MAX_VERTEX_NUMS  600//最大顶点数目
	#define MAX_WEIGHT  -1//用-1代表最大边权值
	#define MAX_MUST_PASS 50
	class CodeCraft
	{
	public:
		CodeCraft(int edge_num){
			m_vertexNums = 0;
			m_mustSize = 0;
			m_edgeNums = edge_num;
			m_pathSize = 0;
			m_minCost = 99999;
			m_countAns=0;//统计获得了多少条有效路径
			flag_stop = false;
			isbadgraph = false;
			pMinPath = new int[MAX_VERTEX_NUMS];
			for (int i = 0; i < MAX_MUST_PASS; i++)
				pMinPath[i] = 0;

			pMustPass = new int[MAX_MUST_PASS];
			for (int i = 0; i < MAX_MUST_PASS; i++)
				pMustPass[i] = 0;
		};
		~CodeCraft(){
			delete[] pMinPath;
			pMinPath = NULL;
			delete[] pMustPass;
			pMustPass = NULL;
		};
		//建立图模型,邻接矩阵
		void build_graph(char ** const topo, const int edge_num);
		void edge_to_num(char *one_edge, int *num);
		void demand_to_num(char * const demand);
	
		//两种策略
		void senior_solution();//较大时的解决方案:寻找近似解
		void primary_solution();//规模较小时的解决方案:寻找最优解

		//核心函数:深度优先暴力穷举
		void dfs_best_paths(int pre_cost, int start,  int *visited);
		//核心函数:优化的暴力穷举
		void force_valid_paths(int curStart, int *result, int idx, vector visited_ban, int preCost);

		//核心函数的辅助函数
		int next_adj_vertex(int curVex, int nextVex, bool flagfirst);
		int get_unvisited_size(vector &visited_ban);
		void get_new_path(int *pResultPath, int path_size, int curCost);
		int dijkstra(int start_must, int *path, vector &visited_ban, int *shortcost);
		//当前源点是否可达其他未访问的必经点,即shortcost中的最短路径值是有效的。
		bool is_valid_connect(vector &visited_ban, int *shortcost);
		void map_must_vertex();
		int unconnected_size();
	public:
		int m_startVex;//起点
		int m_endVex;//终点
		int m_vertexNums;//顶点个数,
		int m_edgeNums;//边的条数
		int m_mustSize;//必经点个数
		int m_pathSize;//结果路径的长度
		int *pMinPath;//存储最小路径
		int m_minCost;//最优路径的权值
		int m_countAns;//统计获得了多少条有效路径
		bool flag_stop;//终止标志
		int *pMustPass;//存放必经点
		map mapping;
		bool isbadgraph;
	};
}

#endif

route.cpp的实现

#include "stdafx.h"

#include "route.h"
#include "io.h"

using namespace cf;
/*******************************************************/
//Update:日期2016/04/10,时间10::15
//调整内容:针对14,,15的特点调整策略
/*******************************************************/
class Vertex  //边的自定义,不用表示起点和终点
{
public:
	Vertex()
	{
		LinkID = -1;//没有相连的索引号
		Cost = MAX_WEIGHT;
	}
	int LinkID;
	int Cost;
};

Vertex graph_matrix[MAX_VERTEX_NUMS][MAX_VERTEX_NUMS];//图数据,注意他是个全局变量


struct node
{
	friend bool operator< (node n1, node n2)
	{
		return n1.priority_cost > n2.priority_cost;
	}
	int vexFrom;
	int vexTo;
	int priority_cost;//优先级
};

clock_t start_time, finish_time;


//你要完成的功能总入口
void search_route(char *topo[5000], int edge_num, char *demand)
{
	start_time = clock();
	//一,建立图并获取基本信息
	CodeCraft graph(edge_num);//每执行一个函数将获取对应的信息,放到CodeCraft类对象的变量中
	graph.demand_to_num(demand);
	graph.build_graph(topo, edge_num);
	graph.map_must_vertex();//执行映射
	int  ucsize = graph.unconnected_size();
	if (ucsize > 50)
		graph.isbadgraph = true;
	//cout << ucsize << endl;
	//选择合适的方案解决问题
	if (graph.m_vertexNums < 51 )
		graph.primary_solution();//规模较小时的解决方案:寻找最优解
	else
		graph.senior_solution();//较大时的解决方案:寻找近似解
}

void CodeCraft::primary_solution()//规模较小时的解决方案:寻找最优解
{
	int visited[MAX_VERTEX_NUMS] = { 0 }; //已访问过的顶点 
	int cur_cost = 0;
	dfs_best_paths(cur_cost, this->m_startVex, visited);
	
	//记录答案
	for (int i = 0; i < this->m_pathSize; i++)
		record_result(pMinPath[i]);
}


void CodeCraft::senior_solution()//较大时的解决方案:寻找近似解
{
	int result[MAX_VERTEX_NUMS] = { 0 };
	vector visited_ban(MAX_VERTEX_NUMS, 0);
	visited_ban[this->m_endVex] = 1;//只有访问终点时才重新开放访问权限
	int path_size = 0;
	int cur_cost = 0;
	force_valid_paths(this->m_startVex, result, path_size, visited_ban, cur_cost);
	//记录答案
	for (int m = 1; m < m_pathSize; m++)
		record_result(graph_matrix[pMinPath[m - 1]][pMinPath[m]].LinkID);
}


//妈了个蛋,暴力出奇迹
void CodeCraft::dfs_best_paths(int pre_cost, int start,  int *visited)
{
	int i = 0;
	static int path_size = 0;
	static int cur_cost = 0;//当前路径代价
	static int path_count = 0;//当前符合要求的路径条数
	static int path_stack[2000] = { 0 };

	visited[start] = 1;
	if (start == this->m_endVex){//如果是终点
		i = 0;
		bool flag = true;
		while (i < m_mustSize )//检查是否全部必经点都访问过
		{
			if (visited[this->pMustPass[i]] != 1)
			{
				flag = false;//没有经过所有必经点
				break;
			}
			i++;
		}
		if (flag && this->m_minCost > cur_cost)//必经点全部都访问过,并且当前路径权值更小,就记录路径
		{
			this->get_new_path(path_stack, path_size, cur_cost);//更新最短路径
			path_count++;
		}
	}
	else{
		int nextVex = 0,curVex = start;
		bool flagfirst = true;
		while (nextVex != -1)//如果存在相邻点
		{
			nextVex = next_adj_vertex(curVex, nextVex, flagfirst);//curVex相邻的下一个顶点
			if (nextVex != -1 && visited[nextVex] == 0)//如果该相邻的顶点没有被访问过
			{
				path_stack[path_size++] = graph_matrix[curVex][nextVex].LinkID;
				cur_cost += graph_matrix[curVex][nextVex].Cost;
				dfs_best_paths(graph_matrix[curVex][nextVex].Cost, nextVex, visited);
			}
			flagfirst = false;
		}
	}
	path_size--;
	cur_cost -= pre_cost;
	visited[start] = 0;
}

//在邻接矩阵从curVex行,第nextVex列开始找curVex的相邻点,即graph_matrix[curVex][i].Cost != -1的点
int CodeCraft::next_adj_vertex(int curVex, int nextVex, bool flagfirst)
{
	int i = nextVex;
	if (!flagfirst)
		i++;
	for (; i < m_vertexNums; i++)
	{
		if (graph_matrix[curVex][i].Cost != -1)
			return i;
	}
	return -1;
}

int CodeCraft::get_unvisited_size( vector &visited_ban)
{
	int unvisited_size = 0;
	for (int i = 0; i < this->m_mustSize; i++)
	{
		if (visited_ban[this->pMustPass[i]] == 0)//如果必经点没有被访问过
			unvisited_size++;
	}
	return unvisited_size;
}

//当前源点是否可达其他未访问的必经点,即shortcost中的最短路径值是有效的。
bool CodeCraft::is_valid_connect( vector &visited_ban, int *shortcost)
{
	for (int i = 0; i < this->m_mustSize; i++)
	{//如果必经点没有被访问过却无法得到路径则返回false
		if (visited_ban[this->pMustPass[i]] == 0 && shortcost[this->pMustPass[i]] == -1)
				return false;
	}
	return true;
}

//idx始终记录当前result中的有效路径长度
void CodeCraft::force_valid_paths(int curStart, int *result, int idx, vector visited_ban, int preCost)
{
	if (flag_stop)
		return;
	//计算出一次结果,距离当前起始点的其他未访问必经点的远近关系
	int path[MAX_VERTEX_NUMS] = { 0 };//记录每一次迪杰特斯拉起点到其他点的路径
	int shortcost[MAX_VERTEX_NUMS] = { 0 }; //存储起点到各终点的最短路径权值
	//保留先前的信息
	int pre_idx = idx;
	vector pre_visited_ban = visited_ban;
	//开始计算当前节点到其他未访问必经节点的距离
	dijkstra(curStart, path, visited_ban, shortcost);

	bool reach_flag = is_valid_connect(visited_ban, path);//查看当前源点curStart是否能连接其他所有未访问必经点
	int unvisited_size = get_unvisited_size(visited_ban);//未访问的必经点个数

	if (unvisited_size == 0)//所有必经点都已经连起来了,最后再连接终点即可(有可能连不上)
	{
		visited_ban[this->m_endVex] = 0;//访问终点时才重新开放
		dijkstra(curStart, path, visited_ban, shortcost);//计算curStart在禁止点集下到目的点最短路径,最后一次建立连接
		int curCost = shortcost[this->m_endVex];//curStart到endVex的最短路径值
		if (curCost == -1)
			return;
		//记录路径
		vector  tmppath;//记录遍历结果
		for (int i = this->m_endVex; path[i] != -1; i = path[i])
			tmppath.push_back(i);
		//倒序记录,一条路径是7(终点),5,3,实际上应该是3,5,7
		result[idx++] = curStart;//idx是result的size
		for (int j = 0; j < tmppath.size(); j++)
			result[idx++] = tmppath[tmppath.size() - 1 - j];

		if ((curCost + preCost) < this->m_minCost)//如果当前路径更优就更新路径
			get_new_path(result, idx, curCost + preCost);
		
		finish_time = clock();
		double duration = double((finish_time - start_time)) / CLOCKS_PER_SEC;
		if (duration > 9.732)
			flag_stop = true;
		/*
		//输出路径
		for (int i = 0; i < idx; i++)
			cout << result[i] << "  ";
			*/
		cout << endl << curCost + preCost << "   " << endl;
		m_countAns++;//统计获得了多少条有效路径
		
	}
	else if (reach_flag && unvisited_size > 0)
	{
		//对找到的未访问的必经点的距离大小进行排序,准备一个一个递归深入
		int unvisited_nums = unvisited_size;
		priority_queue pri_que;
		node *pnode = new node[unvisited_nums];
		int nodesize = 0;
		for (int i = 0; i < this->m_mustSize; i++)
		{
			if (visited_ban[this->pMustPass[i]] == 0)//如果必经点没有被访问过
			{
				pnode[nodesize].priority_cost = shortcost[this->pMustPass[i]];//权值就是优先级
				pnode[nodesize].vexTo = this->pMustPass[i];//未被访问的有效必经点
				pnode[nodesize].vexFrom = curStart;//当前源点
				nodesize++;
			}
		}
		int i1 = 0;
		for (; i1 < unvisited_nums; i1++)
			pri_que.push(pnode[i1]);
		//当前源点curStart距离其他未访问必经点的远近优先形成访问顺序
		int i2 = 0;
		if (isbadgraph )
			i2 = unvisited_nums / 5;
		for (; i2 < unvisited_nums; i2++)
		{
			//一,获取路径以及权值等信息,暂时未完成
			int nextStart = pri_que.top().vexTo;//这个点就是距离curStart最近的“未访问的必经点”
			pri_que.pop();
			int curCost = shortcost[nextStart];
			
			bool flagre = false;
			for (int k = path[nextStart]; path[k] != -1; k = path[k])
			{
				if ( mapping.find(k) != mapping.end() && visited_ban[k] == 0)
				{
					flagre = true;
					break;
				}
			}
			if (flagre)
				continue;
				
			//1,记录路径
			vector  tmppath;//记录遍历结果
			for (int k = nextStart; path[k] != -1; k = path[k])
			{
				tmppath.push_back(k);
				visited_ban[k] = 1;
			}
			//2,倒序记录,一条路径是7(终点),5,3,实际上应该是3,5,7
			visited_ban[curStart] = 1;//新产生的路径上的节点通通禁止访问
			result[idx++] = curStart;//idx是result的size,3
			for (int j = 0; j < tmppath.size() - 1; j++)//注意这里减1。3,5
				result[idx++] = tmppath[tmppath.size() - 1 - j];//下一次一来将先记录7,这里就不记录了,免于重复。

			//二,准备递归深入
			unvisited_size--;
			force_valid_paths(nextStart, result, idx, visited_ban, preCost + curCost);
			unvisited_size++;
			idx = pre_idx;
			visited_ban = pre_visited_ban;
		}
		delete[] pnode;
		pnode = NULL;
		return;
	}
}

void CodeCraft::get_new_path(int *pResultPath, int path_size, int curCost)
{
	this->m_minCost = curCost;
	this->m_pathSize = path_size;
	for (int i = 0; i < path_size; i++)
		this->pMinPath[i] = pResultPath[i];
}


void CodeCraft::map_must_vertex()
{
	for (int i = 0; i < this->m_mustSize; i++)
		this->mapping[this->pMustPass[i]] = 0;
}


//函数功能:提取目的节点,并且返回节点数目
//"0,1,2|3"提取后为0,2,3,1
void CodeCraft::demand_to_num(char * const demand)
{
	int len = strlen(demand);
	if (demand[len - 1] == '\n')
		len--;
	int i = 0;
	while (i < len && demand[i] != ',')//提取起点
		m_startVex = 10 * m_startVex + demand[i++] - '0';

	i++;
	while (i < len && demand[i] != ',')//提取终点
		m_endVex = 10 * m_endVex + demand[i++] - '0';

	int nsize = 0;
	for (i++; i < len; i++)
	{
		while (i < len && demand[i] != '|')//提取指定必经节点
			this->pMustPass[nsize] = 10 * this->pMustPass[nsize] + demand[i++] - '0';
		nsize++;
	}
	this->m_mustSize = nsize;
}


//将一条字符串边转换成数字边
//aline_edge,字符串边,包含了四条信息
//edge_num,总边数

void CodeCraft::edge_to_num(char *one_edge, int *num)
{
	int i = 0;
	char *temp_str[10];

	while ((temp_str[i] = strtok(one_edge, ",")) != NULL)
	{
		num[i] = atoi(temp_str[i]);
		i++;
		one_edge = NULL;
	}
}


/****************************************************
*目的:建立一幅图像,并且总是选择较小的边作为图的真正边
*并且获得最大的顶点编号vectex_num(全局变量)
*参数1:topo,图的原始数据
*参数2,:edge_num,边的数目
*
****************************************************/
void CodeCraft::build_graph(char ** const topo, const int edge_nums)
{
	if (topo == NULL)
		return ;
	map< pair, int> mapping;   //用来检查顶点A->B是否多次出现,如果多次出现就总是取最小的
	int num[4] = { 0 };
	int vertexNums = 0;
	for (int i = 0; i < edge_nums; i++)
	{
		edge_to_num(topo[i], num);
		if (mapping.find(pair(num[1], num[2])) == mapping.end()){//如果不存在
			graph_matrix[num[1]][num[2]].LinkID = num[0];
			graph_matrix[num[1]][num[2]].Cost = num[3];
			mapping[pair(num[1], num[2])] = num[3];
		}
		else{//如果顶点A->B已经存在,则选取较小者即可
			if (mapping[pair(num[1], num[2])] > num[3])//现在的要小一些
			{
				graph_matrix[num[1]][num[2]].LinkID = num[0];
				graph_matrix[num[1]][num[2]].Cost = num[3];
				mapping[pair(num[1], num[2])] = num[3];
			}
		}
		if (num[1] >= vertexNums)
			vertexNums = num[1]+1;//在编号的基础上+1,因为编号是从0开始算的。
		if (num[2] >= vertexNums)
			vertexNums = num[2]+1;
	}
	for (int i = 0; i < vertexNums; i++)//自己到自己的距离为-1,即不存在路径
		graph_matrix[i][i].Cost = -1;
	this->m_vertexNums = vertexNums;
}


// 最短路径:Dijkstra, dist[j] = min{dist[j], dist[i]+graph[i][j]}  
int CodeCraft::dijkstra(
	int start_must,
	int *path,
	vector &visited_ban,
	int *shortcost
	)
{
	//初始化,-1代表max_value  
	memset(path, -1, sizeof(int)*MAX_VERTEX_NUMS);
	memset(shortcost, -1, sizeof(int)*MAX_VERTEX_NUMS);
	vector visited(visited_ban);
	visited[start_must] = 0;//重新允许访问
	int min, k;
	//起点到终点最短距离为0  
	shortcost[start_must] = 0;
	int num = m_vertexNums - 1;
	while (num)
	{
		//贪心策略:从访问过的顶点中,找出最短路径,从已知的最短路径开始延伸  
		//寻找新的中间点k,依据就是数组中权值最小的那个点的位置(且没被访问过)    
		min = -1;
		k = -1;
		for (int i = 0; i < m_vertexNums; i++)
		{
			if (visited[i] != 1 && shortcost[i] != -1 &&
				(min != -1 && shortcost[i] < min || min == -1))
			{
				min = shortcost[i];
				k = i;
			}
		}

		//终点最短路径已找到或所有顶点最短路径都已找到  
		if (min == -1)
			break;
		//标记访问过的顶点  
		visited[k] = 1;
		num--;
		
		

		//dist[j] = min{d[j], dist[i]+graphy[i][j]}  
		//更新未访问过邻接顶点的最短路径  
		for (int i = 0; i min + graph_matrix[k][i].Cost || shortcost[i] == -1))
			{
				shortcost[i] = min + graph_matrix[k][i].Cost;
				path[i] = k; //更新记录前驱顶点,供最后回溯最短路径  
			}
		}
	}
	return 0;
}


int  CodeCraft::unconnected_size()
{
	int start_vertex = m_startVex;
	set setting(pMustPass, pMustPass + m_mustSize);
	vector dfs_visited(m_vertexNums,0);//通过dfs_visited数组来标记这个顶点是否被访问过,0表示未被访问,1表示被访问
	queueque;
	
	dfs_visited[start_vertex] = 1;//标记顶点start_vertex被访问
	que.push(start_vertex);
	
	while (!que.empty())
	{
		int k = que.front();
		que.pop();
		for (int j = 0; j 0 && dfs_visited[j] == 0)
			{
				dfs_visited[j] = 1;//标记为已经被访问过
				que.push(j);//弹出
			}
		}
	}
	int cnt = 0;

	for (int i = 0; i < m_vertexNums; i++)
	{
		if (dfs_visited[i] == 0)
			cnt++;
	}
	return cnt;
}





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

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

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

你可能感兴趣的:(数学与算法,数据结构与算法)