您的位置:首页 > 其它

2016华为软件精英挑战赛 图论学习

2016-08-21 14:54 239 查看
github

暴力深搜dfs
算法设计思想

算法分析

代码实现

高级用例贪心策略
算法设计思想
基于贪心思想的算法

算法的优化

算法分析

代码实现

复赛心得

个人渣解,含有论文算法数据等,可进一步coding,欢迎star :)

github

赛题链接

从悄悄关注别人各种动态的过程中,知道了有一种神器叫“求解器”,原来其实就是调用库解决问题的方式!自己后来也想通过别人口中的求解器解决问题,但是发现基于求解器也是需要很强的建模能力的,,无论哪种解决问题的算法或者方法都需要一定能力!

在比赛途中耳濡目染,交流过程中,已知可解决NP问题的算法有

1),遗传算法

2),蛙跳算法

3),蒙特卡洛模拟随机算法,每次运行的结果都不会一样。

4),模拟退火

5),将问题转化成TSP旅行商问题,因为TSP问题和本问题很像,所以有人努力往此问题转化。

6),线性规划

暴力深搜dfs

算法设计思想

针对前五个低级案例的解决方案:

从起点开始深度优先搜索,并用数组记录沿途边号,当遇到终点时,此时就已经搜索到一条路径,否则就一直深度优先访问下去。

这条路径不一定全部经过毕竟点,所以要检查一下是否包含全部必经点。

如果全部包含必经点,则存储该路径,以后遇到比该路径权值更小的就更新路径。

显然这是一个递归的思路。

算法分析

这是一个暴力搜索,从起点到终点的路径随着顶点个数的增多有趋近指数级的条数,并且还要搜索出很多无效路径,所以相当费时。

代码实现:

注意:

1),采用邻接矩阵建图,矩阵中每个元素存储着边的索引号和权重

class Vertex  //顶点的自定义(可能叫边的定义更好点)
{
public:
Vertex()
{
LinkID = -1;//没有相连的索引号
Cost = MAX_WEIGHT;
}
int LinkID;
int Cost;
};

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


2)头文件中类的完整声明. 其中图是一个全局变量(邻接矩阵)!

#ifndef __ROUTE_H__
#define __ROUTE_H__
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <map>
#include <iostream>
#include <string.h>
#include <set>
#include <stack>
#include <queue>
#include <vector>
#include <functional>
#include <time.h>
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<int> visited_ban, int preCost);

//核心函数的辅助函数
int next_adj_vertex(int curVex, int nextVex, bool flagfirst);
int get_unvisited_size(vector<int> &visited_ban);
void get_new_path(int *pResultPath, int path_size, int curCost);
int dijkstra(int start_must, int *path, vector<int> &visited_ban, int *shortcost);
//当前源点是否可达其他未访问的必经点,即shortcost中的最短路径值是有效的。
bool is_valid_connect(vector<int> &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<int, int> mapping;
bool isbadgraph;
};
}

#endif


3)深度优先暴力搜索满足条件的所有路径,结果只保存其中最短的一条

具体代码如下:

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::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;
}


高级用例:贪心策略

算法设计思想:

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

基于贪心思想的算法

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

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

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

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

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

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

算法的优化

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

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

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

算法分析

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

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

代码实现:

github

个人渣解,含有论文算法数据等,可进一步coding,欢迎star :)

源码见下:

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

复赛心得

如果直接用初赛的方法分别找出两条路径P’和P’’的话,会有两个问题,一个是由于点数增多到了2000个会搜不出来,另一个是由于重合边数太多会难以出较优解。为了解决上述两个问题采用如下的思路:

(1) 使用DLX算法搜出两条路径的初始解。找一条路径的问题可以转化为01精确覆盖问题,只要把每条边选/不选作为一组01变量,每个点经过/不经过作为一组01变量,就能列出精确覆盖问题的方程。但传统的DLX模型解决此类图论问题的时候会产生问题,输出的路径可能含有环,因此DLX算法必须在递归的过程中实时进行去环操作,简单的说就是一旦发现成环立刻判为不合法解返回。

由于DLX算法会挑图中最薄弱的环节先进行搜索,因此搜索效率高,容易出可行解,搜索的每一步中还可以用Tarjan强连通缩点+Topo排序算法判断当前解是否一定是非法解进行剪枝。

(2) 使用迭代优化算法对初始解进行优化。因为初始解不保证解的优劣性,只保证可行性,因此可以将初始解按关键点分割成一段段“线路”,第一步优化是使用Dijkstra将每对相邻关键点之间的线路进行“绷紧”,用最短路替换DLX搜出来的弯弯曲曲的路径,并且通过增加重边权值有意回避另一条路径上的重边;第二步优化是交换关键点在路径上的出现次序,并且使用“绷紧”算法重新构造可行解,让解的总权值一步步变小。

总体来说,上述算法的问题在于DLX算法在深搜时仍不够强劲,大数据下可能会跑不出解;优化还不够彻底,比不上那些用线性规划算法跑出来的解优。但无论如何,至少这是一系列成功尝试,在写算法的过程中颇具收获。搭整个比赛算法框架的过程中,由于代码量大、细节多,因此对编程风格、DEBUG能力也是非常考验的。在快提交截止的时候发现最短路算法写挂了玩命DEBUG,是一种多么酸爽的体验……

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

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

原作者博客:http://blog.csdn.net/ebowtang
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息