您的位置:首页 > 编程语言 > Java开发

一个止步于64强的小白对于2017年华为挑战赛的总结(java+spfa+最小代价最大流+启发式算法)

2017-04-10 21:18 555 查看
因为舍友的邀请,参加了今年华为软件精英挑战赛,最后的成绩也不太理想,没进32强,但还是写个总结在这里吧。。。

没有进入32强意味着我们只做了初赛试题,题目和数据在这里:

http://codecraft.huawei.com/home/detail

题目实际上要求给出一个服务器配置问题。在满足消费者流量需求的前提下,给出花费money最小的流量服务器安置方案。题目中给出了全部网络节点的数目、边的数目和消费节点的数目(文件中第一行数据),每台服务器的价格(第三行数据),和每条链路上的最大流量与单位流量的价格(第5行到第N行的数据),同时也给出了消费节点的编号、与消费节点相连接的网络节点的编号和消费节点的需求流量。

看到的第一眼就觉得这是一个图论的问题,然后再一本算法书上翻了下,觉得最小代价最大流很适合题目,有考虑到时间原因我最终选择了spfa算法。

网上的最小花费最大流大都是针对有向图的,对于无向图就把一条边拆成2条正边,2条反边就好啦~

于是算法流程变成了:

step 1:初始解生成

我们用了最笨的方法,直连消费节点。也就是此时有:

Cost=消费节点数目*服务器的价格

step 2:用基于spfa的minCostmaxFlow算法算出cost

step 3:启发式算法优化

至于启发式算法我原来考虑了这么几个:

模拟退火,蚁群算法,DE算法,遗传算法

很明显,模拟退火算法容易陷入局部最优,但是速度快,其余的更容易达到全局最优,但要求初试的解的数量比较多,也就是说我一次迭代就要算好几个最小费用最大流。后来我发现我的JAVA版最小费用最大流对高级用例只能跑250~300次便放弃了复杂的启发式算法,直接用的模拟退火。。。

如果有人想了解其他的算法,可以看:

http://blog.csdn.net/yezi_1026/article/details/53425072

个人觉得DE挺好的,但很少用于离散的分析问题,其实这个题也类似于整数规划或0-1规划问题,但在DF算法中变异因子F的选取就要看看了。

没有考虑遗传算法,虽然它可以灵巧的用2进制编码处理问题,但是在高维中,遗传算法收敛很慢,甚至是不收敛的。但是也不是不可以用,可以考虑降维,但我的spfa实在是太慢了。。。

关于DE的文章:http://blog.csdn.net/hp910315/article/details/50557042

模拟退火的那段的伪代码如下:

dofire(初试解)
opiter=50000000;//迭代的次数,其实我对于高级用例是用的定时器,这个可以用来处理低中级用例u
preSP=初试解
preSP.check()//这个函数用来检查是否消费节点的需求全部被满足
int iter=0;
{
newSP=changeSpPosition(preSP);
if((preNeed&&nowNeed&&preCost>nowCost)||(!preNeed&&nowNeed))//一定发生替换()
//如果现在的方案满足所有消费节点的需求,且现在方案的nowCost小,则一定替换
{
替换
if(nowCost<minCost)//目前最优的,则
记录下来
continue;
}else{
if(preNeed&&!nowNeed)//一定不发生替换
{
continue;//不替换
}else{
//按照一定的概率进行替换
int erro=nowCost-preCost;
double r=rand.nextDouble();//随机生成一个双精度型
if(r<Math.exp(-(nowCost-preCost)/T))//如果在这一范围内则替换
{
替换
continue;
}else
{
continue;//不替换
}
}
}
}


至于changeSpPosition(preSP)的函数,也就是实现服务器摆放的函数,我们也是很简单粗暴:

以很小的概率增加服务器

以很大的概率减少服务器数量(尤其是在前期)

以一定概率改变服务器的摆放位置(也就是在已有的服务器上删除一台,在原来非服务器的节点加入一台)

因为我们事先没有对图进行预处理,直接粗暴SPFA导致用例迭代次数不足(尤其是高级用例),再者JAVA本身要比C\C++慢,所以我们又进行了补偿,也就是说,我减少服务器的时候不是一台一台的减少,在前期我可以多台减少,后台再一台一台减少。后来队友又发现,每次删除提供流量最小的服务器会得到较好的结果,于是乎,我们删除的时候总是删除在目前状态中提供流量最小的。。。

源码如下:

有很多备注。。因为我记性不好,呵呵。。。

本身就是小白,所以还望大神指点,尤其是你们怎么能在85s内跑2万多次spfa的。。。膜拜中。。。

//package com.cacheserverdeploy.deploy
package realcode.ver10;
/*
* 改进之处:对changeSeverMethod()函数的删除操作进行算法上的优化
* */
import java.util.*;

public class Deploy
{
private int nodeNum,edgNum,ServerMoney,CondumerNum;
private int[] subPath;//存储每次从SPFA中的到的路径
private int Inf=99999999;//定义一个无穷大的数
private int[] head,thead,Dis;
private ArrayList<Edge> edge=new ArrayList<Edge>();
private ArrayList<int[]> CPosition=new ArrayList<int[]>();
private int Ss,St,Tb,Ts;//超级源点的位置,超级汇点的位置,超级汇点建立的起始边代号,超级汇点建立的终止边代号
private ArrayList<Edge> eee;//用于做edge的每次变化品
private HashMap<Integer,ArrayList> Adj=new HashMap<Integer,ArrayList>();
private HashMap<Integer,ArrayList> preRoud=new HashMap<Integer,ArrayList>();
private static boolean stop=false;
private static Timer timer=new Timer();//触动定时器,如果到85s则将stop改成true用来终止最优化循环
private ArrayList<Integer> minSP;
private HashMap<Integer,Integer> CPosi=new HashMap<Integer,Integer>();//为了最后转化正确路径的时候迅速
private int Sb,Se;//超级源点建立的起始边代号,超级源点建立的终止边代号
private int minNode,deteNode,minNode2,detIter;//记录删除的时候提供最小流量的节点,和最终删除的节点

public static String[] deployServer(String[] graphContent)
{
/**do your work here**/
Deploy d=new Deploy();
timer.schedule(new MyTask(), 89200);//89200
d.getBasicInformation(graphContent);
return d.changeForm(d.getRealRoud(d.dofire(d.getIntaSeverPosition())));
}
//定时器
static class MyTask extends TimerTask{

@Override
public void run() {
// TODO Auto-generated method stub
stop=true;
timer.cancel();
}

}

//初试步骤,通过传进来的数组得到路径信息
public void getBasicInformation(String[] g)
{
int layer=0;//表示是第几个空行,同时也可以推知是何种顺序,读出来的结果应该是对的
for(int i=0;i<g.length;i++)
{
if(g[i].equals("") || g[i]==null)//有空行
layer++;
int[] CN=this.changeCharToNum(g[i].toCharArray());//将刚行的文本数据转化为数组数组,CN转化的也是对的
if(layer==0)//第一行信息,第一行的数据是对的
{
this.nodeNum=CN[0];
this.edgNum=CN[1];
this.CondumerNum=CN[2];
this.initNet();//初始化网络
continue;
}
if(layer==1 && !g[i].equals(""))//服务器部署细心
{
this.ServerMoney=CN[0];//System.out.println(this.ServerMoney+"");正确
continue;
}
if(layer==2 && !g[i].equals(""))//出发点 到达点 最大容量 代价,用于构建edges
{
this.addedges(CN[0], CN[1], CN[2], CN[3]);
this.addedges(CN[1], CN[0], CN[2], CN[3]);
this.Adj.get(CN[0]).add(CN[1]);//加入到邻接表,这是因为该算法不考虑空间复杂性所以才那么做的
this.Adj.get(CN[1]).add(CN[0]);//如果算法还考虑空间复杂性,那么可以用NodewithEdges和edge的关系来做
continue;
}
if(layer==3 && !g[i].equals(""))//消费节点编号 消费节点相连节点 需要的最小代价,用于构建超级汇点
{
this.CPosition.add(new int[]{CN[0],CN[1],CN[2]});//这个初始化方法不错
this.CPosi.put(CN[1], CN[0]);
}
}
//一些基于上面的前期准备工作
this.Ss=this.nodeNum;
this.St=this.nodeNum+1;
this.buildSuperT();
}

//第一步选择初试服务器摆放位置,需要路径信息(度和链接信息)
public ArrayList getIntaSeverPosition()
{//先实现简单的,也就是在消费节点相连的节点上放一个
ArrayList<Integer> SP=new ArrayList<Integer>();//用于标示哪些位置用来摆放服务器
for(int i=0;i<this.CondumerNum;i++)
SP.add(this.CPosition.get(i)[1]);
return SP;
}

//第二步开始改进的模拟退火,传如服务器的初试的路径信息
public HashMap dofire(ArrayList SP1)
{
//要用的变量什么的一个声明
Random rand=new Random();//用于生成随机数
int T0=1000,opiter=50000000;//模拟退火的初试温度,模拟退火外循环的迭代次数
//if(this.nodeNum<200)
//opiter=4000;
if(this.nodeNum>200 && this.nodeNum<700)
opiter=1500;
double T;//模拟退火的当前温度
boolean preNeed,nowNeed=true;//记录过去和现在的方案中,所有消费节点的需求是否可以得到满足
int preCost,nowCost=Inf,minCost=Inf;//记录过去和现在方案的花费,最小花费
HashMap Road=new HashMap();//目前记录的是edge的标示
eee=this.deepCloneBaseFor(edge);//因为每次用的都是不同的,这样就不用每次都建了
thead=(int[])head.clone();
//算法正式开始
//针对第一个数据
this.buldSuperS(SP1);//根据传入的服务器位置SP1,构建超级源点
preCost=this.smallCostBigFunction()+SP1.size()*this.ServerMoney;//得到第初试服务器放置的最大流情况下的花费
preNeed=this.checkCNeed();
if(preNeed)//如果能满足条件,则先将路径记录下来
{
minCost=preCost;
Road=this.HashMapdeepclone(this.preRoud);//
//Road=t;//
minSP=(ArrayList)SP1.clone();
}
//针对以后的数据
ArrayList preSP=SP1,nowSP;//原来和现在服务器的摆放位置
//System.out.println("dofire 开始循环");
this.detIter=0;
int[] order=null;
if(this.nodeNum>700)
order=this.calServerGood(SP1.size());
int iter=0;
for(;!stop && iter<opiter;iter++)
{
T=T0/(1+iter);//当前温度

//3中改变策略(增加服务器,减少服务器,改变服务器位置)
nowSP=this.changeServerMethod(preSP,preNeed,iter,order);//因为要知道上一次是谁提供的流量最少,所以把这一条放在了重建网络的前面
//System.out.println("dofire 成功进行了一次 changeServerMethd   "+iter);//也就是问题出现在没有被替换的时候
//错误出现在,如果减少服务器操作没有成功,那么eee也发生了变换,因为我删除的时候已经用到了eee此时,eee肯定不同了,解决方案是在record里面存的不是链路索引,而是要删除的节点号
eee=this.deepCloneBaseFor(edge);//重新恢复到还没有确定服务器的状态
thead=(int[])head.clone();

this.buldSuperS(nowSP);//构建超级汇点
nowCost=this.smallCostBigFunction()+nowSP.size()*this.ServerMoney;
nowNeed=this.checkCNeed();//检查是否满足了所有服务器的要求
if((preNeed&&nowNeed&&preCost>nowCost)||(!preNeed&&nowNeed))//一定发生替换()
//如果现在的方案满足所有消费节点的需求,且现在方案的nowCost小,则一定替换
{
preCost=nowCost; preSP=nowSP;preNeed=nowNeed;//替换
if(nowCost<minCost)//如果是目前最优的,则记录下来
{
//System.out.println("dofire 替换的路径:"+nowSP+"  nowNeed="+nowNeed+"  nowCost="+nowCost);
System.out.println("第"+iter+"次迭代"+"  "+nowCost);
minCost=nowCost;
Road=this.HashMapdeepclone(preRoud);//记录下nowNeed的路径
minSP=(ArrayList)nowSP.clone();
}
if(this.detIter<=4 && this.nodeNum>700)
order=this.calServerGood(nowSP.size());//得到排序后的数组,order里面存储的是边的代号
else
order=null;
continue;
}else{
if(preNeed&&!nowNeed)//一定不发生替换
{
continue;//不替换
}else{
//按照一定的概率进行替换
int erro=nowCost-preCost;
double r=rand.nextDouble();//随机生成一个双精度型
if(r<Math.exp(-(nowCost-preCost)/T))//如果在这一范围内则替换
{
preCost=nowCost; preSP=nowSP;preNeed=nowNeed;//替换
if(this.detIter<=4 && this.nodeNum>700)
order=this.calServerGood(nowSP.size());//得到排序后的数组,order里面存储的是边的代号
else
order=null;
continue;
}else
{
continue;//不替换
}
}
}
}
System.out.println("iter="+iter+"  minCost="+minCost+"  stop="+stop);

return Road;

}

//得到真正的Road,即把负边的给去掉
public HashMap getRealRoud(HashMap old)
{
if(old.isEmpty())
return null;

eee=this.deepCloneBaseFor(edge);//因为每次用的都是不同的,这样就不用每次都建了
this.buldSuperS(minSP);

//System.out.println("getRealRoud  oldRoud:"+old.size());

int len=old.size();
HashMap<Integer,Integer> allE=new HashMap<Integer,Integer>();//用于构建Hash表
HashMap<Integer,ArrayList> realRoud=new HashMap<Integer,ArrayList>();//真正路径
for(int i=0;i<len;i++)//这个循环得到allE,也就是记录每条路径应该走多少流量
{//这个地方不应该是 i<old.size 因为 old有remove操作,会导致它的长度越来越小,导致循环过早结束
ArrayList<Integer> sub=(ArrayList<Integer>)old.get(i);//得到第i条路径
for(int j=0;j<sub.size()-1;j++)//将所有的边放在Hash表中,计算每条边实际走了多少流量,也就是去除负边的情况下一共有多少流量
{//注意,sub的最后一位是目前该条线路的流量,除最后一位的前几位,应该是节点的索引
//本来这个地方错了,在allE中,应该是不存储负边的
int idex=sub.get(j);
if(idex%2!=0)
idex=idex^1;
if(allE.get(idex)==null)//如果目前还没有存入,则初始化为0
allE.put(idex, 0);
int flow=allE.get(idex);
if((int)sub.get(j)%2==0)//(不是我们构建Ss或St加的边 && 边的标示是偶数)
flow=flow+(int)sub.get(sub.size()-1);
else//如果是负边
flow=flow-(int)sub.get(sub.size()-1);
allE.remove(idex);//将allE中的flow更新成最新的
allE.put(idex, flow);
}
}
//从allE的数据中得到路径-->构建HashMap-->根据HashMap找路径
//构建HashMap
HashMap<Integer,LinkedList> find=new HashMap<Integer,LinkedList>();
Set s=allE.keySet();
Iterator it=s.iterator();
for(;it.hasNext();)
{
int edex=(int)it.next();
int u=this.eee.get(edex).u;//头结点
int v=this.eee.get(edex).v;//尾节点
if(find.get(u)==null)//如果暂时无此头结点,则让它有孩子
{
LinkedList<int[]> son=new LinkedList<int[]>();
find.put(u, son);
}
LinkedList<int[]> temp=find.get(u);//找到头结点u对应的尾节点表
if(allE.get(edex)!=0)//因为有个118到70的路径不知道是什么鬼
temp.add(new int[]{u,v,allE.get(edex)});//在尾节点里分别存入{头结点,尾节点,allFlow}
else//因为出现有 118 但是 size=0 这就说明118加了个空的,那么应该就是这里删去,这是因为走正走负,导致有些边其实没有走
{//一直认为是后面错了,想不到。。。。
if(find.get(u).isEmpty())
find.remove(u);
}
}
//根据HashMap找路径
int reallen=0;//做realRoud的key
while(!find.isEmpty())//当find表不为空时
{
ArrayList<Integer> path=new ArrayList<Integer>();
int hh=Ss;
int min=Inf;
int[] shiyan=new int[3];
while(hh!=St)//当不是超级汇点的时候
{
int mq=((LinkedList<int[]>)find.get(hh)).getFirst()[2];
if(min>mq)
min=mq;
shiyan=((LinkedList<int[]>)find.get(hh)).getFirst();
hh=shiyan[1];
path.add(hh);
}
path.remove(path.size()-1);//将超级汇点去掉
hh=Ss;//head我已经定义为全局变量了
while(hh!=St)//根据minflow,更改路径上的allFlow,如果allFlow为0则删除,如果被删除的头结点也为空了,则也删除对应find上的一行
{
((LinkedList<int[]>)find.get(hh)).getFirst()[2]-=min;
int next=((LinkedList<int[]>)find.get(hh)).getFirst()[1];
if(((LinkedList<int[]>)find.get(hh)).getFirst()[2]==0)
{
((LinkedList<int[]>)find.get(hh)).removeFirst();//如果等于0,则删除
if(((LinkedList<int[]>)find.get(hh)).isEmpty())
find.remove(hh);//如果没有了尾节点,则删除该HashMap的head边
}
hh=next;
}
path.add(this.CPosi.get(path.get(path.size()-1)));//加入消费者的编号
path.add(min);//在path中增加minflow
realRoud.put(reallen++, path);//在realRoud中增加path
}

System.out.println("-----------测试整体供给和整体需求--------------");
int sum1=0,sum2=0;
for(int i=0;i<realRoud.size();i++)
{
ArrayList<Integer> pa=realRoud.get(i);
sum1+=pa.get(pa.size()-1);
}
for(int i=0;i<this.CondumerNum;i++)
sum2+=this.CPosition.get(i)[2];
System.out.println(" path sum--->"+sum1);
System.out.println(" real sum--->"+sum2);
System.out.println("----------------------------------------");

return realRoud;
}

//模拟退火需要的cost function最小花费最大流
public int smallCostBigFunction()
{
//前面先进行一些数据的预处理工作
//正式开始执行算法
this.preRoud.clear();//清除上一次得到的路径
//this.smallCost=0;
int minflow,minCost=0,len=0;
while(spfa(this.nodeNum+2))//这个n还需不要要加2
{
ArrayList<Integer> subpath=new ArrayList<Integer>();
minflow=Inf+1;
//求线路允许通过的最大流量,也就是这条线路中包含路径的最小cap
for(int i=subPath[St];i!=-1;i=subPath[eee.get(i).u])
{
subpath.add(i);//包含超级源点路径,也包含超级汇点路径,但是是逆序的
if(eee.get(i).cap<minflow)
minflow=eee.get(i).cap;
}
subpath.add(minflow);//将线路此时的流量信息放到最后一位
preRoud.put(len++, subpath);//记录路径,这时候记录的是  subpath里的所有信息  和 该线路上的流量
for(int i=subPath[St];i!=-1;i=subPath[eee.get(i).u])
{
eee.get(i).cap-=minflow;
eee.get(i^1).cap+=minflow;//异或操作,对应了改经过边的所对应的反边
}
minCost+=Dis[St]*minflow;//Dis中已将包含了Cost的信息,所以不用重新再考虑

}
return minCost;
}

//最小花费最大流需要的spfa算法,传入的量是超级源点的标号,超级汇点的编号,和所有的节点数目
public boolean spfa(int allNode)
{
Queue<Integer> qu=new LinkedList<Integer>();
boolean[] vis=new boolean[this.nodeNum+2];
for(int i=0;i<subPath.length;i++)
{
subPath[i]=-1;
Dis[i]=Inf;
vis[i]=false;//没有在队列里
}
Dis[Ss]=0;
qu.add(Ss);
vis[Ss]=true;
while(!qu.isEmpty())
{
int hnode=qu.remove();
vis[hnode]=false;
for(int i=this.thead[hnode];i!=-1;i=eee.get(i).next)//找到所有以hnode为头结点的边
{
int tnode=eee.get(i).v;//找到以hnode为头的弧的尾节点
//如果可以这个链路可以过,且代价小
if(eee.get(i).cap>0 && Dis[tnode]>Dis[hnode]+eee.get(i).cost)
{
Dis[tnode]=Dis[hnode]+eee.get(i).cost;
subPath[tnode]=i;//记录路径
if(vis[tnode]==false)//如果尾节点不再队列里面
{
qu.add(tnode);
vis[tnode]=true;
}
}
}
}
if(Dis[St]>=Inf)
return false;
return true;
}

//最后一步,将得到的结果转化为符合条件的字符串数组
public String[] changeForm(HashMap Roud)
{
if(Roud==null)//如果没有找到路径,则返回一个空指针
return null;
//如果找到了路径
//System.out.println("changeForm中最终找到的路径数目是:"+Roud.size());
String[] contents=new String[Roud.size()+2];//
contents[0]=Roud.size()+"";//第一行,路径数
int i=1;
for(;i<Roud.size()+2;i++)
contents[i]="";
i=2;
Set s=Roud.keySet();
Iterator it=s.iterator();
for(;it.hasNext();)
{
int k=(int)it.next();
ArrayList<Integer> everyRoud=(ArrayList<Integer>)Roud.get(k);
for(int j=0;j<everyRoud.size();j++)
{
if(j!=everyRoud.size()-1)
contents[i]+=everyRoud.get(j)+" ";
else
contents[i++]+=everyRoud.get(j)+"";//最后一个是容量,后面应该没有空格
}
}

return contents;

}

//改变服务器放置的三种策略
public ArrayList changeServerMethod(ArrayList SP1,boolean need,int iter,int[] order)
{
ArrayList SP=(ArrayList)SP1.clone();
Random rr=new Random();//生成随机量
double meths=rr.nextDouble();//确定以下三种方法的随机量
double addS,detS;
if(this.nodeNum<800)//如果不是高级样例
{
if(this.nodeNum<300)
{
addS=0.18;
detS=0.5;
}else
{
addS=0.1;//增加服务器的概率
detS=0.5;
if(iter<=300)
detS=0.72;
}
}else
{
addS=0.1;//增加服务器的概率
detS=0.8;
if(iter>200)
detS=0.7;
}
//methed 1:增加服务器数量
if(SP1.size()<this.CondumerNum && (need==false || meths<addS))
{
System.out.println("增加服务器操作");
while(true)//当没有加入成功
{
int add=rr.nextInt(this.nodeNum);//在除超级源点和超级汇点的网络节点里随机选择一个加入
if(SP.indexOf(add)==-1)//说明没有在原来的服务器中
{
SP.add(add);
break;
}
}
return SP;
}
//method 2:减少服务器器数量
if(SP.size()>1 && meths>=addS && meths<detS)
{
System.out.println("减少服务器操作");
if(this.nodeNum<700 || this.detIter>4)
{
int min=Inf;
int minN=0;
for(int i=Sb+1;i<=Se;i=i+2)
{
//System.out.print(eee.get(i).u+"("+eee.get(i).cap+")   ");
if(eee.get(i).cap<min)
{
min=eee.get(i).cap;
minN=eee.get(i).u;//真实的源点应该是超级节点的尾节点,但是因为这是负边,所以真实的是源点就是边的头结点
}
}
int del;
if(minN==minNode)//说明现在出现了删除该最小的就满足不了消费节点的需求的情况
del=rr.nextInt(SP.size());
else
{
this.minNode=minN;
del=SP.indexOf(this.minNode);
if(del==-1)//当前一步没替换换时,会出现这样的情况
del=rr.nextInt(SP.size());
}
SP.remove(del);

if(this.nodeNum>700 && iter<25)
{
int del2=rr.nextInt(SP.size());
SP.remove(del2);
}
}else
{//针对高级用例的前几次,大幅度减少节点的数量

int detNum=0;
switch(this.detIter)
{
case 0://删除50个
detNum=50;
break;
case 1://删除30个
detNum=30;
break;
case 2://删除20个
detNum=20;
break;
case 3://删除10个
detNum=10;
break;
default://删除5个
detNum=5;
break;

}
for(int gaod=0;gaod<detNum;gaod++)
{
//int dell=eee.get(order[gaod]).u;
int del2=SP.indexOf(order[gaod]);
if(del2==-1)
del2=rr.nextInt(SP.size());
SP.remove(del2);
}

this.detIter++;
}

return SP;
}
//method 3:改变服务器位置
if(meths>=detS)
{
System.out.println("改变服务器位置");
while(true)//当没有加入成功
{
int add=rr.nextInt(this.nodeNum);//在除超级源点和超级汇点的网络节点里随机选择一个加入
if(SP.indexOf(add)==-1)//说明没有在原来的服务器中
{
SP.add(add);
break;
}
}
//删除一台服务器
int min=Inf;
int minN=0;
for(int i=Sb+1;i<=Se;i=i+2)
{
//System.out.print(eee.get(i).u+"("+eee.get(i).cap+")   ");
if(eee.get(i).cap<min)
{
min=eee.get(i).cap;
minN=eee.get(i).u;//真实的源点应该是超级节点的尾节点,但是因为这是负边,所以真实的是源点就是边的头结点
}
}
int del=SP.indexOf(minN);
if(del==-1)//当前一步没替换换时,会出现这样的情况
del=rr.nextInt(SP.size());
SP.remove(del);
}
return SP;
}
////////////////////////上面主要函数所要调用的一些小函数////////////\
//将带空格,且最大是千位的书转型
public int[] changeCharToNum(char[] cc)
{
int cishu=0,len,zi=0;
int[] Num=new int[4];
for(;zi<cc.length;zi++)
{
len=0;
for(;zi<cc.length && cc[zi]!=' ';zi++)//看看这个数字有多少位
len++;
//根据len得到数字,并将该数字存入数组
int sum=0;
for(int i=0;i<len;i++)//将每个char转化为数字,并qui和
sum+=((int)(cc[zi-i-1]-48))*((int)Math.pow(10, i));
Num[cishu]=sum;
cishu++;
}
return Num;
}
//构建网络时的加边操作
public void addedges(int u,int v,int cap,int cost)
{
Edge e=new Edge(u,v,cap,cost,head[u]);//正向边
this.edge.add(e);
head[u]=(edge.size()-1);//以u为头结点的弧所在的边的位置
Edge ee=new Edge(v,u,0,-cost,head[v]);//反向边
this.edge.add(ee);
head[v]=(edge.size()-1);//以v为头结点的弧所在的边的位置
}
//构建网络时的加边操作
public void addedgesthead(int u,int v,int cap,int cost)
{
Edge e=new Edge(u,v,cap,cost,thead[u]);//正向边
this.eee.add(e);
thead[u]=(eee.size()-1);//以u为头结点的弧所在的边的位置
Edge ee=new Edge(v,u,0,-cost,thead[v]);//反向边
this.eee.add(ee);
thead[v]=(eee.size()-1);//以v为头结点的弧所在的边的位置
}
//构建网络的初始化网络操作
public void initNet()
{
this.head=new int[this.nodeNum+2];//因为肯定会有一个超级源点,一个超级汇点
this.subPath=new int[this.nodeNum+2];
this.Dis=new int[this.nodeNum+2];
for(int i=0;i<head.length;i++)
{
head[i]=-1;
subPath[i]=-1;
}
for(int j=0;j<this.nodeNum;j++)
{
ArrayList<Integer> aa=new ArrayList<Integer>();
this.Adj.put(j, aa);
}
}
//构建超级汇点
public void buildSuperT()
{
Tb=edge.size();
for(int i=0;i<this.CondumerNum;i++)
{
int c=this.CPosition.get(i)[1];
this.addedges(c, this.St, CPosition.get(i)[2], 0);
}
Ts=edge.size()-1;
this.minNode=Inf;
this.minNode2=Inf;
}
//构建超级源点
public void buldSuperS(ArrayList SP)
{
this.Sb=eee.size();
for(int i=0;i<SP.size();i++)
{
int node=(int)SP.get(i);
this.addedgesthead(Ss, node, Inf, 0);
}
this.Se=eee.size()-1;
//this.detIter=0;
}
//判断是否所有消费节点的需求都得到了满足
public boolean checkCNeed()
{
for(int i=Tb;i<=Ts;i=i+2)//因为加1之后是反边
{
if(eee.get(i).cap>0)
return false;
}
return true;
}
//HashMap的克隆方法
public HashMap<Integer,ArrayList> HashMapdeepclone(HashMap<Integer,ArrayList> Old)
{
HashMap<Integer,ArrayList> New=new HashMap<Integer,ArrayList>();
for(int i=0;i<Old.size();i++)
{
ArrayList<Integer> aa=new ArrayList<Integer>();
ArrayList<Integer> ao=(ArrayList)Old.get(i);
aa=(ArrayList)ao.clone();//不能成功拷贝的程序
New.put(i, aa);
}
return New;
}

//测试用clone方法1:重写clone方法,将ArrayList遍历,clone到底
public ArrayList deepCloneBaseFor(ArrayList n)
{
ArrayList<Edge> r=new ArrayList<Edge>();
for(int i=0;i<n.size();i++)
{
Edge e=(Edge)((Edge)n.get(i)).clone();
r.add(e);
}
return r;
}

//按服务器节点所供给的容量,对服务器节点进行排序,返回一个数组,数组里面存储的是边的代号
//采用归并排序
public int[] calServerGood(int len)
{
//System.out.println("SP.size()="+len+"   Sb="+Sb+"  Se="+Se);
int[][] record=new int[2][len];
int jt=0;
for(int i=Sb+1;i<=Se;i=i+2)
{
record[0][jt]=eee.get(i).u;//链路的索引
record[1][jt++]=eee.get(i).cap;//要排的数数据
}
int[][] temp=new int[2][len];
this.mergeSort(record, 0, len-1, temp);
int[] result=new int[len];
for(int i=0;i<len;i++)
result[i]=record[0][i];
return result;
}

public void mergeSort(int[][] a,int f,int l,int temp[][])//排序的主算法
{
if(f<l)
{
int mid=(f+l)/2;
mergeSort(a,f,mid,temp);//左边有序
mergeSort(a,mid+1,l,temp);//右边有序
this.addMerge(a,f,mid,l,temp);//合并有序数组a[f..mid]和a[mid+1...l]到temp
}
}

public void addMerge(int[][] a,int f,int mid,int l,int[][] temp)//排序用的合并算法
{
int i=f,j=mid+1,m=mid,n=l,k=0;
while(i<=m && j<=n)
{
if(a[1][i]<=a[1][j])
{
temp[0][k]=a[0][i];
temp[1][k++]=a[1][i++];
}
else
{
temp[0][k]=a[0][j];
temp[1][k++]=a[1][j++];
}
}
while(i<=m)//当前半部分多
{
temp[0][k]=a[0][i];
temp[1][k++]=a[1][i++];
}
while(j<=n)//当后半部分多
{
temp[0][k]=a[0][j];
temp[1][k++]=a[1][j++];
}
for(i=0;i<k;i++)//会写到a中,可以进行不写回的优化
{
a[0][f+i]=temp[0][i];
a[1][f+i]=temp[1][i];
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐