上一篇博客 关于矩形排样问题(二) 给出了矩形排样问题的背景,并对遗传算法做出了详细的介绍。但是没有给出具体的解决方法,这里针对矩形排样问题,给出基于遗传算法的解法。

利用率的定义

谈到矩形排样问题,必然涉及到板材的利用率问题,这里的利用率定义如下:
假设n种小矩形Ri的面积分别为Si(i=1,2,…,n),数量分别为ni(i=1,2,…,n),宽为wi,高为li,以 (wi,li)表示,则要下料的矩形毛坯总数 ,设原料板材宽为 W,高为L ,以 (W,L)表示,矩形件优化排样的目标是,找到一种下料这个矩形毛坯的方法,使得“一刀切”后余料面积 尽可能大,以使得余料可以用来继续加工成可用的新零件,如下图所示,这样达到板材利用率

尽可能大。

布局规则

还要考虑的一个问题就是布局规则,主要有以下几种方式:定位规则、组合规则和邻接规则

  • 定位规则

    待排矩形件零件编号确定后,接下来的工作就是确定被选待排炬形件在布局
    空间中的摆放位置,总体有以下3种:
    ①占角策略,即将待排矩形件摆放在布局空间的某一角;
    ②顺放策略,即从布局空间的某一角开始,将待排矩形件沿着布局空间的
    某一边摆放;
    ③在底盘装载问题中,先将待排矩形件沿布局空间的四边放置,最后摆放
    布局空间的中心。
    考虑到钢结构排样的实际约束条件,本文采用的是第1种先占左下角的定位规则。

  • 组合规则
    为提高工业生产的加工效率,在设计布局方案时应考虑组合矩形。组合矩形是由相同长宽的矩形件以同一种排放方式顺次排在同一层上而形成的。组合矩形的4种情况,如下图所示:

  • 邻接规则
    领接规则是指将长宽相同或相近的矩形件放在一起进行排料。
    规则1:将长或宽相同的矩形相邻排放.规则1的3种情况如下图所示。

    规则2:将长或宽相近的矩形相邻排放.规则2的3种情况如下图所示。

    一般在排样过程即将结束,当(剩余矩形总面积)/(板材面积)小于一定比例时采用领接规则。尤其对于最后一块板材的排料,采用领接规则能更进一步地提高排样布局的合理性,减少材料浪费。

解决方案

对于矩形排样问题,一个简单的方法就是根据各种长度限制,列出相应的公式,进行暴力搜索,但这种方法的时间复杂度较高,在计算量小的情况下还可以接受,但是在实际的工程应用中用处不大。这里利用最低轮廓线的思想,其核心想法是采用最佳匹配搜索策略来确定矩形件的最佳排放位置,即搜索最低轮廓线中轮廓线的长度与待排矩形件的宽度差值最小的一条轮廓线,直至所有矩形件排样完毕。为了满足实际工程中“一刀切”的要求,需要对其进行修改,这里结合分层的思想。分层排样的思想体现的是一种剪切排样的方式,满足“一刀切”的工艺约束,它是将矩形零件按一定的顺序排放,在排放下一零件时若当前层剩余空间不足,则以当前层所排矩形零件的最高水平线为基准开辟新层。传统的分层排样方式是用水平线将板材分成多层的,排样方式如下图所示:

下面进入解决问题的正题:结合遗传算法解决排料问题。采用遗传算法对一刀切矩形排料问题进行求解,在排料过程中引入了改进的最低轮廓线算法思想。上一篇博客对遗传算法进行了详细的介绍,这里不再赘述。总结来说,GA仿照染色体的基因在进化的过程中进行选择交叉变异生成下一代种群。 计算开始时对种群进行初始化,并计算每一个个体的适应度函数,生成新的一代。如果生成的种群不满足优化条件,则按照适应度选择个体,父代进行交叉或变异生成子代,然后子代取代父代,再生成下一个子代。这一过程循环执行,直到满足优化准则为止。下面给出各个步骤的具体实施方案:
问题编码:
个体编码是n 个小矩形Ri 的下标 1,2,3,…,n的一个排列如下图所示

初始种群:
设种群中个体的数量为P_SIZE ,随机产生 P_SIZE个个体Xi,Xi 的基因序列是 1,2,3,…,n的一个排列。
定义适应值函数:
对于个体Xi 的基因序列对应 n个小矩形 的一个排列:按照上述的排料方法, 依次布局在板材上,得到板材的利用率r(i) 。显然板材的利用率r(i) 越大,个体 Xi对应的布局就越优。 因此定义个体的适应值函数为:

遗传选择操作:
计算个体的生存概率
在父代种群中按轮盘赌的选择方式选择生存概率大的个体进入子代。
遗传交叉操作:
以交叉概率 Pc选择 2个个体 Xi和 Xj进行交叉操作。随机选择一个交叉点,在交叉点处,交换 2个个体后半部分得到 2个新的个体Xi’ 和 Xj’, 如下图所示:

很显然,交叉操作可能产生非法个体,即个体中有重复的基因。所以必须对 2个新个体的基因进行调整。

  • 第1步:找出个体Xi’ 的重复基因1、2、4,个体 Xj’的重复基因9、7、8;
  • 第2步:将1、3、4与9、7、8对应交换,得到了2个合法的新个体Xi” 和 Xj” 如下图所示:

    遗传变异操作:
    以变异概率Pm 选择某一个体进行变异操作。若按普通的变异操作也会产生不合法的个体,所以要设计一种新的变异算子。

  • 第1步:在要进行变异的个体中,随机选择2个基因位;

  • 第2步:将所选择 2个基因位上的基因值交换,得到 1个新个体。

    在每代的遗传操作过程中,保存最优个体进入子代,这样至少使子代不会比父代差。同时加入一些新品种,即随机产生一些新个体替换老个体,以防止陷入僵局。整个遗传算法的流程图如下图所示:

编码实现

下面给出具体的编码实现。对于遗传算法的实现网上有一大堆解法,编程语言也各式各样,典型的编程工具包是MATLAB,研究得很透彻,很成熟,已经封装成函数,直接调用传入相应的参数就可以得到结果。这里给出基于VC的编码方法。
整个窗体设计比较简陋,几个Button和文本编辑框以及图形控件用来显示排样的结果。

下面只给出核心部分的实现(程序写得比较粗糙,只实现了算法,细节的部分还待完善,呵呵)。
首先在主窗体中声明表示个体的结构体:

typedef struct//针对遗传算法定义的一个个体{int gene[1000]; double fitness;double rfitness;double cfitness;}GenoType;

以及作为原材料的矩形板材:

typedef struct //矩形板{int l; int w;int x;int y; //矩形的右下角int flag;}RectAA;typedef struct //矩形板{int l; int w;int x;int y;//矩形的左上角}RectBB;

考虑到算法实现的可视化,中间大部分区域用于显示排样的结果,以彩色矩形块的方式显示出来,实现比较简单,不是重点。
下面是遗传算法实现的核心,这里只给出代表核心操作的几个函数:

//基因换位
void CMyDlg::swap(int *a, int *b)
{int temp;temp=*a;*a=*b;*b=temp;
}//随机数
int CMyDlg::IntGenerate()
{int RANGE_MIN=0;int RANGE_MAX=m_Snumber;int randpop=(rand()%1000/1000.0)*RANGE_MAX;return randpop;
}//选择种群
void CMyDlg::SecletMember()
{int *a=new int[m_Snumber];int x1,x2,j=0,temp1;for(int i=0; i<m_Snumber; i++){   a[j]=i;j++;}for(int j=0;j<m_NumAll;j++)    {for(int i=0;i<1000;i++)                //100次交换足以产生各种结果了{x1=IntGenerate();x2=IntGenerate();if (x1!=x2){temp1=a[x1];a[x1]=a[x2];a[x2]=temp1;}}for(int i=0;i<m_Snumber;i++)population[j].gene[i]=a[i];}if(m_flag2){for(int i=0;i<m_Snumber;i++)population[m_NumAll].gene[i]=i;population[m_NumAll].fitness=Valuecount(population[m_NumAll]);}
}//适应性
void CMyDlg::evaluate()
{   int j=0;for(int i=0;i<(m_NumAll*5000000/m_GenS);i++)//1000控制界面速度{   if(0==i%(m_NumAll*50000/m_GenS)){   population[j].fitness=Valuecount(population[j]);j++;}}}//评价
double CMyDlg::Valuecount(GenoType node)
{  int rl=rect[0].x;for (int i=0;i<m_Snumber;i++){  rectdraw[i]=recto[node.gene[i]];}OnDrawfirst();OnDrawbest();for (int i=1;i<m_Snumber;i++){if(rect[i].x>=rl)rl=rect[i].x;}return 1.000*rl/m_width;
}//选种
void CMyDlg::KeepTheBest()
{int mem,i;curbest=0;  for(mem=1;mem<m_NumAll;mem++)   if(population[mem].fitness<population[curbest].fitness)curbest=mem;if(population[curbest].fitness<population[m_NumAll].fitness)population[m_NumAll]=population[curbest];//获得当前世代里的最好基因序列,并保存在当前世代的最后一个染色体中
}//传代
void CMyDlg::elitist()
{   int i;double best,worst;int best_mem=0,worst_mem=0;best=population[0].fitness;worst=population[0].fitness;for(i=1;i<m_NumAll;i++){if(population[i].fitness<=best){best=population[i].fitness;best_mem=i;}if(population[i].fitness>=worst){worst=population[i].fitness;worst_mem=i;}}if(best<=population[m_NumAll].fitness) //后一个体不如前一个体,就不要动前一世代{for(i=0;i<m_Snumber;i++)population[m_NumAll].gene[i]=population[best_mem].gene[i];population[m_NumAll].fitness=best;    }else                            //否则{for(i=0;i<m_Snumber;i++)population[worst_mem].gene[i]=population[m_NumAll].gene[i];     population[worst_mem].fitness=population[m_NumAll].fitness;}
}void CMyDlg::SecletBetter()
{int mem,i;double sum=0;double *x=new double[m_NumAll];double p;int p1,p2;for(mem=0;mem<m_NumAll;mem++)sum+=population[mem].fitness;for(mem=0;mem<m_NumAll;mem++)    x[mem]=sum-population[mem].fitness;sum=0;for(mem=0;mem<m_NumAll;mem++)sum+=x[mem];for(mem=0;mem<m_NumAll;mem++) //以对总体的贡献来确定其在种群中的相对适应度population[mem].rfitness=(double)x[mem]/sum;population[0].cfitness=population[0].rfitness;for(mem=1;mem<m_NumAll;mem++)                         {population[mem].cfitness=population[mem-1].cfitness+population[mem].rfitness;}  for(i=0;i<m_NumAll;i++){p=rand()%1000/1000.0;if(population[0].cfitness>p) //适者生存newpopulation[i]=population[0];else{for(int j=0;j<m_NumAll;j++)//弱肉强食if(p>=population[j].cfitness && p<population[j+1].cfitness)newpopulation[i]=population[j+1];}}for(i=0;i<m_NumAll;i++)population[i]=newpopulation[i];
/*  for(i=0;i<100;i++)       //随机PK{p1=(rand()%1000/1000.0)*100;p2=(rand()%1000/1000.0)*100;if(p1!=p2){if (population[p1].rfitness>population[p2].rfitness){population[p2]=population[p1];}elsepopulation[p1]=population[p2];}}
*/delete [] x;
}void CMyDlg::crossover()//交叉
{int i,j;int min,max,flag;double x;for(i=0;i<m_Snumber;i++){   x=rand()%1000/1000.0;if(x<m_pxCross){min=0;max=0;while(min==0)min=IntGenerate();while(max==0)max=IntGenerate();if(max<min){int temp;temp=max;max=min;min=temp;}flag=max;for(j=min;j<=(max+min)/2;j++)//从min到max倒序{swap(&population[i].gene[j],&population[i].gene[flag]);flag=flag-1;}}}
}void CMyDlg::mutate()//变异
{int i;int x1,x2;double x;   for(i=0;i<m_Snumber;i++){    x=rand()%1000/1000.0;if(x<m_pMutation){x1=0;x2=0;while(x1==0)x1=IntGenerate();while(x2==0)x2=IntGenerate();swap(&population[i].gene[x1],&population[i].gene[x2]);}}
}//优化函数
void CMyDlg::OnProcess()
{   UpdateData(TRUE);m_flag1=TRUE;generation=0;srand(time( NULL ) ); //取系统时间为随机种子SecletMember();evaluate();KeepTheBest();SetTimer(1,10,NULL);}//数据初始化
void CMyDlg::InitData(CString filename)
{   CString str;DataFile.Open(filename,CFile::modeRead);int i=0;DataFile.ReadString(str);while(str!=_T("")){  str.TrimLeft(' ');Data[i][0]=atoi(str.Left(str.Find(' ')));str=str.Right(str.GetLength()-str.Find(' ')-1);Data[i][1]=atoi(str);DataFile.ReadString(str);i++;}m_Snumber=i;DataFile.Close();for (i=0;i<m_Snumber;i++){rect[i].l=Data[i][0];rect[i].w=Data[i][1];recto[i]=rect[i];}sum=0;for (i=0;i<m_Snumber;i++){sum+=rect[i].l*rect[i].w;//根据面积计算利用率}if (sum>m_height*m_width)MessageBox("超出原始板料!请重新输入");for(i=0;i<m_Snumber;i++)//长宽统一{   int a,b;a=rect[i].l;b=rect[i].w;rect[i].l=max(a,b);rect[i].w=min(a,b);}/*  int max0,max1,max2,max3;for (i=9;i>0;i--){  for (int j=9;j>9-i;j--){if (rect[j][0]>rect[j-1][0])//按面积预先排列{ max0=rect[j][0];rect[j][0]=rect[j-1][0];rect[j-1][0]=max0;max1=rect[j][1];rect[j][1]=rect[j-1][1];rect[j-1][1]=max1;max2=rect[j][2];rect[j][2]=rect[j-1][2];rect[j-1][2]=max2;max3=rect[j][3];rect[j][3]=rect[j-1][3];rect[j-1][3]=max3;}}}
*/empty[0].l=m_width;empty[0].w=m_height;empty[0].x=0;empty[0].y=m_height;
}//画图
void CMyDlg::OnDrawfirst()
{   //  m_flag0=TRUE;
//  Invalidate(TRUE);UpdateData(TRUE);for(int i=0;i<m_Snumber;i++){rect[i]=rectdraw[i];}empty[0].l=m_width;empty[0].w=m_height;empty[0].x=0;empty[0].y=m_height;for(int i=1;i<m_Snumber+1;i++){empty[i].l=2000;empty[i].w=2000;empty[i].x=2000;empty[i].y=2000;}CClientDC dc(this);if(m_height!=0 &&m_width!=0){CRect r;GetClientRect(r);r.left=r.left+21;r.right=r.right-130;r.bottom=r.bottom-18;r.top=r.bottom-m_height*r.Width()/m_width;dc.Rectangle (&r);}elseMessageBox("长宽不能为0!");m_flag0=TRUE;
}//绘制竞争胜利的个体
void CMyDlg::OnDrawbest()
{int num=0;int flag1;//横竖while (num!=m_Snumber){  for(int i=0;i<num+1;i++ ){flag1=CalcuRate(empty[i],rect[num]);if(3==flag1);if(1==flag1){   rect[num].x=empty[i].x+rect[num].w;rect[num].y=empty[i].y-rect[num].l;empty[num+1].l=empty[i].l-rect[num].w;empty[num+1].w=empty[i].w;empty[num+1].x=empty[i].x+rect[num].w;empty[num+1].y=empty[i].y;empty[i].l=rect[num].w;empty[i].w=empty[i].w-rect[num].l;empty[i].y=empty[i].y-rect[num].l;OrderEmpty();rect[num].flag=flag1;break ;}if(0==flag1){   rect[num].x=empty[i].x+rect[num].l;rect[num].y=empty[i].y-rect[num].w;empty[num+1].l=empty[i].l-rect[num].l;empty[num+1].w=empty[i].w;empty[num+1].x=empty[i].x+rect[num].l;empty[num+1].y=empty[i].y;empty[i].l=rect[num].l;empty[i].w=empty[i].w-rect[num].w;empty[i].y=empty[i].y-rect[num].w;OrderEmpty();rect[num].flag=flag1;break ;}}num++;}DrawBest();}//绘图
void CMyDlg::DrawBest()
{CClientDC dc(this);for (int i=0;i<m_Snumber;i++){if(rect[i].l!=0 && rect[i].w!=0){   COLORREF color;color=RGB((int)1.00*i*250/m_Snumber+20,(int)1.00*i*255/2*m_Snumber+50,(int)1.00*i*255/3*m_Snumber+50);CRect r;if (rect[i].flag==0){r.left=Transformx(rect[i].x-rect[i].l);r.top=Transformy(rect[i].y+rect[i].w);r.right=Transformx(rect[i].x);r.bottom=Transformy(rect[i].y);dc.Rectangle(r);dc.FillSolidRect(r.left+1,r.top+1,r.Width()-2,r.Height()-2,color);}if (rect[i].flag==1){r.left=Transformx(rect[i].x-rect[i].w);r.top=Transformy(rect[i].y+rect[i].l);r.right=Transformx(rect[i].x);r.bottom=Transformy(rect[i].y);dc.Rectangle(r);dc.FillSolidRect(r.left+1,r.top+1,r.Width()-2,r.Height()-2,color);}           }}}//计算比率
int CMyDlg::CalcuRate(RectBB empty,RectAA rect)
{if((empty.w>=rect.l && empty.l>=rect.w) && (empty.w>=rect.w && empty.l>=rect.l))return 1;if((empty.w>=rect.l && empty.l>=rect.w) && (empty.l<rect.l || empty.w<rect.w))return 1;if((empty.w>=rect.w && empty.l>=rect.l) && (empty.l<rect.w || empty.w<rect.l))return 0;if((empty.l<rect.l || empty.w<rect.w) && (empty.l<rect.w || empty.w<rect.l))return 3;
}//边界x处理计算
int CMyDlg::Transformx(int x)
{   int xx;CRect r;GetClientRect(r);r.left=r.left+21;r.right=r.right-130;r.bottom=r.bottom-18;r.top=r.bottom-m_height*r.Width()/m_width;xx=int(r.left+x*r.Width()/m_width+0.5);return xx;
}//边界y处理计算
int CMyDlg::Transformy(int y)
{int yy;CRect r;GetClientRect(r);r.left=r.left+21;r.right=r.right-130;r.bottom=r.bottom-18;r.top=r.bottom-m_height*r.Width()/m_width;yy=int(r.bottom-y*r.Width()/m_width+0.5);return yy;}//排序处理
void CMyDlg::OrderEmpty()
{ RectBB mid;for (int i=m_Snumber;i>0;i--){for (int j=m_Snumber;j>m_Snumber-i;j--){if (abs(empty[j].x-0)<abs(empty[j-1].x-0)){ mid=empty[j];empty[j]=empty[j-1];empty[j-1]=mid;}}}}//利用率计算
void CMyDlg::OnTimer(UINT nIDEvent)
{if(generation<m_GenS){  generation++;SecletBetter();crossover();mutate();evaluate();elitist();}else{   KillTimer(TRUE);CString str,str1;str.Format("板材利用率:%.2f%%",100*sum/(Valuecount(population[m_NumAll])*m_height*m_width));MessageBox(str);m_flag1=TRUE;m_flag2=FALSE;}CDialog::OnTimer(nIDEvent);
}//绘图
void CMyDlg::DrawTheLast(GenoType node)
{int rl=rect[0].x;for (int i=0;i<m_Snumber;i++){  rectdraw[i]=recto[node.gene[i]];}OnDrawfirst();OnDrawbest();
}

下面给出测试的例子:

测试例子的输入以文件的方式存放,输入如下测试例子:

 400 230400 160340 130246 100350 100170 110220 120420 280330 220290 170230 200210 170240 130260 150180 160200 180245 160210 200220 140250 170300 300350 320320 280

在原始板料长宽分别为2000、1000的情况下,点击优化布局后,结果如下:

注:这里的板材利用率,不是上述定义的利用率,因为上述定义的利用率很合理,但是确实不好算,需呀手工计算。为了简单起见,算的是切割矩形总面积/原材料面积。。。呵呵
工程源码
下载

关于矩形排样问题(三)相关推荐

  1. 基于链表和禁忌搜索启发式算法实现非一刀切二维矩形排样算法

    一.二维矩形排样问题介绍   二维矩形排样问题可以简单理解为:给定一个矩形的材料,需要从上面切割出多个尺寸不同的小矩形,应该如何切割才可以使得材料的利用率最高. 二.方法介绍   小编觉得求解该问题的 ...

  2. 矩形排样 matlab,二维多阶段矩形剪切排样算法(精).pdf

    二维多阶段矩形剪切排样算法(精) 第32卷第5期 计算机应用与软件 Vol32No.5 5 2015年5月 ComputerApplicationsandSoftware May201 二维多阶段矩 ...

  3. 【数学建模】【matlab】二维矩形排样代码实现

    题目来源及衍生背景 题目来源于2021年mathorcup数学建模比赛D题第一题, 最初时间紧迫,采用randchoose()函数随机选取一款订单,有点类似于蚁群算法,只是缺少了"信息素&q ...

  4. PCB拼板之多款矩形排样算法实现--学习

    参考资料:<一种新型pcb合拼求解过程> 拼版合拼问题描述和求解过程 合拼问题描述 Pcb合拼问题是通过二维矩形组合排样而演化与扩展而形成的一种新拼版问题,把每个零件都看成一个规则的矩形进 ...

  5. 矩形件排样 matlab,一种矩形件优化排样的切割式填充方法与流程

    本发明涉及板材下料排样优化 技术领域: ,具体涉及一种矩形件优化排样的切割式填充方法. 背景技术: :二维矩形件原料排样问题是具有最高计算复杂性的一种NP完全问题.优化问题,是指将一系列规格大小不一的 ...

  6. 二维排样规则算法php,一种实用的二维不规则零件排样算法

    I5JKI5L 您的论文得到相关企业家品评 一种实用的二维不规则零件排样算法 !"#$%&'()*('+!,)-./#0)*/1!+1-&*)23-45.6*3#/0*-/! ...

  7. 冲压模具设计——排样与定距设计方法

    冲压模具设计--排样与定距设计方法 排样方案对材料的利用率.冲压加工的工艺性以及模具的结构和寿命等有着显著的影响.具统计,在冲压件的成本中,材料当浪费所占比例在60%以上.因此,合理排样对提高材料利用 ...

  8. c++ 快排优化(三数取中法)

    快排优化(三数取中法) 文章目录 快排优化(三数取中法) 前言 一.三数取中法 二.递归思想 三.程序实现过程(代码) 1.取基准数(三数取中) 2.快速排序(递归) 总结 前言 作为刚刚入门c和c+ ...

  9. 套料排版代码python_【黑科技】CAD内自动套料,排样,排版功能终于被攻克了!!

    本帖最后由 AA纯水乐 于 2019-6-22 11:42 编辑 8 w7 a5 k# T. a$ V v! a% }- {$ K8 t6 T  [- k * J$ F" h% P6 W1 ...

最新文章

  1. 实例1 -- 判断输入年份是否为闰年
  2. html改变下拉框的大小,调整屏幕大小时,HTML导航栏下拉框内容无法正确调整大小...
  3. eda可视化_5用于探索性数据分析(EDA)的高级可视化
  4. SQL Server -- SQLserver 存储过程执行错误记录到表
  5. python对比柱状图_python 绘制分组对比柱状图
  6. Java面试题以及答案精选(架构师面试题)-Spring专题
  7. 【回归损失函数】L1(MAE)、L2(MSE)、Smooth L1 Loss详解
  8. Hadoop3.2.0使用详解
  9. PHP - 如何处理文件名乱码
  10. HDU 4511 小明系列故事——女友的考验 (AC自动机 + DP)题解
  11. Anylogic------------数据库
  12. Unity3D学习 ④ Unity导入商店资源,实现基本的奔跑、攻击动作切换与交互
  13. Mirth Connect 快速安装
  14. 运营方法 - 运营的思考方法
  15. 多项式求值秦九韶算法
  16. 23种设计模式及解释(中英文对照), 以及有实例源码参考
  17. GYM 101350 M. Make Cents? ( STL
  18. 第1讲:暴力破解--利用计算机执行速度
  19. 【MySQL】数据库表操作
  20. [UWP]使用Acrylic(亚克力)

热门文章

  1. 巨变来了,金融大数据平台走向何方
  2. 支付宝WAP支付总结
  3. vuejs java_[Java教程]Vuejs的一些总结
  4. 论文笔记 Reinforced Feature Points: Optimizing Feature Detection and Description for a High-Level Task
  5. 题目描述:把下面数组的首尾两个元素互换 <br>var arr = [“鹿晗“,“王俊凯“,“蔡徐坤“,“彭于晏“,“周杰伦“,“刘德华“,“赵本山“];
  6. MySQL数据迁移到新目录_mysql数据目录迁移
  7. docker mysql数据迁移
  8. 2080Ti深度学习环境配置及常用软件安装
  9. 打印一个我们熟知的乘法口诀表!
  10. 项目经理最头痛的问题:项目计划执行不到位,难以跟踪进度