基于搜索的算法

一、实验目的

1 理解搜索树与解空间树的区别

2 理解回溯法\分支限界法\穷举搜索的区别

3 理解回溯法的特征(基于解空间树\纵向优先\剪枝) 和 分支限界法的特征(基于解空间树\横向优先\剪枝)

4掌握回溯法的简单实现和时间复杂度分析

  • 实验内容

1、图着色问题

2、0/1背包问题

三、问题分析

1、图着色问题:

      回溯法求解图着色问题时,首先把所有顶点的颜色初始化为0,然后依次为每个顶点着色,如果当前顶点着色没有冲突,则继续为下一个顶点着色,否则,为当前顶点着下一个颜色,如果所有m种颜色都试探过并且不发生冲突,则回溯到当前顶点的上一个顶点,以此类推;

2、0/1背包问题

      回溯法求解0/1背包问题时,不断的去试探每个重量的物品,然后如若发生冲突,就是重量已经超过背包了,那就回溯到上一个物品;

四、问题解决

1、图着色问题

      (1)、算法:回溯法求解图着色问题GrapghColor

                输入:图G=(V,E),m种颜色

                输出:n个顶点的着色情况color[n]

                过程:1、将数组color[n]初始化为0;

                            2、i=0;

                            3、当i>=0为顶点i着色:

                                  3.1、依次考查每一种颜色,若顶点i的着色与其他顶点的着色不发生冲突,则转步骤3.2;否则,搜索下一颜色;

                                  3.2、如果color[i]大于m,重置顶点i的着色情况,i=i-1,转步骤3回溯;

                                  3.3、若顶点i是一个合法的着色且顶点尚未全部着色,则i=i+1,转步骤3处理下一个顶点;

                                  3.4、若顶点已全部着色,则输出数组color[n],算法结束;

      (2)、代码:

       
 void GraphColor(int m,int [][]arc,int []color,int n){/*m种颜色,n个顶点,数组arc[]存储图,color[]存储颜色*/

    int i,j;

    for (i=0;i<n;i++){

        color[i]=0;

    }

    for (i=0;i>=0;){

        color[i]=color[i]+1;

        while (color[i] <=m && OK(i,arc,color)==1)

            color[i]=color[i]+1;

        if (color[i]>m) color[i--]=0;

        else if (i<n-1) i=i+1;

        else {

                System.out.println(Arrays.toString(color));

        }

    }

}

int OK(int i,int [][]arc,int []color){ /*判断着色是否发生冲突*/

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

        if (arc[i][j]==1 && color[i]==color[j]){

            return 1;

        }

    }

    return 0;

}

该算法的时间复杂度为:O(m^n)

      (3)、

2、0/1背包问题:

      (1)、算法:回溯法求解0/1背包问题

                输入:物品的重量w[n],价值p[n],背包容量C

                输出:获得的最大价值

                过程:1、将数组x[n]全部初始化为-1;

                            2、i=0;

                         3、当i>=0时对物品i进行选择:

                                  3.1、x[i]=x[i]+1,试探装入物品i,计算cw和cp

                                  3.2、如果物品i试探失败,则x[i]=-1,i=i-1,转步骤3进行回溯;

                                  3.3若数组x[i]构成部分解,则i=i+1,转步骤3考查下一个物品;

                                  3.4、若数组x[i]构成全部解,更新变量bestP

                            4、输出bestP

      (2)、代码:

public class Zero_One {

    private static int[] p;//物品的价值数组

    private static int[] w;//物品的重量数组

    private static int c;//最大可以拿的重量

    private static int count;//物品的个数



    private static int cw;//当前的重量

    private static int cp;//当前的价值

    static int bestp;//目前最优装载的价值

    private static int r;//剩余物品的价值



    private static int[] cx;//存放当前解

    private static int[] bestx;//存放最终解



    public static int Loading(int[] ww, int[] pp, int cc) {

        //初始化数据成员,数组下标从1开始

        count = ww.length - 1;

        w = ww;

        p = pp;

        c = cc;

        cw = 0;

        bestp = 0;

        cx = new int[count + 1];

        bestx = new int[count + 1];



        //初始化r,即剩余最大价格

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

            r += p[i];

        }



        //调用回溯法计算

        BackTrack(1);

        return bestp;

    }



    /**

     * 回溯

     *

     * @param t

     */

    public static void BackTrack(int t) {

        if (t > count) {//到达叶结点

            if (cp > bestp) {

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

                    bestx[i] = cx[i];

                }



                bestp = cp;

            }

            return;

        }



        r -= p[t];

        if (cw + w[t] <= c) {//搜索左子树

            cx[t] = 1;

            cp += p[t];

            cw += w[t];

            BackTrack(t + 1);

            cp -= p[t];//恢复现场

            cw -= w[t];//恢复现场



        }



        if (cp + r > bestp) {//剪枝操作

            cx[t] = 0;//搜索右子树

            BackTrack(t + 1);

        }

        r += p[t];//恢复现场

    }





    public static void main(String[] args) {

        //测试

        int[] w1 = {0, 15, 25, 40, 20, 15, 24};

        int[] p1 = {0, 10, 5, 20, 2, 14, 23};

        int c1 = 30;

        Loading(w1, p1, c1);

        System.out.println("最优装载为:" + bestp);

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

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

        }

    }

}

该算法时间复杂度:O(n*2^n)

      (3)、截图:

五、实验结果总结

回答以下问题:

  1. 请从你实现的级别中选择一题,按照你的实现绘制搜索树。

  1. 你觉得回溯法和蛮力搜索有相同点吗?有不同点吗?请举例说明。

回溯法实际上一个类似穷举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”(即回退),尝试别的路径。

回溯法搜索解空间时,通常采用两种策略【剪枝策略】避免无效搜索,提高回溯的搜索效率:

用 约束函数 在扩展结点处剪除不满足约束的子树;

用 限界函数 剪去得不到问题解或最优解的子树。

蛮力法是一种简单直接地解决问题的方法,通常直接基于问题的描述和所涉及的概念定义,找出所有可能的解。然后选择其中的一种或多种解,若该解不可行则试探下一种可能的解。

  1. 你觉得回溯法能用递归实现吗?如果能,请把你实现的一道题目改为回溯法的递归程序。

可以,
 

/** 递归实现回溯 (打印出所有的可行解)
     *  算法缺陷:当找不到构成素数环的数时,无任何结果
     * @param num   从数组num中 填充素数环的n个位置
     * @param k 第k个位置(解分量)
     */
    public void getAllPrimeCircleByBackTrace(int[] num,  int k){
        if(k >=n ){
            for (int c : circle) {
                System.out.println(c);
            }

            return;
        }

        int len = num.length;
        for (int i = 0; i < len; i++) {
            circle[k] = num[i];

            if(checkPrimeCircle(k) == 1) { //第k个分量的取值满足约束条件,则解决第k+1个分量
                getPrimeCircleByBackTrace(num,k+1);
                //break;
            } else{
                circle[k] = 0;
            }
        }
    }

    /** 递归实现回溯 (打印出所有的可行解)
     *  算法缺陷:当找不到构成素数环的数时,无任何结果
     * @param num   从数组num中 填充素数环的n个位置
     * @param k 第k个位置(解分量)
     */
    private boolean flag = false;

    public void getPrimeCircleByBackTrace(int[] num,  int k){

        if(k >=n ){
            for (int c : circle) {
                System.out.println(c);
            }
            flag = true;
            return;
        }

        int len = num.length;
        for (int i = 0; i < len; i++) {
            circle[k] = num[i];

            if(checkPrimeCircle(k) == 1) { //第k个分量的取值满足约束条件,则解决第k+1个分量
                getPrimeCircleByBackTrace(num,k+1);

                if(flag) break;
            } else{
                circle[k] = 0;
            }
        }
    }
    /**
     * 循环实现回溯法求解素数环问题
     * @param num
     *
     */
    public void getPrimeCircleByBackTraceLoop(int[] num){
       int k = 0;//表示解分量素数环中的第k个位置
       int[] location = new int[n]; //将素数环每个位置存放的数的坐标保留
       int len = num.length;
        for (int i = 0; i < n ; i++) {
            circle[i] = num[0];
            location[i]=0;
        }

        int i = 0;
       while( k >= 0){
           //对第k个位置(解分量)在num中试探逐一取值
           i = location[k];
           for (; i < len ; i++) {
               circle[k] = num[i];

               if(checkPrimeCircle(k) == 0 ) { //第k个分量取当前值不满足约束,试探num中下一个值
                   circle[k] = 0;
                   continue;
               }
               else { //第k个分量的取当前值满足约束条件,就退出num的试探
                   location[k] = i;
                   break;
               }
           }

           if(k==n-1 && circle[k] != 0) {  //说明对所有分量都有取到值
               for (int c: circle) {
                   System.out.println(c);
               }
               return;
           }


           else if(k < n-1) {   //试探下一个分量

               k++;
           }
           else{  //需要回溯

                location[k] = 0;
                circle[k] = 0;
                k--;
                location[k] = location[k] + 1;

           }
       }
    }


    /* 素数环问题中,
       判断第k个分量是否满足2个隐式约束条件
       条件一:第k个分量的取值和之前各分量的取值不重复
       条件二:第k个分量和第k-1个分量的和是否为素数,如最后一个数,则还要判断第1个数和最后1个数之和也为素数
       * */
    private int  checkPrimeCircle(int k){
        //先判断第k个分量的取值是否与前面各分量的取值冲突
        if(k==0) return 1;
        for (int i = k-1; i >= 0 ; i--) {
            if(circle[i] == circle[k]) return 0;
        }
        //再判断第k个分量和第k-1个分量的和是否为素数

        int tSum = circle[k] + circle[k-1];
        if(isPrime(tSum) == 0) return 0;

        //最后,如果第k个分量是最后一个分量,还需要判断第1个分量和最后1个分量的和是素数
        if(k == n-1){
             tSum = circle[k] + circle[0];
             if (isPrime(tSum) == 0) return 0;
        }
        return 1;
    }

    /*
    *  判断n是否为素数*/
    private int isPrime(int n){
        if(n <=1 ) return 0;
        for(int i=n-1;i>=2;i--){
            if(n % i == 0) return 0;
        }
        /* // 或者优化为下面的形式
         for( int i = (int)Math.sqrt(n); i>=2;i--){
            if (n % i == 0) return 0;
        }*/
        return 1;
    }


private int subsetSum = 0;
    void getSubsetByBackTrace(int[] num, int target,int k){
        int len = num.length;
        if(k>= len){

            for (int c : choice) {
                System.out.println(c);
            }

            return;
        }


           if(subsetSum + num[k] <= target){
               choice[k] = 1;
               subsetSum += num[k];
               getSubsetByBackTrace(num,target,k+1);
               subsetSum -= num[k];
           }
           else {
               choice[k] = 0;
               getSubsetByBackTrace(num,target,k+1);

        }

    }

    /* 子集数和问题中,
          判断第k个分量是否满足约束条件
          前k个数字选择的结果之和<=target
    * */

    private boolean checkSubset(int[] num, int target,int k){
        subsetSum += choice[k] * num[k];
        if( k == 0) {

            return num[k] <= target;
        }

        return subsetSum <= target;

    }

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
罗马尼亚度假问题是一个著名的搜索问题,其目的是找到一条从起点城市到目标城市的最短路径。该问题可以通过广度优先搜索(BFS)和迪杰斯特拉算法(Dijkstra)来解决。 1. 广度优先搜索 广度优先搜索是一种基于队列实现的搜索算法,它从起点城市开始,一层一层地遍历中的所有节点,直到找到目标节点。该算法保证了最短路径的正确性,因为每个节点都是在前一层节点的基础上扩展而来的。 具体步骤如下: 1)创建一个队列,并将起点城市加入队列; 2)从队列中取出一个节点,如果该节点是目标节点,则搜索结束;否则,将该节点的未被访问过的邻居节点加入队列,并标记为已访问; 3)重复执行步骤2,直到找到目标节点或队列为空。 2. 迪杰斯特拉算法 迪杰斯特拉算法是一种基于贪心策略的最短路径算法,它从起点城市开始,不断更新到各个节点的最短路径,直到找到目标节点。该算法通过维护一个优先队列来实现节点的扩展顺序,每次取出距离起点最近的节点进行扩展。 具体步骤如下: 1)初始化起点城市到各个节点的距离为无穷大,起点城市到自身的距离为0; 2)将起点城市加入优先队列; 3)从优先队列中取出距离起点最近的节点,更新该节点的邻居节点到起点城市的距离,并将未被访问过的邻居节点加入优先队列; 4)重复执行步骤3,直到找到目标节点或优先队列为空。 以上两种算法可以解决罗马尼亚度假问题,并且保证了最短路径的正确性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值