计算机算法设计与分析——回溯算法

基本题一:装载问题

一、实验目的与要求

1、掌握装载问题的回溯算法;

2、进一步掌握回溯算法;

二、实验题目

现有n个集装箱要装进两艘载重分别为c1,c2的船,其中第i个集装箱重w[i],并且题目保证 i=1nwi≤c1+c2  , 问是否存在一个合理的装载方案,使得这n个集装箱都装进两艘船,若有请找出一种方案。

输入第一行三个个整数nc1c2表示上述变量的值。

输入第二行n个整数W[i]代表集装箱重量。

若方案存在:

输出一行一个整数,代表载重为c1的船的装货重量,第二行n个整数,其中第i个整数为1表示第i件物品装进载重为c1的船,为0表示装进另一艘船。

若不存在方案,打印一行“no solution”

 

Simple input

10 500 121

21 54 21 45 20 65 320 1 20 54

Simple output

500

1 1 0 0 1 1 1 0 1 0

三、实现思想

将货物的选择构成树,只针对集装箱a进行装载,使所装货物的质量尽量多,且不超过集装箱的容量。

 

四、实现代码

#include<bits/stdc++.h>

using namespace std;

int bestw=0;

bool bestflag[100];

void load(int i,int r,int n,int w[],int c1,int cw,bool flag[])

{

       if(i>n)

       {

              if(cw>bestw)

              {

              bestw=cw;

              for(int j=1;j<=n;j++)

              bestflag[j]=flag[j];

              }

              return;

       }

       r-=w[i];

       if(cw+w[i]<=c1)

       {

              cw+=w[i];

              flag[i]=1;

              load(i+1,r,n,w,c1,cw,flag);

              flag[i]=0;

              cw-=w[i];

       }

       if(cw+r>bestw)

       load(i+1,r,n,w,c1,cw,flag);

       r+=w[i];

}

int main()

{

       int n,c1,c2,w[100],cw=0,r=0;

       bool flag[100];

       cin>>n>>c1>>c2;

       for(int i=1;i<=n;i++)

       {

       cin>>w[i];

       r+=w[i];

       }

       load(1,r,n,w,c1,cw,flag);

       cout<<bestw<<endl;

       for(int i=1;i<=n;i++)

       cout<<bestflag[i]<<" ";

       cout<<endl;

}

 

五、实验结果

基本题二:0—1背包问题

一、实验目的与要求

1、掌握0—1背包问题的回溯算法;

2、进一步掌握回溯算法;

二、实验题目

给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?

三、实现思想

实现思想跟装载问题很像,只是评判的标准变为了使价值最优。

四、实现代码

#include<bits/stdc++.h>

using namespace std;

int w[100],v[100],n,c,bestv=0,bestflag[100];

void load(int i,int r,int cv,int cw,int cflag[])

{

       if(i>n)

       {

              if(cv>bestv)

              {

                     bestv=cv;

                     for(int i=1;i<=n;i++)

                     bestflag[i]=cflag[i];

              }

              return;

       }

       r-=v[i];

       if(cw+w[i]<=c)

       {

              cw+=w[i];

              cv+=v[i];

              cflag[i]=1;

              load(i+1,r,cv,cw,cflag);

              cflag[i]=0;

              cv-=v[i];

              cw-=w[i];

       }

       if(cv+r>bestv&&cw+w[i+1]<c)

       load(i+1,r,cv,cw,cflag);

       r+=v[i];

}

int main()

{

       int cv=0,cflag[100],cw=0,r=0;

       cout<<"请分别输入物品数和背包容量"<<endl;

       cin>>n>>c;

       cout<<"请分别输入物品重量:"<<endl;

       for(int i=1;i<=n;i++)

       cin>>w[i];

       cout<<"请分别输入物品价值:"<<endl;

       for(int i=1;i<=n;i++)

       {

       cin>>v[i];

       r+=v[i];

       }

       load(1,r,cv,cw,cflag);

       cout<<bestv<<endl;

       for(int i=1;i<=n;i++)

       cout<<bestflag[i]<<" ";

}

 

五、实验结果

基本题三:批处理作业调度问题

一、实验目的与要求

1、掌握批处理作业调度问题的回溯算法;

2、进一步掌握回溯算法;

二、实验题目

给定n个作业的集合J = (j1,j2,j3….jn),每个作业都必须先在第一个机器执行t1时间,然后在第二个机器执行t2时间,问怎样安排作业顺序使得所有作业总的等待加执行时间总和最小。

输入第1行一个整数n表示作业的个数。

输入第2行到第n+1行每行2个整数t1t2,其中第i+1行表示第i个作业在两个机器上的执行时间。

输出一行一个整数,代表最优值。

Simple input

3

2 1

3 1

2 3

Simple output

18

 

三、实现思想

回溯加全排列

四、实现代码

#include<bits/stdc++.h>

using namespace std;

int bestf=0xffffff,bestx[100];

void dfs(int i,int n,int M[][100],int f,int f1,int f2[],int x[])

{

       if(i>n)

       {

              for(int j=1;j<=n;j++)

              bestx[j]=x[j];

              bestf=f;

       }

       else

       {

              for(int j=i;j<=n;j++)

              {

                     f1+=M[x[j]][1];

                     f2[i]=((f2[i-1]>f1)?f2[i-1]:f1)+M[x[j]][2];

                     f+=f2[i];

                     if(f<bestf)

                     {

                            swap(x[i],x[j]);

                            dfs(i+1,n,M,f,f1,f2,x);

                            swap(x[i],x[j]);

                     }

                     f1-=M[x[j]][1];

                     f-=f2[i];

              }

       }

}

int main()

{

       int M[100][100],n,f=0,f1=0,f2[100],x[100];

       cin>>n;

       for(int j=1;j<=n;j++)

       for(int k=1;k<=2;k++)

       {

              cin>>M[j][k];

       }

       for(int j=0;j<=n;j++)

       {

       x[j]=j;

       f2[j]=0;

       }

       dfs(1,n,M,f,f1,f2,x);

       cout<<bestf<<endl;

}

 

五、实验结果

 

基本题四:n后问题

一、实验目的与要求

1、掌握n后问题的回溯算法;

2、进一步掌握回溯算法;

二、实验题目

在一个n*n的棋盘上放置彼此不受攻击的n个皇后,按照国际象棋规则,皇后可以攻击与其在同一行,同一列或者同一对角线的其他皇后,求合法摆放的方案数。

输入一行包含一个整数n。

输出一行一个整数代表方案数。

Simple input

2

Simple output

2

三、实现思想

采用dfs方法,一行一行地放置,每次放置后去检查新放置的跟之前放置的有没有冲突,没有就继续放置,有就回到上一行,放置在下一列

四、实现代码

#include<bits/stdc++.h>

using namespace std;

int cnt=0,q[20];

int find(int i,int k)

{

    int j=1;

    while(j<i)  //j=1~i-1是已经放置了皇后的行

    {

        //第j行的皇后是否在k列或(j,q[j])与(i,k)是否在斜线上

        if(q[j]==k || abs(j-i)==abs(q[j]-k))

            return 0;

        j++;

    }

    return 1;

}

//放置皇后到棋盘上

void place(int k,int n)

{

    int j;

    if(k>n)

    {

           cnt++;

           return;

       } //递归出口

    else

    {

        for(j=1;j<=n;j++)   //试探第k行的每一个列

        {

            if(find(k,j))

            {

                q[k] = j;   //保存位置

                place(k+1,n);  //接着下一行

            }

        }

    }

}

int main(void)

{

    int n;

    printf("请输入皇后的个数(n<=20),n=:");

    scanf("%d",&n);

    if(n>20)

        printf("n值太大,不能求解!\n");

    else

    {

        place(1,n);        //问题从最初状态解起

        cout<<cnt<<endl;

        printf("\n");

    }

    return 0;

}





 

五、实验结果

 

基本题五:最大团问题

一、实验目的与要求

1、掌握最大团的回溯算法;

2、进一步掌握回溯算法;

二、实验题目

给定无向图G(N,E),含有n个结点,m条边。现在有以下定义:

完全子图:原图的一个子图,并且该子图是一个完全图。

团:它是一个完全子图,并且它不包含在任何其它更大的完全子图中。

最大团:包含结点最多的团。

请计算G的一个最大团。

输入第一行2个整数n,m;

接下来m行,每行两个数,u,v表示u,v结点有一条无向边。

输出2行

第一行一个整数x表示最大团中结点个数。

第二行x个整数,最大团中结点编号,若答案不唯一,打印任意一个即可。

Simple input

3 3

1 2

1 3

2 3

Simple output

3

1 2 3

三、实现思想

每次选取一个顶点,观察其与之前最大团中顶点是否有边,没有边的话,会抛弃该顶点,选择下一个顶点。右枝就是不选择该节点。

四、实现代码

#include<bits/stdc++.h>

using namespace std;

int bestx[100],bestcn=0,G[100][100];

void dfs(int i,int n,int cn,int x[])

{

       if(i>n)

       {

              for(int j=1;j<=n;j++)

              bestx[j]=x[j];

              bestcn=cn;

              return;

       }

              int OK=1;

              for(int j=1;j<i;j++) //j<i,只检查之前的结点

              if(x[j]&&G[i][j]==0)

              {

                     OK=0;

                     break;

              }

              if(OK)

              {

                     x[i]=1;

                     cn++;

                     dfs(i+1,n,cn,x);

                     x[i]=0;

                     cn--;

              }

              if(cn+n-i>bestcn)  //右枝

              {

                     x[i]=0;

                     dfs(i+1,n,cn,x);

              }

}

int main()

{

       int x[100];

       int n,m,u,v,cn=0;

       cin>>n>>m;

       for(int i=1;i<=m;i++)

       {

              cin>>u>>v;

              G[u][v]=G[v][u]=1;

       }

       dfs(1,n,cn,x);

       cout<<bestcn<<endl;

       for(int j=1;j<=n;j++)

       {

              if(bestx[j]==1)

              cout<<j<<" ";

       }

        

}

 

五、实验结果

 

基本题六:图的m着色问题

一、实验目的与要求

1、掌握图的m着色问题的回溯算法;

2、进一步掌握回溯算法;

二、实验题目

给定无向连通图G(N,E),含有n个结点,k条边。现在有m种颜色,现在要给n个结点涂色,要求相邻结点不同色。求共有多少种涂色方案。

输入第一行3个整数n,k,m;

接下来k行,每行两个数,u,v表示u,v结点有一条无向边。

输出2行

第一行一个整数x表示方案数。

Simple input

3 3 3

1 2

1 3

2 3

Simple output

6

三、实现思想

回溯dfs,不断搜索可行解

四、实现代码

#include<bits/stdc++.h>

using namespace std;

int G[100][100],cnt=0,m;

int OK(int i,int n,int color[])

{

       for(int j=1;j<=n;j++)

       {

              if(G[i][j]==1&&color[i]==color[j])

              return 0;

       }

       return 1;

}

void dfs(int i,int n,int color[])

{

       if(i>n)

       {

              cnt++;

              return;

       }     

       for(int j=1;j<=m;j++)

       {

              color[i]=j;

              if(OK(i,n,color))

              dfs(i+1,n,color);

              color[i]=0;

       }

}

int main()

{

       int n,k,u,v;

       int color[100];

       cin>>n>>k>>m;

       for(int i=1;i<=k;i++)

       {

              cin>>u>>v;

              G[u][v]=G[v][u]=1;

       }

       dfs(1,n,color);

       cout<<cnt<<endl;

}

五、实验结果

基本题六:旅行商问题

一、实验目的与要求

1、掌握旅行商问题的回溯算法;

2、进一步掌握回溯算法;

二、实验题目

给定无向图G(N,E),含有n个结点,m条边。现在有以下定义:

有一个商人从1号结点出发,希望经过每个结点一次回到起点,并且他希望走权值最小的一条路径。

输入第一行2个整数n,m;

接下来m行,每行三个数,u,v,w表示u,v结点有一条权值为w的无向边。

如果不存在这种路径,打印 -1;

否则打印两行。

第一行一个整数代表最优值,第二行n+1个点表示路径经过村庄的顺序,若答案不唯一,打印任意一个即可。

Simple input

3 3

1 2 2

1 3 4

2 3 10

Simple output  

16

1 2 3 1

三、实现思想

回溯,排列树,每次查询该节点距离上一节点是否有连接,并且加上后的当前路径长度是否小于bestcp

四、实现代码

#include<bits/stdc++.h>

using namespace std;

int n,m,u,v,w;

int a[100][100],x[100],bestx[100],bestcp=0xffff,cp=0;

void dfs(int t)

{

       if(t>n)

       {

              if(a[x[n]][1]&&a[x[n]][1]+cp<bestcp)

              {

                     bestcp=cp+a[x[n]][1];

                     for(int j=1;j<=n;j++)

                     bestx[j]=x[j];

              }

       }

       else

       {

              for(int i=t;i<=n;i++)

              {

                     if((a[x[t-1]][x[i]])&&(a[x[t-1]][x[i]]+cp<bestcp))

                     {

                            swap(x[t],x[i]);

                            cp+=a[x[t-1]][x[t]];

                            dfs(t+1);

                            cp-=a[x[t-1]][x[t]];

                            swap(x[t],x[i]);

                     }

              }

       }

}

int main()

{

       cin>>n>>m;

       for(int i=1;i<=m;i++)

       {

              cin>>u>>v>>w;

              a[u][v]=a[v][u]=w;

       }

       for(int i=1;i<=n;i++)

              x[i]=i;

       dfs(2);

       cout<<bestcp<<endl;

       for(int i=1;i<=n;i++)

       cout<<bestx[i]<<" ";

       cout<<bestx[1];

       return 0;

}

五、实验结果

 

 

基本题七:圆的排列问题

一、实验目的与要求

1、掌握旅行商问题的回溯算法;

2、进一步掌握回溯算法;

二、实验题目

给定n个大小不等的圆,c1,c2,c3….cn,现将n个圆排进一个矩形框中,要求各圆与底边相切,现在求n个圆该怎么排列才能使得矩形最短且能容下n个圆。

输入包含两行:

第一行一个整数n代表圆的个数。

第二行n个整数表示圆的半径。

输出一行一个浮点数x表示最小矩形长度,结果保留三位有效数字。

 

Simple input

3

1 1 2

Simple output

7.657

 

三、实现思想

回溯,排列树,计算圆心横坐标依据计算相切圆两圆心之间的距离

四、实现代码

#include<bits/stdc++.h>

using namespace std;

int n;

double r[100],x[100],bestr[100];

double minlen=10000;

double center(int t)

{

       double temp=0;

       for(int j=1;j<t;j++)

       {

              double value=x[j]+2.0*sqrt(r[t]*r[j]);

              if(value>temp)

              temp=value;

       }

       return temp;

}

void compute()

{

       double low=0,high=0;

       for(int j=1;j<=n;j++)

       {

              if(x[j]-r[j]<low)

              low=x[j]-r[j];

              if(x[j]+r[j]>high)

              high=x[j]+r[j];

       }

       if(high-low<minlen)

       {

              minlen=high-low;

              for(int i=1;i<=n;i++)

              bestr[i]=r[i];

       }

}

void dfs(int t)

{

       if(t>n)

       {

              compute();

       }

       else

       {

              for(int i=t;i<=n;i++)

              {

                     swap(r[t],r[i]);

                     double centerx=center(t);

                     if(centerx+r[1]+r[t]<minlen)

                     {

                            x[t]=centerx;

                            dfs(t+1);

                     }

                     swap(x[t],x[i]);

              }

       }

}

int main()

{

       cin>>n;

       for(int i=1;i<=n;i++)

       cin>>r[i];

       dfs(1);

       cout<<minlen<<endl;

}

五、实验结果

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值