// 2017华为软件精英挑战赛小结
// 不说废话,直接上货!希望对目前的参赛者,或日后学习的人,提供一些参考和思路。
#include <赛题说明.pdf> // 见附录文件 赛题说明.pdf 或网址传送门:http://codecraft.huawei.com/home/detail
#include <算法导论.26> //《算法导论》第26章--最大流
#include <算法导论.24> //《算法导论》第24章--Dijkstra算法
#include <智能优化算法及其Matlab仿真.6> //《智能优化算法及其Matlab仿真》第6章--粒子群算法
int main()
{
解题思路:启发式搜索算法 + 最大流算法
启发式搜索算法用来安排部署视频服务器的位置,最大流算法用来计算当前部署方案的总成本以及输出路径。
其中,启发式搜索算法我选用的是 “粒子群算法”,其实《智能优化算法及其Matlab仿真》(包子阳、余继周著)这本书中总结了目前8种主要的智能优化算法,包括 遗传、差分进化、免疫、蚁群、粒子群、模拟退火、禁忌搜索、神经网络 等算法。粒子群算法属于群体智能算法,是一种高效的并行随机搜索算法,它和遗传算法很相近,它能以较大的概率收敛于全局最优可行解。【这本书虽然我只看了一部分,但还是良心推荐了】
粒子群算法当中的“粒子”,其实就是解,粒子群 即 “解集”(解空间)。我们把一种服务器部署方案视为一个解。当然,每一种服务器部署方案都有两种可能结果:(1)在当前的网络结构中能够提供消费者的消费需求,该方案可行,即解可行;(2)在当前的网络结构中不能够提供消费者的消费需求,该方案不可行,即解不可行。本赛题的核心问题就是,在当前网络结构中,寻求可行的服务器部署方案(可行解),并在可行方案中寻求最优(成本最低)的部署方案(最优可行解)。换句话说就是,利用粒子群算法,搜索“粒子群”(解空间)中的“最优粒子”(最优可行解)。
该算法的优点是实现简单,参数少,【但是】合适参数的控制是个难点。调整控制参数是个“体力活”,而且还要靠“LUCK”。另外,我们设解空间的维度D = 消费节点个数,因为从题意当中可以很容易知道,较优的服务器部署方案的服务器个数不可能超过消费节点个数。每个维度中的搜索范围是V = 网络节点个数。所以,该问题的解空间的 规模 = V^D(例如最大规模的测试题为800^360 = ∞ 无穷大),所以如何在这么大的解空间当中找到尽可能 较优的可行解,是本赛题的关键,更是难点中的难点。解决思路有:(1)寻求缩小每个维度的搜索范围的方法;(2)寻求较好的初始的可行解;(3)寻求合适的启发策略,即从一种可行解的基础上按照一定规律迭代出下一个可行解;(4)寻求较好的控制参数,根据每个解的优劣(适应度)动态调节参数。等等。而我的努力方向是(2)(3)(4)。
最大流算法,算法导论第26章当中写的非常精彩,我当时看了的前两节内容,然后直接就把最大流算法编出来了,而且直接就可以在我的程序当中使用了,计算总成本了。在本次的华为软件比赛中,当确定某个服务器部署方案后,该问题就成了典型的最小费用最大流问题了,该部分的算法理论已经非常成熟了。不多分析了,【看书去】。在这里边有三个概念很重要:流、残存网络、增广路径。(按照我个人理解 来解释,若有错误,请多包涵,请多指正)
流:是一种抽象的单位量化,比如某某需要23Gbps的视频需求,则视为23个流单位的需求,1Gbps视为1个流。残存网络(残存图):字面意思是使用过的剩下的残存的图,但这样理解就太小看它了,它是一个满足成本最小的多任务调度系统,在每次增加一个新任务时,会反向调节之前已分配的任务,使新任务添加进来后的总成本依然保持最小,而我们这里要增加的任务对象是 流。增广路径:即增加一个新的路径(任务),正是因为残存网络(残存图)中有反向调节的机制,所以每次增加一条新路径,都不会破坏之前已添加的最小成本路径。
从多个服务器和多个消费节点来看,该问题是一个多源多汇问题,常见的解决思路就是,转为单源单汇问题处理,不过这个问题我个人更喜欢转化为“单源多汇”,虽然本质和单源单汇一样,但这样可能少写些代码。于是,编程时我只增加了一个超级消费节点S。另外,我看到的大部分有关最大流的资料都是针对有向图的,并说最大流不适合解决无向图,因为有可能会导致负环,并且是使用spfa算法来寻找增广路径的(因为时间有限,我看的资料很少,少见多怪了)。【但是】我觉得最大流问题更适合无向图,而且处理起来会比有向图更简单,不过这里我使用的是Dijkstra算法来寻找增广路径的。无向图网络的一条无向边被视为两条“等大反向”的有向边,注意“等大反向”这一点跟重要,这个时候这个无向图网络 与 相对应的残存图网络 是一模一样的,则残存图网络中这两条“等大反向”的有向边 可以互为反向边(反向流)。利用残存图网络的带宽属性和残存图网络的成本属性 寻找增广路径,利用残存图网络的费用流属性和原图网络的成本属性计算当前部署方案的总成本,利用残存图网络的费用流属性寻找最终的消费路径,即最终输出路径。下面且看我用代码来表达我的设计吧,哈哈。【很多代码是自己原创,不足之处求指正】
另外,个人觉得最大流算法这里没有值得优化的空间,毕竟这个算法已经这么成熟了嘛。所以,服务器的部署调度策略才是本赛的重中之重。最终效果图我就不展示了,个人的粒子群算法还有很多不足之处,控制参数需要继续优化,不过对于简单用例来说 跑出最优解还是没问题的。【欢迎讨论交流】
1、程序结构:
文件cdn.cpp 主函数main() (包含 输入输出接口)
文件deploy.cpp 部署服务器入口函数deploy_server() (调用图构造函数 调用粒子群算法函数)
文件myGraph.cpp 、myGraph.h 粒子群算法函数birds() 、 最大流计算成本函数countPathCost() 、 Dijkstra算法寻增广路径函数finMinPathCost() 、 最终路径输出函数finalOut()
2、文件myGraph.h的结构图
网络图的存储结构:邻接链表
结构体Edge 边
结构体User 消费节点
结构体Vertex 网络节点
类型myGraph 网络图类
3、函数关系调用图
(这个图有点大,分成上下两个图)
4、最后上代码(略去标准库的头文件)
文件deploy.cpp
#include "deploy.h"
#include "myGraph.h"
using namespace std;
extern string strOut; //最终输出路径字符串,外部全局变量
extern string strSer; //服务器字符串,外部全局变量
//你要完成的功能总入口
void deploy_server(char * topo[MAX_EDGE_NUM], int line_num,char * filename)
{
string oneline; //输入文件的每一行
string threeFir[3]; //输入第一部分,网络节点数 边数 消费节点数
int threeFir_int[3]; //输入第一部分,网络节点数 边数 消费节点数,int 类型
int service = 0; //输入第二部分,服务器成本
string node[4]; //输入第三部分,边始节点 边终结点 边带宽 边租用成本
string buynode[3]; //输入第四部分,消费节点 网络节点 需求带宽
string strcase; //输出文件字符串
//输入文件的 第一部分 和 第二部分
oneline = topo[0]; //输入文件第一部分
istringstream strin0(oneline);
for (int i = 0; i < 3; i++) //分割字符串
{
getline(strin0, threeFir[i], ' ');
threeFir_int[i] = atoi(threeFir[i].c_str());
}
myGraph G0(threeFir_int[0],threeFir_int[1],threeFir_int[2]); //利用(网络节点数 边数 消费节点数), 构造图 G0
oneline = topo[2]; //输入文件第二部分
service = atoi(oneline.c_str());
strcase += threeFir[2] + "\n"; //输出文件字符串,默认输出 服务器直接放在网络节点处的情况
//输入文件的 第三部分 和 第四部分
int lineN2 = 4;
int lineN3 = threeFir_int[1] + lineN2 + 1;
for (int i = lineN2; i < lineN2 + threeFir_int[1]; i++) //输入文件第三部分
{
oneline = topo[i];
istringstream strin2(oneline);
for (int i = 0; i < 4; i++) //分割字符串
getline(strin2, node[i], ' ');
G0.AddSingleEdge(atoi(node[0].c_str()),atoi(node[1].c_str()),atoi(node[2].c_str()),atoi(node[3].c_str())); //利用(边始节点 边终结点 边带宽 边租用成本),为图 G0 添加 单向边
G0.AddSingleEdge(atoi(node[1].c_str()),atoi(node[0].c_str()),atoi(node[2].c_str()),atoi(node[3].c_str())); //利用(边始节点 边终结点 边带宽 边租用成本),为图 G0 添加 单向边
}
for (int i = lineN3; i < lineN3 + threeFir_int[2]; i++) //输入文件第四部分
{
oneline = topo[i];
istringstream strin3(oneline);
for (int i = 0; i < 3; i++) //分割字符串
getline(strin3, buynode[i], ' ');
G0.AddUser(atoi(buynode[0].c_str()),atoi(buynode[1].c_str()),atoi(buynode[2].c_str())); //利用(消费节点 网络节点 需求带宽),为图 G0 添加 消费节点情况
G0.AddSingleEdge(threeFir_int[0],atoi(buynode[1].c_str()),atoi(buynode[2].c_str()),0); //利用(超级消费节点 消费节点连接的网络节点 消费节点带宽需求 0),为图 G0 添加超级消费节点的 单向边
strcase += buynode[1] +" "+ buynode[0] +" "+ buynode[2]; //输出文件字符串,默认输出 服务器直接放在网络节点处的情况
}
//设置服务器
//使用 粒子群启发式算法 和 最小费用最大流算法,布置服务器位置并计算返回 总成本 costSum
long costSum = G0.birds(service);
long maxCostSum = threeFir_int[2]*service; //服务器直接放在网络节点处的情况的 总成本 maxCostSum
if (costSum < maxCostSum)
{
G0.finalOut(); //计算输出路径,得到输出路径字符串 strOut 和 服务器字符串 strSer
strcase = strOut; //输出文件字符串,由默认的输出情况替换为新的 输出路径的情况
cout << "--------------------" << endl
<< " case: === " << costSum << " / "<< maxCostSum << endl
<< " Services :" << strSer << endl
<< "--------------------" << endl;
//cout << strcase << "\n"; //显示输出路径
}
const char * topo_file = strcase.c_str();
write_result(topo_file, filename);
}
文件myGraph.h
struct User;
struct Vertex;
struct Edge
{
int Vstart;
int Vend;
int Egbps;
int Ecost;
int Fij; //费用流fij
Edge * next;
Edge(int vs, int ve, int eg, int ec, int fij):Vstart(vs),Vend(ve),Egbps(eg),Ecost(ec),Fij(fij),next(NULL){}
};
struct Vertex
{
int outNum;
int outGbps;
Edge * head;
Vertex():outNum(0),outGbps(0),head(NULL){};
};
struct User
{
int Unode;
int Ugbps;
User():Unode(-1),Ugbps(-1){}
};
class myGraph
{
public:
int Vnum;//网络节点个数(包含超级消费节点)
int Enum;//边数
int Unum;//消费节点个数
Vertex * V;//原图网络节点
User * U;//消费节点
int * boolV;//服务器标记数组
int FijMax;//最大流
Vertex * VF;//残存图网络节点
int ** Map;//路径转移矩阵
int ** MapCost;//最小路径成本矩阵
myGraph():Vnum(0),Enum(0),Unum(0),V(NULL),U(NULL),boolV(NULL),FijMax(0),VF(NULL),Map(NULL),MapCost(NULL){}
myGraph(int vn, int en, int un):Vnum(vn+1),Enum(2*en),Unum(un)
{
V = new Vertex[Vnum];
U = new User[Unum];
boolV = new int[Vnum];
for (int i = 0; i < Vnum; i++)
boolV[i] = 0;
FijMax = 0;
VF = NULL;
Map = NULL;
MapCost = NULL;
}
~myGraph()
{
for (int i = 0; i < Vnum; i++)
{
Edge * eh = V[i].head;
while (eh != NULL)
{
Edge * del = eh;
eh = eh->next;
delete del;
}
}
delete [] V;
V = NULL;
delete [] U;
U = NULL;
delete [] boolV;
boolV = NULL;
if (VF != NULL)
{
for (int i = 0; i < Vnum; i++)
{
Edge * eh = VF[i].head;
while (eh != NULL)
{
Edge * del = eh;
eh = eh->next;
delete del;
}
}
delete [] VF;
}
VF = NULL;
if (Map != NULL)
{
for (int i = 0; i < Vnum; i++)
delete [] Map[i];
delete [] Map;
}
Map = NULL;
if (MapCost != NULL)
{
for (int i = 0; i < Vnum; i++)
delete [] MapCost[i];
delete [] MapCost;
}
MapCost = NULL;
}
myGraph(const myGraph &g);
void AddSingleEdge(int vs, int ve, int eg, int ec);
void AddUser(int u, int un, int ug);
void createVF();
void createMap();
void setMap();
long countCost(int service, int * Node);
long countPathCost(int serviceCost);
int finMinPathCost();
void addFij(int vs, int ve, int fij);
void finalOut();
int searchPath(int Node, int f);
void outStack(int f);
void showGraph();
int * topOutNode();
int * topGbpsNode();
void mapSort(int * bird0, int * birdk);
long birds(int serviceCost);
};
文件myGraph.cpp
#include "myGraph.h"
#define INT_MAX_MY 30000 //自定义 int 最大值
#define LONG_MAX_MY 6000000 //自定义 long 最大值
#define STACKN 500 //输出路径堆栈 最大长度
#define BIRDS 5 //粒子群算法 单位 可行解 个数
#define BIRDS_STYPE1 1 //粒子群算法 第一类 可行解
#define BIRDS_STYPE2 2 //粒子群算法 第二类 可行解
#define BIRDS_STYPE3 3 //粒子群算法 第三类 可行解
#define BIRDS_STYPE4 5 //粒子群算法 第四类 可行解
#define BIRDS_STYPE5 8 //粒子群算法 第五类 可行解
#define TSEARCH 200 //粒子群算法 最大迭代次数
#define NRAND 200 //粒子群算法 指定位置的服务器随机变异 概率
#define NNEGA 100 //粒子群算法 去掉指定位置的服务器 概率
#define NGOGBEST 100 //粒子群算法 指定位置的服务器变为全局最优解的服务器位置 概率
#define NGOPERFE 100 //粒子群算法 指定位置的服务器变为局部最优解的服务器位置 概率
#define NNEXT 100 //粒子群算法 指定位置的服务器变为相邻网络节点的位置 概率
#define NUNCHA 200 //粒子群算法 指定位置的服务器不发生改变 概率
using std::string;
using std::stringstream;
string strOut; //最终输出路径字符串,外部全局变量
string strSer; //服务器字符串,外部全局变量
stringstream strPath; //路径字符串
int stackN[STACKN] = {}; //输出路径堆栈
int top = 0; //输出路径堆栈 栈顶 top
//复制构造函数
myGraph::myGraph(const myGraph &g)
{
Vnum = g.Vnum;
Enum = g.Enum;
Unum = g.Unum;
V = new Vertex[Vnum];
U = new User[Unum];
boolV = new int[Vnum];
for (int i = 0; i < Vnum; i++)
{
V[i].outNum = g.V[i].outNum;
V[i].outGbps = g.V[i].outGbps;
V[i].head = NULL;
Edge *ge = g.V[i].head;
if (ge != NULL)
{
V[i].head = new Edge(ge->Vstart,ge->Vend,ge->Egbps,ge->Ecost,ge->Fij);
ge = ge->next;
}
Edge *e = V[i].head;
while (ge != NULL)
{
e->next = new Edge(ge->Vstart,ge->Vend,ge->Egbps,ge->Ecost,ge->Fij);
e = e->next;
ge = ge->next;
}
boolV[i] = g.boolV[i];
}
for (int i = 0; i < Unum; i++)
{
U[i].Unode = g.U[i].Unode;
U[i].Ugbps = g.U[i].Ugbps;
}
FijMax = g.FijMax;
VF = NULL;
Map = NULL;
MapCost = NULL;
}
//利用(边始节点 边终结点 边带宽 边租用成本),添加 单向边
void myGraph::AddSingleEdge(int vs, int ve, int eg, int ec)
{
if (vs == ve || eg <= 0)
return;//舍弃自环边 和 舍弃带宽有误的边
Edge * NewEdge = new Edge(vs,ve,eg,ec,0);
if (V[vs].head == NULL || V[vs].head->Vend >= ve)
{
NewEdge->next = V[vs].head;
V[vs].head = NewEdge;
}
else
{
Edge *pre;
pre = V[vs].head;
while (pre->next != NULL && pre->next->Vend < ve)
pre = pre->next;
NewEdge->next = pre->next;
pre->next = NewEdge;
}
V[vs].outNum++;
V[vs].outGbps += eg;
//delete NewEdge;
return;
}
//利用(消费节点 网络节点 需求带宽),添加 消费节点情况
void myGraph::AddUser(int u, int un, int ug)
{
U[u].Unode = un;
U[u].Ugbps = ug;
FijMax += ug;//增加最大流 FijMax
return;
}
//新建 最小费用最大流算法的 残存图 节点VF[]
void myGraph::createVF()
{
if (VF != NULL)
{
for (int i = 0; i < Vnum; i++)
{
Edge * eh = VF[i].head;
while (eh != NULL)
{
Edge * del = eh;
eh = eh->next;
delete del;
}
}
delete [] VF;
}
VF = NULL;
VF = new Vertex[Vnum];
for (int i = 0; i < Vnum; i++)
{
VF[i].outNum = V[i].outNum;
VF[i].outGbps = V[i].outGbps;
VF[i].head = NULL;
Edge *ge = V[i].head;
if (ge != NULL)
{
VF[i].head = new Edge(ge->Vstart,ge->Vend,ge->Egbps,ge->Ecost,ge->Fij);
ge = ge->next;
}
Edge *eF = VF[i].head;
while (ge != NULL)
{
eF->next = new Edge(ge->Vstart,ge->Vend,ge->Egbps,ge->Ecost,ge->Fij);
eF = eF->next;
ge = ge->next;
}
}
return;
}
//新建 原图的节点V[] 最小路径成本矩阵 MapCost[][] 和 路径转移矩阵 Map[][]
void myGraph::createMap()
{
if (Map != NULL)
{
for (int i = 0; i < Vnum; i++)
delete [] Map[i];
delete [] Map;
}
Map = NULL;
if (MapCost != NULL)
{
for (int i = 0; i < Vnum; i++)
delete [] MapCost[i];
delete [] MapCost;
}
MapCost = NULL;
Map = new int *[Vnum];
MapCost = new int *[Vnum];
for (int i = 0; i < Vnum; i++)
{
Map[i] = new int[Vnum];
MapCost[i] = new int[Vnum];
}
for (int i = 0; i < Vnum; i++)
for (int j = 0; j < Vnum; j++)
{
Map[i][j] = j - Vnum;
MapCost[i][j] = INT_MAX_MY;
}
return;
}
//设置 原图的节点V[] 最小路径成本矩阵 MapCost[][] 和 路径转移矩阵 Map[][],迪杰斯特拉算法
void myGraph::setMap()
{
int *mark = new int[Vnum];
for (int k = 0; k < Vnum; k++)
{
for (int i = 0; i < Vnum; i++)
mark[i] = 0;
int a = k;
mark[a] = 1;
Map[a][a] = a;
MapCost[a][a] = 0;
while (a >= 0)//寻找节点
{
Edge *p = V[a].head;//松弛处理
while (p)
{
if (mark[p->Vend] == 0)
{
int d = MapCost[k][a] + p->Ecost;
if(MapCost[k][p->Vend] > d)
{
MapCost[k][p->Vend] = d;
Map[k][p->Vend] = a;
}
}
p = p->next;
}
int u = -1;
int tmp = INT_MAX_MY;
for (int j = 0; j < Vnum-1; j++)
{
if(mark[j] == 0 && MapCost[k][j] < tmp)
{
u = j;
tmp = MapCost[k][j];
}
}
if (u != -1)
mark[u] = 1;//标记节点
a = u;
}
}
for (int i = 0; i < Unum; i++)
Map[Vnum-1][U[i].Unode] = U[i].Unode;
for (int i = 0; i < Vnum-1; i++)
MapCost[Vnum-1][i] = INT_MAX_MY;
delete [] mark;
return;
}
//当服务器放置情况为 Node[] 数组的情况时,返回 总成本
long myGraph::countCost(int serviceCost, int * Node)
{
int countnum = 0;//统计服务器个数
for (int i = 0; i < Vnum; i++)
boolV[i] = 0;
for (int i = 0; i < Unum; i++)
{
if (Node[i] >= 0 && boolV[Node[i]] == 0)//统计服务器个数,并把放置服务器的节点 标记为 1,Node[i] < 0 表示该维度,不设置服务器
{
boolV[Node[i]] = 1;
countnum++;
}
}
if (countnum == 0)
return LONG_MAX_MY;//无解情况一,没有服务器布置
long sumCost = countPathCost(serviceCost);//计算 路径成本 和 服务器成本,返回 总成本 sumCost
if (sumCost >= LONG_MAX_MY)
return LONG_MAX_MY;//无解情况二,即找不到有效路径而且没有达到最大流
return sumCost;//有解情况,返回当前可行解的 总成本
}
//计算 路径成本 和 服务器成本,并返回 总成本,最小费用最大流算法
long myGraph::countPathCost(int serviceCost)
{
createVF();//生成残存图 节点VF[]
int remaindFij = FijMax;//最大流 FijMax
Edge *p = VF[Vnum-1].head;//先检查 在消费节点处的网络节点 放服务器的情况
while (p)
{
if (boolV[p->Vend] == 1)
{
boolV[p->Vend] = 2;//使用该服务器 标记为 2
remaindFij = remaindFij - p->Egbps;
p->Fij = p->Egbps;
p->Egbps = 0;
p->Ecost = INT_MAX_MY;
}
p = p->next;
}
if (remaindFij == 0)
return serviceCost*Unum;//消费节点的网络节点处全部放置服务,满足最大流,返回 总成本
while (remaindFij)//满足最大流时,remaindFij == 0,停止循环
{
int fij = finMinPathCost();//在残存图中 寻找增广路径,并返回增广路径的 流 fij
if (fij < 0)
return LONG_MAX_MY;//无解情况,找不到有效路径而且没有达到最大流,返回 总成本
remaindFij = remaindFij - fij;
}
long pathCost = 0;
for (int i = 0; i < Vnum-1; i++)//计算路径成本 pathCost
{
Edge *gV = V[i].head;
Edge *gVF = VF[i].head;
while (gV != NULL)
{
pathCost += gV->Ecost * gVF->Fij;
gV = gV->next;
gVF = gVF->next;
}
}
int nums = 0;
for (int i = 0; i < Vnum-1; i++)//统计服务器使用个数 nums
if (boolV[i] >= 2)
nums++;
long sumCost = pathCost + serviceCost*nums;//总成本 = 路径成本 + 服务器成本
return sumCost;//有解情况,返回 总成本
}
//在残存图中 寻找增广路径,并返回增广路径的 流 fij,迪杰斯特拉算法
int myGraph::finMinPathCost()
{
int *mark = new int[Vnum];
int *dist = new int[Vnum];
int *preNode = new int[Vnum];
int *gbpsPer = new int[Vnum];
for (int k = 0; k < Vnum; k++)
{
mark[k] = 0;
dist[k] = INT_MAX_MY;
preNode[k] = -1;
gbpsPer[k] = -1;
}
int a = Vnum-1;//从超级消费节点 开始
mark[a] = 1;
dist[a] = 0;
preNode[a] = INT_MAX_MY;
gbpsPer[a] = INT_MAX_MY;
while (a >= 0 && boolV[a] <= 0)//寻找服务器
{
Edge *p = VF[a].head;//松弛处理
while (p)
{
if (mark[p->Vend] == 0)
{
if (p->Ecost < INT_MAX_MY)
{
int d = dist[a] + p->Ecost;
if(dist[p->Vend] > d)
{
dist[p->Vend] = d;
preNode[p->Vend] = a;
}
}
}
p = p->next;
}
int u = -1;
int tmp = INT_MAX_MY;
for (int j = 0; j < Vnum-1; j++)
{
if(mark[j] == 0 && dist[j] < tmp)
{
u = j;
tmp = dist[j];
}
}
if (u != -1)
{
mark[u] = 1;
Edge *pu = VF[preNode[u]].head;
while (pu->Vend != u)
pu = pu->next;
gbpsPer[u] = (pu->Egbps < gbpsPer[preNode[u]])?(pu->Egbps):(gbpsPer[preNode[u]]);
}
a = u;
}
if (a == -1)//未找到服务器
{
delete [] mark;
delete [] dist;
delete [] preNode;
delete [] gbpsPer;
return -1;//无解情况,找不到有效路径而且没有达到最大流
}
boolV[a] = 2;//使用服务器 标记为 2
int fij = gbpsPer[a];
int lastNode = a;
while (preNode[lastNode] != INT_MAX_MY)//增广路径 preNode[lastNode]
{
addFij(preNode[lastNode], lastNode, fij);//设置增广路径上的 流的使用情况,即设置 费用流
lastNode = preNode[lastNode];
}
delete [] mark;
delete [] dist;
delete [] preNode;
delete [] gbpsPer;
return fij;
}
//设置增广路径上的 流的使用情况,(增广路径始节点 增广路径终结点 增广路径流),即设置 费用流
void myGraph::addFij(int vs, int ve, int f)
{
if (f <= 0)
return;
Edge *pS = VF[vs].head;//残存图中 增广路径同向边
while (pS->Vend != ve)
pS = pS->next;
pS->Egbps = pS->Egbps - f;
if (pS->Egbps == 0)
pS->Ecost += INT_MAX_MY;
if (vs != Vnum-1)
{
Edge *pE = VF[ve].head;//残存图中 增广路径反向边
while (pE->Vend != vs)
pE = pE->next;
if (pE->Egbps == 0)
pE->Ecost -= INT_MAX_MY;
pE->Egbps = pE->Egbps + f;
if (pS->Fij >= pE->Fij)//新增广路径的流 与 之前该边的流同向
pS->Fij += f;
else
{
if (f >= pE->Fij)//新增广路径的流 与 之前该边的流反向,且可以逆向
{
pS->Fij = f - pE->Fij;
pE->Fij = 0;
}
else
pE->Fij = pE->Fij - f;
}
}
else
pS->Fij += f;
return;
}
//最终的输出路径,设置 输出路径字符串 strOut 和 服务器字符串 strSer
void myGraph::finalOut()
{
stringstream strNums;//路径条数字符串
stringstream strSerstr;//服务器字符串
string str1,str2;
int sumPathNums = 0;//统计输出的路径条数
for (int i = 0; i < STACKN; i++)//初始化输出路径堆栈为 -1
stackN[i] = -1;
for (int i = 0; i < Unum; i++)//遍历消费节点,寻找路径
{
if (boolV[U[i].Unode] >= 2)//消费节点连接的网络节点是服务器,得到一条路径 strPath
{
sumPathNums++;
strPath << U[i].Unode << " " << i << " " << U[i].Ugbps << "\n";
}
else
{
int fijU = U[i].Ugbps;//消费节点的总需求
while (fijU > 0)//消费节点的路径是否寻找完整,即 fijU == 0
{
stackN[0] = i;
stackN[1] = U[i].Unode;
top = 2;//栈顶
int resF = searchPath(U[i].Unode,INT_MAX_MY);//利用 费用流 递归寻找路径,并将路径节点压入堆栈 stackN[],返回 费用流路径最小流
if (resF <= 0)
return;//没找到路径,这种情况其实不会出现
if (resF > fijU)//如果是富余路径
resF = fijU;
outStack(resF);//将路径节点弹出堆栈 stackN[],得到一条路径 strPath
strPath << stackN[1] << " " << stackN[0] << " " << resF << "\n";
sumPathNums++;
fijU = fijU - resF;
}
}
}
//得到 最终输出路径 strOut = 路径条数字符串 strNums + 路径字符串 strPath
strNums << sumPathNums << "\n\n#";
strPath << "#";
getline(strNums, str1, '#');
getline(strPath, str2, '#');
strNums.str("");
strPath.str("");
strOut = "";
strOut = str1 + str2;
//得到 服务器字符串 strSer
int serUseNum = 0;
int serNum = 0;
for (int i = 0; i < Vnum-1; i++)
if (boolV[i] >= 2)
{serUseNum++;serNum++;strSerstr << " " << i;}
else if (boolV[i] > 0)
{serNum++;strSerstr << " <" << i << ">";}
strSerstr << " === " << serUseNum << " - " << serNum << " / " << Unum << "#";
getline(strSerstr, strSer, '#');
strSerstr.str("");
return;
}
//利用 费用流 递归寻找路径,并将路径节点压入堆栈 stackN[],返回 费用流路径最小流
int myGraph::searchPath(int Node, int f)
{
Edge *path = VF[Node].head;
if (path == NULL)//没找到路径,返回 -1
return -1;
while (path->Fij <= 0)
path = path->next;
if (f > path->Fij)
f = path->Fij;
int nextNode = path->Vend;
stackN[top] = nextNode;
top++;
if (boolV[nextNode] >= 2)//找到服务器
return f;//找到服务器,返回
int resF = searchPath(nextNode,f);
return resF;
}
//将路径节点弹出堆栈 stackN[],得到一条路径 strPath
void myGraph::outStack(int f)
{
while (top > 2)
{
top--;
int nearService = stackN[top];
Edge *distService = VF[stackN[top-1]].head;
while (distService->Vend != nearService)
distService = distService->next;
distService->Fij -= f;
strPath << nearService << " ";
stackN[top] = -1;
}
return;
}
//显示
void myGraph::showGraph()
{
using namespace std;
cout << "---------------------------- G show" << endl
<< Vnum << " " << Enum << " " << Unum << endl << endl;
for (int i = 0; i < Vnum; i++)
{
cout << setw(3) << i << " " << setw(3) << V[i].outNum << "| " << setw(3) << V[i].outGbps << "| ";
Edge *e = V[i].head;
while (e != NULL)
{
cout << " -> " << setw(2) << e->Vend;
e = e->next;
}
cout << endl;
}
cout << endl;
for (int i = 0; i < Unum; i++)
{
cout << setw(3) << i << " -- " << setw(3) << U[i].Unode << " -- " << setw(3) << U[i].Ugbps << endl;
}
cout << "---------------------------- end" << endl;
if (Map == NULL)
return;
cout << "---------------------------- G map" << endl;
cout << " " << setw(3) << 0 << " | ";
for (int i = 0; i < Vnum; i++)
cout << " " << setw(3) << i;
cout << endl
<< "--------------------------------------------------------------------------------------------------------------------------"
<< endl;
for (int i = 0; i < Vnum; i++)
{
cout << " " << setw(3) << i << " | ";
for (int j = 0; j < Vnum; j++)
cout << " " << setw(3) << Map[i][j];
cout << endl;
}
cout << "---------------------------- end" << endl;
if (MapCost == NULL)
return;
cout << "---------------------------- G mapCost" << endl;
cout << " " << setw(3) << 0 << " | ";
for (int i = 0; i < Vnum; i++)
cout << " " << setw(3) << i;
cout << endl
<< "--------------------------------------------------------------------------------------------------------------------------"
<< endl;
for (int i = 0; i < Vnum; i++)
{
cout << " " << setw(3) << i << " | ";
for (int j = 0; j < Vnum; j++)
cout << " " << setw(3) << MapCost[i][j];
cout << endl;
}
cout << "---------------------------- end" << endl;
return;
}
//排序 找出出度最大的前 Unum 个节点,并返回该数组
int * myGraph::topOutNode()
{
int outNumsBefore = V[Vnum-1].outNum;
V[Vnum-1].outNum = 1;
int * topON = new int[Unum];
for (int i = 0; i < Unum; i++)
topON[i] = Vnum-1;
for (int i = 1; i < Vnum-1; i++)//-1 除去超级消费节点
{
if (V[i].outNum > V[topON[Unum-1]].outNum)
{
int j = 0;
while (V[i].outNum <= V[topON[Unum-1]].outNum)
j++;
for (int jj = Unum-1; jj > j; jj--)
topON[jj] = topON[jj-1];
topON[j] = i;
}
}
for (int i = 0; i < Unum; i++)
if(topON[i] == Vnum-1)
topON[i] = -i-1;
V[Vnum-1].outNum = outNumsBefore;
return topON;
}
//排序 找出带宽最大的前 Unum 个节点,并返回该数组
int * myGraph::topGbpsNode()
{
int outNumsBefore = V[Vnum-1].outGbps;
V[Vnum-1].outGbps = 1;
int * topGN = new int[Unum];
for (int i = 0; i < Unum; i++)
topGN[i] = Vnum-1;
for (int i = 1; i < Vnum-1; i++)//-1 除去超级消费节点
{
if (V[i].outGbps > V[topGN[Unum-1]].outGbps)
{
int j = 0;
while (V[i].outGbps <= V[topGN[Unum-1]].outGbps)
j++;
for (int jj = Unum-1; jj > j; jj--)
topGN[jj] = topGN[jj-1];
topGN[j] = i;
}
}
for (int i = 0; i < Unum; i++)
if(topGN[i] == Vnum-1)
topGN[i] = -i-1;
V[Vnum-1].outGbps = outNumsBefore;
return topGN;
}
//排序 利用最小路径成本矩阵MapCost[][],将数组birdk[] 按照离 数组bird0[] 指定位置最近的顺序 进行排序
void myGraph::mapSort(int * bird0, int * birdk)
{
int * mapSequr = new int[Unum];
int * mapSequrMarkI = new int[Vnum];
int * mapSequrMarkJ = new int[Vnum];
for (int i = 0; i < Vnum; i++)
{
mapSequrMarkI[i] = 0;
mapSequrMarkJ[i] = 0;
}
for (int i = 0; i < Unum; i++)
mapSequr[i] = bird0[i]-(Vnum-1);
int ii=0,jj=0,leftNum=Unum;
while (ii >= 0 && jj >= 0 && leftNum > 0)
{
ii=-1,jj=-1;int colI=-1,rowJ=-1;
int tempMax = INT_MAX_MY+1;
for (int i = 0; i < Unum; i++)
{
int b0 = (bird0[i]>=0)?bird0[i]:(bird0[i]+Vnum-1);
if (mapSequrMarkI[b0] == 0)
{
for (int j = 0; j < Unum; j++)
{
int bk = (birdk[j]>=0)?birdk[j]:(birdk[j]+Vnum-1);
if (mapSequrMarkJ[bk] == 0)
{
if (tempMax > MapCost[b0][bk])
{
tempMax = MapCost[b0][bk];
ii = i;
jj = j;
colI = b0;
rowJ = bk;
}
}
}
}
}
if (ii >= 0 && jj >= 0)
{
mapSequrMarkI[colI] = 1;
mapSequrMarkJ[rowJ] = 1;
mapSequr[ii] = birdk[jj];
}
leftNum--;
}
for (int i = 0; i < Unum; i++)
birdk[i] = mapSequr[i];
delete [] mapSequr;
delete [] mapSequrMarkI;
delete [] mapSequrMarkJ;
return;
}
//粒子群算法 设置服务器位置
long myGraph::birds(int serviceCost)
{
clock_t start_clock,now_clock;
double duration_time;
start_clock = clock();
srand((unsigned)time(NULL));
int stackMustNode[400] = {}; //可行解堆栈
int topMN = 0; //可行解堆栈 栈顶
int N = BIRDS_STYPE5*BIRDS; //粒子群算法 可行解总数 N 解集
int D = Unum; //粒子群算法 维度 D
int **bird = new int *[N]; //粒子群算法 可行解 bird[] 解集
int **perfect = new int *[N]; //粒子群算法 可行解 bird[] 的当前的局部最优可行解
for (int i = 0; i < N; i++)
{
bird[i] = new int[D];
perfect[i] = new int[D];
}
long *perfectFit = new long[N]; //粒子群算法 可行解 bird[] 的当前的局部最优可行解的 perfect[] 适应度值
int *gbest = new int[D]; //粒子群算法 所有可行解集 bird[] 的当前的全局最优可行解
long gbestFit = LONG_MAX_MY; //粒子群算法 所有可行解集 bird[] 的当前的全局最优可行解的 gbest 适应度值
createMap();
setMap(); //设置 原图的节点V[] 最小路径成本矩阵 MapCost[][] 和 路径转移矩阵 Map[][],迪杰斯特拉算法
//粒子群算法 初始化 可行解解集
//第一类 可行解,服务器直接放置在消费节点的网络节点处
for (int i = 0; i < BIRDS_STYPE1*BIRDS; i++)
{
for (int j = 0; j < D; j++)
{
bird[i][j] = U[j].Unode;
perfect[i][j] = bird[i][j];
}
}
//第二、三、四类 可行解, (消费节点处可行解 + 排序数组) 组成
int * topON = topOutNode();//排序 找出出度最大的前 Unum 个节点,并返回该数组 topON
int * topGN = topGbpsNode();//排序 找出带宽最大的前 Unum 个节点,并返回该数组 topGN
int addIndex = D/(BIRDS_STYPE4*BIRDS - BIRDS_STYPE1*BIRDS);
int birdNums = BIRDS_STYPE1*BIRDS;
for (int index = 0; birdNums < BIRDS_STYPE4*BIRDS; index+=addIndex)
{
topMN = 0;
for (int k = index; k < D+index; k++)//找出只有消费节点的网络节点位置处 放置服务器的可行解
{
int disapearNode = k % D;
bird[0][disapearNode] = bird[0][disapearNode] - (Vnum - 1);//bird[] < 0 表示该维度,不设置服务器
int costMust = countCost(serviceCost, bird[0]);//当服务器放置情况为 Node[] 数组的情况时,返回 总成本
if (costMust >= LONG_MAX_MY)
{
stackMustNode[topMN] = bird[0][disapearNode] + (Vnum - 1);
topMN++;
bird[0][disapearNode] = bird[0][disapearNode] + (Vnum - 1);
}
}
if (birdNums%4 == 0)
{
for (int j = 0; j < D; j++)
{
if (j < topMN)
bird[birdNums][j] = stackMustNode[j] - (rand()%2)*(Vnum - 1);
else
bird[birdNums][j] = topON[j-topMN] - (rand()%2)*(Vnum - 1);
perfect[birdNums][j] = bird[birdNums][j];
}
}
else if (birdNums%4 == 1)
{
for (int j = 0; j < D; j++)
{
if (j < topMN)
bird[birdNums][j] = stackMustNode[j] - (rand()%2)*(Vnum - 1);
else
bird[birdNums][j] = topGN[j-topMN] - (rand()%2)*(Vnum - 1);
perfect[birdNums][j] = bird[birdNums][j];
}
}
else
{
for (int j = 0; j < D; j++)
{
if (j < topMN)
bird[birdNums][j] = stackMustNode[j] - (rand()%2)*(Vnum - 1);
else
{
int randNode = rand() % (Vnum - 1);
bird[birdNums][j] = randNode - (rand()%2)*(Vnum - 1);
}
perfect[birdNums][j] = bird[birdNums][j];
}
}
for (int k = 0; k < D; k++)
if (bird[0][k] < 0)
bird[0][k] = bird[0][k] + (Vnum - 1);
mapSort(bird[0], bird[birdNums]);//排序 利用最小路径成本矩阵MapCost[][],将数组bird[k] 按照离 数组bird[0] 指定位置最近的顺序 进行排序
birdNums++;
}
delete [] topGN;
delete [] topON;
//第五类 可行解,服务器直接放置在消费节点的网络节点处
for (int i = BIRDS_STYPE4*BIRDS; i < N; i++)
{
for (int j = 0; j < D; j++)
{
int randNode = rand() % (Vnum - 1);
bird[birdNums][j] = randNode - (rand()%2)*(Vnum - 1);
perfect[birdNums][j] = bird[birdNums][j];
}
}
//粒子群算法 迭代 寻找最优服务器部署方案
for (int t = 0; t < TSEARCH; t++)
{
for (int i = 0; i < N; i++)
{
long gCostRes = countCost(serviceCost, bird[i]);//当服务器放置情况为 bird[] 数组的情况时,返回 总成本,即 适应度值,最小费用最大流算法
if (t == 0)
perfectFit[i] = gCostRes;
if (gCostRes < perfectFit[i])//保存当前可行解的局部最优解
{
perfectFit[i] = gCostRes;
for (int j = 0; j < D; j++)
perfect[i][j] = bird[i][j];
}
if (gCostRes < gbestFit)//保存当前可行解集的全局最优解
{
gbestFit = gCostRes;
for (int j = 0; j < D; j++)
gbest[j] = bird[i][j];
}
//待调参数,按照一定概率进行 随机变异、向局部最优解靠近、向全局最优解靠近、不变、去掉服务器 等等操作
int case1 = NRAND*serviceCost/(gCostRes-gbestFit+gCostRes-perfectFit[i]+1)+100;
int case2 = case1 + NNEGA;//*UnumServices/(gCostRes-gbestFit+gCostRes-perfectFit[i]+1)/50+100;
int case3 = case2 + NGOGBEST*((gCostRes-gbestFit)/serviceCost+1);
int case4 = case3 + NGOPERFE*((gCostRes-perfectFit[i])/serviceCost+1);
int case5 = case4 + NNEXT;
int caseS = case5 + NUNCHA*((gCostRes-gbestFit+gCostRes-perfectFit[i])/serviceCost+1);
for (int j = 0; j < D; j++)
{
int randint = rand() % caseS;
if (randint < case1)
{
bird[i][j] = rand() % (Vnum-1);
}
else if (randint < case2)
{
if (bird[i][j] >= 0)
bird[i][j] = bird[i][j] - (Vnum - 1);//取消该维度的服务器放置
//else
// bird[i][j] = bird[i][j] + (Vnum - 1);
}
else if (randint < case3)
{
if (bird[i][j] < 0 && gbest[j] >= 0)
bird[i][j] = bird[i][j] + (Vnum - 1);
else if (bird[i][j] >= 0 && gbest[j] >= 0)
bird[i][j] = Map[gbest[j]][bird[i][j]];//路径转移矩阵 Map[][],快速向全局最优解靠近
else if (bird[i][j] >= 0 && gbest[j] < 0)
bird[i][j] = bird[i][j] - (Vnum - 1);
}
else if (randint < case4)
{
if (bird[i][j] < 0 && perfect[i][j] >= 0)
bird[i][j] = bird[i][j] + (Vnum - 1);
else if (bird[i][j] >= 0 && perfect[i][j] >= 0)
bird[i][j] = Map[perfect[i][j]][bird[i][j]];//路径转移矩阵 Map[][],快速向局部最优解靠近
else if (bird[i][j] >= 0 && perfect[i][j] < 0)
bird[i][j] = bird[i][j] - (Vnum - 1);
}
else if (randint < case5)
{
if (bird[i][j] >= 0 && V[bird[i][j]].outNum > 0)
{
int kp = rand() % V[bird[i][j]].outNum;
Edge *p = V[bird[i][j]].head;
for (int k = 0; k < kp; k++)
p = p->next;
bird[i][j] = p->Vend;
}
}
}
}
now_clock = clock();//计时器
duration_time = (double) (now_clock - start_clock)/CLOCKS_PER_SEC;
if (duration_time > 60)
break;
}
using namespace std;
cout << "duration_time : " << duration_time << endl;
countCost(serviceCost, gbest);//再次运行一次最优解,刷新最优解的 费用流
//finalOut();
for (int i = 0; i < N; i++)
{
delete [] bird[i];
delete [] perfect[i];
}
delete [] bird;
delete [] perfect;
delete [] perfectFit;
delete [] gbest;
return gbestFit;
}
【我的比赛的分析共享结束啦,欢迎留言交流】
return 0;
}
附录:赛题说明.pdf