算法与设计实验总报告(1-4)

 算法设计与分析实验报告

 

专  业

软件工程

班  级

  

姓  名

  

学  号

  

实验名称

实验一:递归与分治算法设计

实验目的

1.掌握递归与分治策略的基本思想。

2.通过设计求解给定问题的递归算法和分治算法学会使用递归和分治法解决问题的一般技巧。

实验内容

1.二分搜索问题:设a[0:n-1]是已排好序的数组。试改写二分搜索算法,使得当搜索元素x不在数组a中时,返回小于x的最大元素的位置i和大于x的最小元素的位置j;当搜索元素x在数组a中时,返回x在数组中的位置,此时i和j相同。

2.假币识别问题:一个袋子里有n个硬币,其中一枚是假币,假币和真币外观一模一样,仅凭肉眼无法区分,但是已知假币比真币轻一些。试设计识别假币的分治算法。

算法描述

1.二分搜索问题的解题思路或算法思想:

二分搜索问题是运用分治策略来解决,基本思想是将n个元素分成个数大致相同的两半,取a[n/2]与x进行比较,如果x=a[n/2],则找到x,算法终止,如果x<a[n/2],则只要在数组a的左半部继续搜索x,如果x>[n/2],则只要在数组a的右半部继续搜索x。

算法步骤:                                                          

  1. 首先需要定义二分查找BinarySearch函数,此函数的参数为数组a,数组长度n,待查找的数字x
  2. 先定义left=0,right=n-1,此处即是二分查找中的分治策略,从两段开始
  3. 设置循环,当left<=right,进入循环
  4. 判断,如果在数组中可以查到数字x,那么输出此时的位置,i和j的值相等,如果在数组中无法查到数字x,再次判断,如果x大于middle位置的值,那么left再次基础+1,反之right=mid-1,依次循环
  5. 最后写主函数,输出i和j的值

 

  1. 假币识别问题的解题思路或算法思想:

    将这n个硬币分成两等份,然后放到天平的两端,则假币在较轻的那一端;

然后将较轻的那一端的硬币再分成2等份,然后再放到天平的两端进行比较,假币还是在较轻的那一段;

直到最后只剩下两个硬币了,分别放到天平的两端,轻的哪一个就是假币。或者最后可能剩下3个硬币,我们可以将这3个硬币中任意拿出来一个,然后将剩下的两个放到天平的两端,如果天平是平的,则说明拿出来的那个硬币就是假币;如果天平不是平的,则轻的那一端是假币。  

   算法步骤:

  1. 首先定义寻找假币的函数,如果只剩下两个银币,那么较轻的那个就是假的
  2. 如果是银币的总个数是偶数个,则逐步的一分为二,把查找一堆银币的问题,变成比较两份,左右两份等重,则无假币,如果奇数个银币,则比较除中间银币外的两等份,左右两份等重,则无假币,如果两份等重,中间银币较轻,则中间银币为假币,否则,返回较轻那份中的假币
  3. 然后定义再将较轻的一侧中银币等分为两份,重复上述做法。
  4. 直到剩下两枚银币,便可用天平直接找出假银币。   

 

程序及运行结果

1.二分搜索问题的程序:

实例:

 #include<iostream>

using namespace std;

 bool BinarySearch(int *a,int n,int x,int& i,int& j){

     int left=0;

     int right=n-1;

     while(left<=right){

         int mid=(left+right)/2;

         if(x==a[mid]){

             i=j=mid;

             return true;

         }

 

         if(x>a[mid])

             left=mid+1;

         else

             right=mid-1;

     }

     i=right;

     j=left;

     return false;

 }

 int main(){

     int n,i,j,a[1000],x;

     while(cin>>n){

         for(i=0;i<n;i++)cin>>a[i];

         cin>>x;//输入待查数据

         BinarySearch(a,n,x,i,j);

         cout<<"通过更改过的二分查找所找到的i,j分别为: "<<'('<<i<<','<<j<<')'<<'\n';

 

} }

 

 

 

 

 

 

 

结果:

第一次测试为如果可以在集合中找到x的情况

第二次测试为如果无法在集合中找到x的情况

 

2.假币识别问题的程序:

 

实例:#include<iostream>

using namespace std;

 

int find_false(int a[],int low,int high)

{

    int i=0;

    int re=0;

    int sum1,sum2,sum3;

    sum1=sum2=sum3=0;

    if(low+1==high)

    {

        if(a[low]<a[high])

        {

            re=low;

            return re;

        }

        else

        {

            re=high;

            return re;

        }

    }

    if((high-low+1)%2==0)

    {

        for(i=low;i<=low+(high-low+1)/2-1;i++)

        {

            sum1+=a[i];

        }

        for(i=low+(high-low+1)/2;i<=high;i++)

        {

            sum2+=a[i];

        }

        if(sum1<sum2)

        {

            re=find_false(a,low,low+(high-low+1)/2-1);

            return re;

        }

        else if(sum2<sum1)

        {

            re=find_false(a,low+(high-low+1)/2,high);

            return re;

        }

    }

    else

    {

        for(i=low;i<=low+(high-low)/2-1;i++)

        {

            sum1+=a[i];

        }

        for(i=low+(high-low)/2+1;i<=high;i++)

        {

            sum2+=a[i];

        }

        sum3=a[low+(high-low)/2];

        if(sum1<sum2)

        {

            re=find_false(a,low,low+(high-low)/2-1);

            return re;

        }

        else if(sum2>sum1)

        {

            re=find_false(a,low+(high-low)/2,high);

            return re;

        }

        else if(sum1+sum3==sum2+sum3)

        {

            re=(high-low)/2+1;

            return re;

        }

    }

}

int main()

{

    int a[100];

    int n,i,re;

    cout<<"请输入硬币的个数:";

    cin>>n;

    cout<<"请分别输入这个"<<n<<"个硬币的重量:"<<endl;

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

    {

        cin>>a[i];

    }

    re=find_false(a,1,n);

    cout<<"假币是第"<<re<<"硬币"<<endl;

    return 0;

}

 

 

 

结果:

 

 

总结

实验心得体会

通过这次试验我亲身体验了递归方法解决实际的问题,对于实验当中的查找以及假币寻找,都是生活中常见的问题,而如何快捷的解决就是算法的价值所在,这两个实例的验证,让我掌握了一些递归方法解决问题的一般技巧熟悉二分搜索等算法掌握分治算法思想加深了对分治法的理解,收获很大

改进意见:

    这一次的实验给我最大的启示是,算法的学习是一个循序渐进的过程,而且需要不断的积累和锻炼,才能够得以提升,算法刚拿到的时候给人一种很迷离的感觉,不知道从何下手,需要耐心的多读几遍,并且仔细的思考,才可以逐步得出完美的代码方案

 

 

 

 

 

 

 

实验成绩:                   指导教师:                     年  月  日

                                                                                                                                                                                                                                

 算法设计与分析实验报告

 

专  业

软件工程

班  级

  

姓  名

  

学  号

  

实验名称

实验二:动态规划算法设计

实验目的

1.掌握动态规划解决问题的一般过程。

2.通过使用动态规划算法求解给定问题,学会使用动态规划解决实际问题。

实验内容

1.天平平衡问题:已知一个天平左右两端共有n个挂钩,且有m个不同质量的钩码,求将钩码全部挂到钩子上使天平平衡的方法的总数。试设计求解该问题的动态规划算法。

2.数塔问题 :对于诸如下图的数塔,若从顶层走到底层,每一步只能走到相邻的结点,经过的结点的数字之和最大的路径。设计求解该问题的动态规划算法。

算法描述

  1. 天平平衡问题:

   要求的是,m个砝码全部挂在钩子上使得天平平衡,所以这m个钩码,只可以分配到天平的左右两边,以来使得左右平衡,即使得左右两边的钩码重量之和相等;所以分析得,左边或者右边的钩码重量之和是全部钩码重量之后的二分之一,再次分析原问题就变成了,通过数组组合,使m个数字分成两组,并这两组数字和相等,数字即来表示钩码的重量

  1. 数塔问题

   这个问题要求从顶部出发在每一个节点可以选择向左或者向右走,一直走到底层,要求找出一条路径,使得路径上的数字之和最大.

  利用动态规划自底向上的方法从最底层开始比较。

从顶点出发时到底向左走还是向右走应取决于是从左走能取到最大值还是从右走能取到最大值,只要左右两道路径上的最大值求出来了才能作出决策。同样的道理下一层的走向又要取决于再下一层上的最大值是否已经求出才能决策。这样一层一层推下去,直到倒数第二层时就非常明了。

算法具体步骤:先倒推:dp[i][j]保留的是从后向前运行到a[i][j]位置时候,路过(i,j)最大的路径长度。不断保留运行到当前位置时候的路径长度的最大值。那么dp[0][0]即为整个路径的最大值。

再正推:减去上一个值,通过与下一行的dp值比较,相等即为同一路径

总公式为dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + data[i][j],最后的结果保存在dp[0][0]中。

 

程序及运行结果

  • 天平平衡问题
  1. 程序代码

package tianping;

 

public class tianping {

 public static void main(String[] args) {

  int m = 15; //全部钩码的重量之和的二分之一

    int n = 10; //钩码的数量    int a[] = {10,9,8,7,6,5,4,3,2,1};

    int h[] =new int[100];

    h[0]=1;

    

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

        for (int j = m; j >=1; j--) {

            if(j>=a[i-1]){

                h[j]=h[j]+h[j-a[i-1]];

            }

        }

    }

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

      System.out.print(h[j]+" ");

}

}

  1. 运行结果

  • 数塔问题
  1. 程序代码

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.util.Scanner;

 

public class B {

 

    

    public static void main(String[] args) {

           

        Scanner in = new Scanner(System.in);

        int n = in.nextInt();

        long max = 0;

        int[][] dp = new int[n][n];

        dp[0][0] = in.nextInt();

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

            for (int j = 0; j <= i; j++) {

                int num = in.nextInt();

                if (j == 0)

                    dp[i][j] = dp[i - 1][j] + num;

                else

                    dp[i][j] = Math.max(dp[i - 1][j - 1], dp[i - 1][j]) + num;

                max = Math.max(dp[i][j], max);

            }

        }

        System.out.println(max);

    }

 

}

 

 

  1. 代码截图

 输入样例:

 

 

总结

实验心得体会:

动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。

动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

通过这节实验课,我更深入了动态规划算法,以前只是从书本上只看教材上的理论和实例,但是通过真正的实际应用才可以把理论付诸于实验,才能让脑海里所学会的算法更加应用于生活

改进意见:

在解决天平问题和数塔问题时候,我最大的纠结在于一开始的思考,最后通过询问算法老师解决了自己的问题,发现了老师在思考问题的时候会从概念和理论出发,而不是胡乱的从各个方向杂乱无章的进行算法分析,所以更正了我的解题思路

 

 

实验成绩:                   指导教师:                     年  月  日

                                                                                                                                                                                                                                

 算法设计与分析实验报告

 

专  业

软件工程

班  级

  

姓  名

  

学  号

  

实验名称

实验三:贪心算法设计

实验目的

1. 掌握贪心法解决问题的一般步骤。

2. 通过设计与实现贪心算法求解给定问题,学会使用贪心法解决实际问题。

实验内容

1. 均分纸牌问题:有N堆纸牌,编号分别为1,2,…,n。每堆上有若干张,但纸牌总数必为n的倍数。可以在任一堆上取若干张纸牌,然后移动。移牌的规则为:在编号为1上取的纸牌,只能移到编号为2的堆上;在编号为n的堆上取的纸牌,只能移到编号为n-1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。

2. 线段覆盖问题:在一维空间中存在N条线段,每条线段的起始坐标与终止坐标已知,要求求出这些线段一共覆盖了多大的长度。

算法描述

1. 均分纸牌问题的解题思路或算法思想:

    此问题可以利用贪心算法求解

    移动最小步骤实现完成当前堆牌数等于均值,就是从邻近堆借牌,在借牌时产生的负数问题,说明邻近堆牌数不足,需要从远处借调;把多于均值的理解为高处,把小于均值的理解为低处,在牌从高处走向低处时,如果有重复移动,如a,b,c相邻,b堆移动1张到c堆,a堆移动3张到b堆再从b到c堆,b堆到c堆移动了两次牌,这种情况是可以避免的,即先由a堆移动3张到b,再由b移动1张到c,移动产生负数的问题等价于上面第一种情况,说明需要至少两次移动,而这种移动可以简化到一次移动,其实n堆牌,最少的移动次数最大是n-1;可以使用贪心算法的关键在于从同一方向到某个点的多次移动可以简化成一次移动

    首先求得每堆牌的期望值,也就是总牌数与牌堆数的比值,或称为平均值,从第一堆开始比较,如果当前堆的牌数不等于平均值那么可以取当前堆的下一堆中的牌进行“补齐”

算法步骤:

从第一堆牌开始处理,如果第一堆牌整好是avg那么就放在一边不管了。如果第一堆牌不是avg,那么就要把第二堆牌(合法的移动只有从2移到1,这也是这个算法的精髓之处)移动几张到第一堆,恰好使第一堆等于avg,从而只考虑第二堆开始到第N堆为止这些堆如何搞的子问题。然后依次递归下去。这里的一个小技巧是认为牌数可以为负数,这样才能继续下去。

 

  1. 线段覆盖问题的解题思路或算法思想:

给定一些线段,线段有起点和终点,求这些线段的覆盖长度,重复的部分只计算一次。排序后寻找所有线段的覆盖的长度和。

将线段按其坐标进行排序(排序的具体方法:按起始坐标排,起始坐标相同的按终止坐标排,都是小在前大在后),使之依次递增,并按顺序分别编号a代表其起始坐标,b代表其终止坐标。然后依次对终止坐标进行比较,并把覆盖长度依次累加,最终求出最大长度。  

   算法步骤:

   首先定义两个数组a,b分别存储起始坐标和终止坐标,并按照起始坐标排好序,定义一个Max变量存储覆盖长度。然后用for循环对终止坐标进行比较,如果b[i]大于b[j],则对覆盖长度进行累加,直到最后为止,所得的Max就是最大覆盖长度。

 

 

 

程序及运行结果

1. 均分纸牌问题的程序:

import java.util.Scanner;

public class B {

    public static void main(String[] args) {

        Scanner scan=new Scanner(System.in);

        int n,sum=0,count=0;

        int average=0;

        int a[]=new int [102];

        n=scan.nextInt();

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

        {

            a[i]=scan.nextInt();

            sum=sum+a[i];

        }

        average=sum/n;

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

            a[i]-=average;

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

        {

            if(a[i]!=0)

            {

                a[i+1]+=a[i];

                count++;

                a[i]=0;

            }

        }

        System.out.println(count);

    }

}

实例:

 

 

 

 

 

结果

   

 

  1. 线段覆盖问题的程序:

   public class B

{

   public static void main(String[] args)

   {

       int a[]={2,3,4,5,6,7,8,9,10,11};

       int b[]={3,5,7,6,9,8,12,10,13,15};     

       int Max=b[0]-a[0];    //附初值     

       for(int i=1,j=0;i<10;i++)

       {

           if(a[i]>=b[i])

           {

               Max+=(b[i]-a[i]);

               j=i;

           }

           else

           {

              if(b[i]<=b[j])

              continue;

              else

              {

                 Max+=b[i]-b[j];

                 j=i;    

              }

           }

        }

        System.out.println("这些线段覆盖的最大长度为"+Max);

   }

}  

    

例:

结果:

 

总结

实验心得体会:

    贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。

贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关

    通过这次实验,我深入了解了贪心算法的基本概念和两个要素,熟悉了贪心算法解决问题的基本步骤,并解决实际问题。

改进意见:

    通过这次的实验,我深刻的体会到了,贪心算法不是对所有问题都能得到整体最优解,关键是贪⼼心策略的选择,选择的贪⼼策略必须具备⽆后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。

    贪心算法随着算法的进行,将积累起其它两个集合:一个包含已经被考虑过并被选出的候选对象,另一个包含已经被考虑过但被丢弃的候选对象。有一个函数来检查一个候选对象的集合是否提供了问题的解答。该函数不考虑此时的解决方法是否最优。还有一个函数检查是否一个候选对象的集合是可行的,也即是否可能往该集合上添加更多的候选对象以获得一个解。和上一个函数一样,此时不考虑解决方法的最优性。选择函数可以指出哪一个剩余的候选对象最有希望构成问题的解。最后,目标函数给出解的值。

为了解决问题,需要寻找一个构成解的候选对象集合,它可以优化目标函数,贪婪算法一步一步的进行。起初,算法选出的候选对象的集合为空。接下来的每一步中,根据选择函数,算法从剩余候选对象中选出最有希望构成解的对象。如果集合中加上该对象后不可行,那么该对象就被丢弃并不再考虑;否则就加到集合里。每一次都扩充集合,并检查该集合是否构成解。如果贪婪算法正确工作,那么找到的第一个解通常是最优的。

 

实验成绩:                   指导教师:                     年  月  日

                                                                                                                                                                                                                                

 算法设计与分析实验报告

 

专  业

软件工程

班  级

  

姓  名

  

学  号

  

实验名称

实验四:回溯与分支限界算法设计

实验目的

1.掌握回溯法解决问题的一般步骤。

2.学会使用回溯法解决实际问题。

3.掌握分支限界法解决问题的基本思想。

4.学会使用分支限界法解决实际问题。

实验内容

1. 骑士游历问题(采用回溯法):在国际象棋的棋盘(8行×8列)上放置一个马,按照“马走日字”的规则,马要遍历棋盘,即到达棋盘上的每一格,并且每格只到达一次。若给定起始位置(x0,y0),编程探索出一条路径,沿着这条路径马能遍历棋盘上的所有单元格。

2. 行列变换问题(采用分支限界法):给定两个m´n方格阵列组成的图形A和图形B,每个方格的颜色为黑色或白色,如下图所示。行列变换问题的每一步变换可以交换任意2行或2列方格的颜色,或者将某行或某列颠倒。上述每次变换算作一步。试设计一个算法,计算最少需要多少步,才能将图形A变换为图形B。

算法描述

1. 骑士游历问题的解题思路或算法思想:

根据骑士走日字,所以在每一个扩展结点有两种选择,要么选择走竖"日",或者走横"日,结合具体的问题可能会有四种走法,但是可以给定约束条件使得效率更高这样会减少一些解,规定只能向右走,所以抽象一下骑士游历就是一个四叉树的搜索过程。

如果选择的方向k=0,表示可能的8种走向都试探不通,不通的原因是走向超出了棋盘范围,或当前位置已经被马访问过。此时马已无路可走,说明前几步走得不对,应该退回去重新换一种走法,这种逐步试探的设计思想称为回溯算法。

算法步骤:

游历问题的本质是,在其可走的八个方向中搜索最短子树,因为其走日字的特点,所以每个位置最多只有八种走法,排除越界及走过的路找出其中可走方向最少的路走,这样容易找出出路。马从棋盘上的某一初始位置(x0,y0)开始,每次选择一个方向k,向前走一格,直到走完64格为止。每走一格,设置数组中相应格的元素值为马走的步数。

为每个方向测定一个值――可通路数,它表示该位置上还有多少条通路。在每一格上对8个方向都进行试探,并分析比较,下一步应该选择可通路数值最小的方向走。

 

  1. 行列变换问题的解题思路或算法思想:

此问题采用的是先进先出队列式分支限界法

在当前节点(扩展节点)处,先生成其所有的儿子节点(分支),然后再从当前的活节点(当前节点的子节点)表中选择下一个扩展节点。为了有效地选择下一个扩展节点,加速搜索的进程,在每一个活节点处,计算一个函数值(限界),并根据函数值,从当前活节点表中选择一个最有利的节点作为扩展节点,使搜索朝着解空间上有最优解的分支推进,以便尽快地找出一个最优解。分支限界法解决了大量离散最优化的问题。

队列式分支限界法将活节点表组织成一个队列,并将队列的先进先出原则选取下一个节点为当前扩展节点。

算法步骤:

输入数据,将计算出的最少变换次数和相应的变换序列输出。第1 行是最少变换次数。从第2 行开始,每行用4 个数表示一次变换。

 

 

程序及运行结果

1. 骑士游历问题的程序:

   import java.util.Scanner;  

public class B {

    

    private boolean Travel(int firstX, int firstY, int[][] board) {  

        // 对应骑士可走的8个方向  

        int[] movex = { -2, -1, 1, 2,  2,  1, -1, -2 };  

        int[] movey = {  1,  2, 2, 1, -1, -2, -2, -1 };  

  

        // 下一步出路的位置  

 int[] nextStepX = new int[board.length];  

 int[] nextStepY = new int[board.length];  

  

        // 记录出路的个数  

 int[] exitS = new int[board.length];  

        int nextX = firstX;  

        int nextY = firstY;  

        board[nextX][nextY] = 1;  

  

for (int m = 2; m <= Math.pow(board.length, 2); m++) {  

              

     //初始化下一个位置可走的位置的数目  

            for (int i = 0; i < board.length; i++) {  

                exitS[i] = 0;  

            }  

              

            int count = 0;  

            // 试探8个方向  

            for (int i = 0; i < 8; i++) {  

      int temI = nextX + movex[i];  

      int temJ = nextY + movey[i];  

  

                // 走到边界,路断  

if (temI < 0 || temI > 7 || temJ < 0 || temJ > 7) {  

          continue;  

                }  

  

                // 记录下可走的方向  

                if (0 == board[temI][temJ]) {  

         nextStepX[count] = temI;  

         nextStepY[count] = temJ;  

           count++;  

                }  

            }  

  

            // 到这里,cout表示当前点有几种走法。nextStep中存储各种走法的坐标。  

            int min = -1;  

            if (count == 0) {  

                return false;  

            }  

  

            if (1 == count) {  

                min = 0;  

            } else {

           for (int i = 0; i < count; i++) {  

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

 {  

      int temI = nextStepX[i] + movex[j];  

      int temJ = nextStepY[i] + movey[j];  

                        if (temI < 0 || temI > 7 || temJ < 0 || temJ > 7) {  

                            continue;  

                        }  

                          

                        // 记录下这个位置可走的方向数  

                        if (0 == board[temI][temJ]) {  

                            exitS[i]++;  

                        }  

                    }  

                }  

  

                int tem = exitS[0];  

                min = 0;  

  

 // 从可走的方向中,寻找最少走的出路  

       for (int i = 1; i < count; i++) {  

                    if (tem > exitS[i]) {  

                        tem = exitS[i];  

                        min = i;  

                    }  

                }  

            }  

  

            // 得到最少的出路  

            nextX = nextStepX[min];  

            nextY = nextStepY[min];  

            board[nextX][nextY] = m;  

        }  

  

        return true;  

    }  

  

    public static void main(String[] args) {  

  

        int firstX, firstY;  

        System.out.println("输入起始点(0-7):");  

        Scanner scanner = new Scanner(System.in);  

  

        firstX = scanner.nextInt();  

        firstY = scanner.nextInt();  

        int[][] board = new int[8][8];  

        B knight = new B();  

  

        if (knight.Travel(firstX, firstY, board)) {  

            System.out.println("游历完成:");  

        } else {  

            System.out.println("游历失败!\n");  

        }  

  

        for (int i = 0; i < board.length; i++) {  

            for (int j = 0; j < board[0].length; j++) {  

                if (board[i][j] < 10) {  

      System.out.print(" " + board[i][j]);  

                } else {  

                    System.out.print(board[i][j]);  

                }  

                System.out.print(" ");  

            }  

            System.out.println();  

        }  

    }  

}

实例:

结果:

 

2. 行列变换问题的程序:

   import java.util.LinkedList;

import java.util.Scanner;

class graph{

static int sour, dest;//sour是图形的初始整数,dest是图形的目的整数

static int ans[]=new int[1<<16];//静态变量(即全局变量),用于存放图形变换的路径

int m=4,n=4,x;

int row[]=new int[4];

int col[]=new int[4];

void setx(int x){

this.x=x;

}

int getx(){

return this.x;

}

 

void rowx(){//将一个整数划分成四行二进制

int y;

for(int i=0;i<m;i++){

y=1;

row[i]=0;

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

if((x&1)!=0) //如果x的最低位是1

row[i]|=y;

y<<=1;

x>>=1;

}

}

}

 

void colx(){//将一个整数划分成四列二进制

int y;

for(int j=0;j<n;j++) col[j]=0;

y=1;

for(int i=0;i<m;i++){

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

if((x&1)!=0) //如果x的最低位是1

col[j]|=y;

x>>=1;

}

y<<=1;

}

}

 

void rowy(){//将四行二进制转换成一个整数

int  z=1, x=0, y;

for(int i=0;i<m;i++){

y=row[i];

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

if((y&1)!=0) //如果y的最低位是1

x|=z;

z<<=1;

y>>=1;

}

}

this.x=x;

}

 

void coly(){//将四列二进制转换成一个整数

int  z=1, x=0, y;

for(int i=0;i<m;i++){

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

if((col[j]&1)!=0) //如果y的最低位是1

x|=z;

z<<=1;

col[j]>>=1;

}

}

this.x=x;

}

 

void Swaprow(int i, int j){//将二进数进行行互换

int o;

o=row[i];

row[i]=row[j];

row[j]=o;

}

 

void Swapcol(int i, int j){//将二进数进行列互换

int o;

o=col[i];

col[i]=col[j];

col[j]=o;

}

 

void reveR(int k){//将二进数进行行颠倒

int y=0, z=1<<(4-1);

for(int j=0;j<4;j++){

if((row[k]&1)!=0) //如果y的最低位是1

y|=z;

z>>=1;

row[k]>>=1;

}

row[k]=y;

}

 

void reveC(int k){//将二进数进行列颠倒

int y=0, z=1<<(4-1);

for(int j=0;j<4;j++){

if((col[k]&1)!=0) //如果y的最低位是1

y|=z;

z>>=1;

col[k]>>=1;

}

col[k]=y;

}

 

int rowswap(int x, int i, int j){//将二进制数的第i行与第j行互换

this.x=x;

rowx();

Swaprow(i,j);

rowy();

return this.x;

}

 

int colswap(int x, int i, int j){//将二进制数的第i列与第j列互换

this.x=x;

colx();

Swapcol(i,j);

coly();

return this.x;

}

 

int revrow(int x, int k){//将二进制数的第K行颠倒

this.x=x;

rowx();

reveR(k);

rowy();

return this.x;

}

 

int revcol(int x, int k){//将二进制数的第K列颠倒

this.x=x;

colx();

reveC(k);

coly();

return this.x;

}

 

}

public class B {

 

public static void main(String[] args){

final int Maxsize=1<<16;

graph  gN;//用于进行行变换、列变换、行颠倒、列颠倒

int E,N;//变换前的初始值,变换前的结果值

gN=new graph();

int hash[]=new int[1<<16];

int i,j,h=0;char c;

graph G1=new graph();

//初始化,输入初始值和目标值,即1010010000101010和1010000001100101

Scanner scanner = new Scanner(System.in);  

String ss=scanner.nextLine();

char[]chArrs=ss.toCharArray();

for(graph.sour=i=0;i<16;i++){

c=chArrs[i];

graph.sour|=(int)(c-'0')<<i;

}

String sd=scanner.nextLine();

char[]chArrd=sd.toCharArray();

for(graph.dest=i=0;i<16;i++){

c=chArrd[i];

graph.dest|=(int)(c-'0')<<i;

}

 

LinkedList  queue=new LinkedList();//初始化先进先出队列

for(int k=0; k<Maxsize;k++)hash[k]=-1;

G1.x=graph.sour;

hash[G1.x]=0;

queue.add(G1.x);

while(!queue.isEmpty()){//以先进先出队列式实现分支限界法

E=(int)queue.removeFirst();

 

for(i=0;i<4-1;i++){//行变换

for(j=i+1;j<4;j++){

gN.x=gN.rowswap(E,i,j);

N=gN.x;

if(hash[N]==-1){

hash[N]=hash[E]+1;

graph.ans[N]=E;

queue.add(N);

}

}

}

for(i=0;i<4-1;i++){//列变换

for(j=i+1;j<4;j++){

gN.x=gN.colswap(E,i,j);

N=gN.x;

if(hash[N]==-1){

hash[N]=hash[E]+1;

graph.ans[N]=E;

queue.add(N);

}

}

}

for(i=0;i<4;i++){//行颠倒

gN.x=gN.revrow(E,i);

N=gN.x;

if(hash[N]==-1){

hash[N]=hash[E]+1;

graph.ans[N]=E;

queue.add(N);

}

}

for(i=0;i<4;i++){//列颠倒

gN.x=gN.revcol(E,i);

N=gN.x;

if(hash[N]==-1){

hash[N]=hash[E]+1;

graph.ans[N]=E;

queue.add(N);

}

}

 

if(hash[graph.dest]!=-1){//如果目的值被遍历到,则退出循环

System.out.println("OK");break;

}

 

 

}

System.out.println(hash[graph.dest]);

output(graph.dest);//输出变换的路径

 

 

 

 

}

public static void outb(int x){//将一个整数以四行二进制的形式显示

for(int i=0; i<4;i++){

for(int j=0;j<4;j++){

if((x&1)!=0)System.out.print(1);

else System.out.print(0);

x/=2;

}

System.out.println();

}

}

public static void output(int N){

if(N==graph.sour){

System.out.println();

outb(N);

return;

}

output(graph.ans[N]);//graph.ans[N]存放着从初始值到目的值的遍历路径

System.out.println();

outb(N);

}

}

实例:

结果:

 

 

总结

实验心得体会:

通过这次实验,我明白了回溯法是属于穷举的子集,对于许多的问题,当我们需要找到某些解集或者满足某种要求的最优解的时候,回溯法往往是很朴素和简单的一种算法。从数学的角度来说,比较适合一些组合数较大的问题,比如五子棋博弈算法,最短路径或者最小代价等,使用回溯算法往往会得到正确的答案,但是回溯算法由于是采用穷举的方式,所以运行时间一般是很大的,当然可以通过各种优化手段来降低,而许多的问题,回溯法是最合适,有可能是唯一的解决方式。

而分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。

改进意见:

通过这两次试验,我明白了分支限界法和回溯法的异同点:

相同点:二者都是一种在问题的解空间树T上搜索问题解的算法。

不同点:1.在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。2.回溯法与分支-限界法对解空间的搜索方式不同,回溯法通常采用尝试优先搜索,而分支限界法则通常采用广度优先搜索。3.对节点存储的常用数据结构以及节点存储特性也各不相同,除由搜索方式决定的不同的存储结构外,分支限界法通常需要存储一些额外的信息以利于进一步地展开搜索。

 

实验成绩:                   指导教师:                     年  月  日

                                                                                                                                                                                                                                

 

猜你喜欢

转载自blog.csdn.net/nanaz11/article/details/86484655
1-4