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

因为舍友的邀请,参加了今年华为软件精英挑战赛,最后的成绩也不太理想,没进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];
        }
    }
}



点赞