c2java 回溯,下一个排列和子集和

本文探讨了回溯与穷举算法的基本概念及其应用。详细介绍了两种算法的工作原理,并通过实例说明了如何利用剪枝技术来提高算法效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

穷举:生成所有候选解,然后找出需要的解。
回溯:把解表示成向量,每个分量取自一个有限集合。从部分解开始,每次添加解的一个分量,然后判断如果有可能扩展成完整解则递归下去,否则换成下一个。可以看做是隐式图上的深度优先搜索。
回溯/穷举的复杂度,最坏时和后者一样,通常情形因为不必遍历所有子节点,还是比较快的。

回溯框架:
backtrack(a[], k)
 if a[0,...,k] is a solution
   output;
 else
   k = k + 1;
   for c: the i-th of candidates
     a[k] = c;
     bactrack(a, k); 

剪枝
把快死的或者齐心怪状的树枝剪掉。对于搜索树来说,就是把候选范围缩小。
例1,八皇后问题: 把八个皇后放在8x8棋盘上,使得任意两个不在同一行或者同一列或者对角线上。
直接以棋盘格为编号,解空间为2^64, 以皇后为编号,64^8, 不同行,8^8, 不同列,8!。
如果在生成排列后再做检测,这就是穷举;如果在for分支前先做检测,这就是回溯剪枝了。

例2,子集和问题:从一个正整数集合中找一个子集,使得其元素和为给定的数d。
记当前部分和为s。 如果选择e, s + e > d 或者s + 剩下所有元素 < d,该分支肯定无解,应该剪掉。

只有我们在验证所有叶子都可达确认算法正确后,才可用剪枝。一开始上剪枝可能导致结果不正确,比如下面的求连续可重复子集和sumBT()。

代码:

import java.util.Arrays;

public class Backtrack
{
    static void subsetBT(boolean[] a, int k)
    {
        if(k == a.length-1){
            int i;
            System.out.printf("{");
            for(i = 1; i <= k; ++i)if(true == a[i])System.out.printf(" %d", i);
            System.out.printf(" },");

            return;
        }

        k = k + 1;
        boolean[] candidates = {false, true};
        for(boolean c : candidates){
            a[k] = c;
            subsetBT(a, k);
        }
    }
    static void generateSubsets(int n)
    {/*生成{1,2,...,n}的所有子集*/
        boolean[] set = new boolean[n+1]; /*set[i] --- whether select i*/
        subsetBT(set, 0);
        System.out.printf("%n");
    }

    static boolean notUsed(int[] a, int n, int k)
    {/*whech k \in a[1..n)*/
        boolean ret = true;
        int i;
        for(i = 1; i < n; ++i)if(a[i] == k){ret = false; break;}
        
        return ret;     
    }
    
    
    static void permutationBT(int[] a, int k)
    {
        int i, n = a.length - 1;
        if(k == n){
            System.out.printf("{"); 
            for(i = 1; i <= k; ++i)System.out.printf(" %d", a[i]);
            System.out.printf("},");
            nextPermutation(a);
            return;
        }
        ++k;
        
        for(i = 1; i <= n; ++i)if(notUsed(a, k, i)){
            a[k] = i;
            permutationBT(a, k);
        }
    }
    static void generatePermutations(int n)
    {/*生成{1,2,...,n}的所有排列*/
        int[] a = new int[n+1];
        permutationBT(a, 0);
        System.out.printf("%n");
    }

    static void nextPermutation(int[] a)
    {
        int k = 0, j = 0, i = 0, n = a.length - 1;
        int[] b = new int[n+1];
        boolean[] c = new boolean[n+1]; 
        
        for(i = n-1; i >= 1; --i){
            for(j = a[i]+1; j <= n; ++j)if(notUsed(a, i, j))break;
            if(j <= n)break;            
        }
        if(i >= 1){
            //a[1..i-1],j,剩下的升序排列    
            for(k = 1; k < i; ++k){b[k] = a[k]; c[b[k]] = true;}
            b[i] = j; c[b[i]] = true;   
            for(k = 1; k <= n; ++k)if(!c[k])b[++i] = k;

            System.out.printf("["); 
            for(i = 1; i <=n; ++i)System.out.printf(" %d", b[i]);   
            System.out.printf("]==");
        }else{
            System.out.printf("==NULL");                        
                  }
    }

    static void setSumBT(int[] a, int k, int[] set, int d, int s)
    {
        int j = 1, i, n = set.length - 1;

        if((s == d)||(k >= n)){
            for(i = 1; i <= n; ++i)if(1 == a[i])System.out.printf("+%d", set[i]);
            System.out.printf("=%d level=%d%n", s, k);  

            return;
        }

        k = k + 1;
        if(true){//prune
            if(s + set[k] > d) j = 0;

            int sum = 0;
            for(i = k; i <= n; ++i)sum += set[i];
            if(s + sum < d)j = -1;
        }

        for(i = j; i >= 0; --i){
            a[k] = i;
            setSumBT(a, k, set, d, s + a[k]*set[k]);        
        }
        
    }

    static boolean sumBT(int[] a, int k, int[] set, int d, int s, int maxCnt)
    {
        int j = 1, i, n = set.length - 1;
        boolean ret = false;

        if((s == d)){
            for(i = 1; i <= k; ++i)System.out.printf("+%d", a[i]);
            System.out.printf("=%d%n", s);  

            return s == d;
        }
        if(k >= maxCnt){return false;}

        k = k + 1;
    
        for(i = 1; i <= n; ++i){
            a[k] = set[i];
            ret = sumBT(a, k, set, d, s + a[k], maxCnt);        
            if(ret == true)return true;
        }
        
        return false;
    }


    public static void main(String[] arg)
    {
        //generateSubsets(3);
        
        //generatePermutations(4);

        int[] set = {0, 3,5,6,7};
        int maxCnt = 5;
        int[] a  =  new int[maxCnt+1]; 
        Arrays.fill(a, 0);
        int d = 15;
        //setSumBT(a, 0, set, d, 0);
        for(d = 1; d < 100; ++d)sumBT(a, 0, set, d, 0, maxCnt);
    }
}

/*
:!javac Backtrack.java && java Backtrack
{ },{ 3 },{ 2 },{ 2 3 },{ 1 },{ 1 3 },{ 1 2 },{ 1 2 3 },
{ 1 2 3 4},[ 1 2 4 3]=={ 1 2 4 3},[ 1 3 2 4]=={ 1 3 2 4},[ 1 3 4 2]=={ 1 3 4 2},[ 1 4 2 3]=={ 1 4 2 3},[ 1 4 3 2]=={ 1 4 3 2},[ 2 1 3 4]=={ 2 1 3 4},[ 2 1 4 3]=={ 2 1 4 3},[ 2 3
 1 4]=={ 2 3 1 4},[ 2 3 4 1]=={ 2 3 4 1},[ 2 4 1 3]=={ 2 4 1 3},[ 2 4 3 1]=={ 2 4 3 1},[ 3 1 2 4]=={ 3 1 2 4},[ 3 1 4 2]=={ 3 1 4 2},[ 3 2 1 4]=={ 3 2 1 4},[ 3 2 4 1]=={ 3 2 4 1
},[ 3 4 1 2]=={ 3 4 1 2},[ 3 4 2 1]=={ 3 4 2 1},[ 4 1 2 3]=={ 4 1 2 3},[ 4 1 3 2]=={ 4 1 3 2},[ 4 2 1 3]=={ 4 2 1 3},[ 4 2 3 1]=={ 4 2 3 1},[ 4 3 1 2]=={ 4 3 1 2},[ 4 3 2 1]=={ 
4 3 2 1},==NULL

before prune:
+3+5+6+7=21 level=4
+3+5+6=14 level=4
+3+5+7=15 level=4
+3+5=8 level=4
+3+6+7=16 level=4
+3+6=9 level=4
+3+7=10 level=4
+3=3 level=4
+5+6+7=18 level=4
+5+6=11 level=4
+5+7=12 level=4
+5=5 level=4
+6+7=13 level=4
+6=6 level=4
+7=7 level=4
=0 level=4

after prune:
+3+5+6=14 level=4
+3+5+7=15 level=4
+3+5=8 level=4
+3+6=9 level=4
+5+6=11 level=4
*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值