DFS入门:全排列算法及POJ 1564 Sum it up详解

原创 2015年11月20日 17:00:07

全排列算法是DFS(深度优先搜索)的一个简单入门应用。
假设我们现在给过一个数n, 要求我们输出从1到 n的n个数的所有排列方式。
详细要求可参考codevs中 1294全排列问题。

首先给出程序的主体及各个变量的作用:

#include<stdio.h>
#include<stdlib.h>
int main(void){
    int n;
    scanf("%d", &n);
    int *flag = (int *)malloc(sizeof(int) * (n + 1));
    int *result = (int *)malloc(sizeof(int) * (n + 1));
    int i;
    for(i = 1;i <= n;i++){
        flag[i] = 0;
    }
    int index = 1;
    DFS(result, flag, index, n);
    return 0;
}

①flag数组标志着第i位数字是否使用,当第i位数字没有使用的时候,flag[i] = 0, 当第i位数字已经被使用的时候, flag[i] = 1。
②result数组包含了要输出的结果。
③index变量代表着当前的第几层(也就是第几位数字)。
④flag数组以及result数组分配n + 1个空间是为了更直观,从数组[1]开始存放值。

接下来给出DFS函数:

void DFS(int *result, int *flag, int index, int n){
    int i;
    if(index == n + 1){
        for(i = 1;i <= n;i++){
            printf("%d ", result[i]);
        }
        printf("\n");
    }
    else{
        for(i = 1;i <= n;i++){
            if(flag[i] == 0){
                result[index] = i;
                flag[i] = 1;
                DFS(result, flag, index + 1, n);
                flag[i] = 0;
            }
        }
    }
}

①首先该函数终止的条件是index == n + 1, 因为当index == n时最后一位数字还没有放进result中,所以真正结束的条件是n + 1。
②因为index是逐层加一,所以不会出现index > n + 1的情况,即是说,index < n + 1时证明还没有构造出一个可以输出的结果。
③当我们第一次递归至可以输出结果时,因为每一个数字依次递增都是可用的,所以第一个输出的数字肯定是1 2 ….. n,而此时flag数组上所有的值都为1代表所有数字都已经使用。
④当输出这一结果后,我们再进行回溯,flag[n] = 0意味着当前数字n不使用,然后回到上一层(index == n - 1层),循环刚好到达到第n - 1层的DFS函数处, 继续数字n - 1不使用,而此时上面的循环(i <= n)迭代加一(此时数字n没有使用所以可以flag[n] == 0 为true),所以在result数组的n - 1位置上插入了n,在下一个DFS函数中第n个位置只能插入n - 1。
举个简单的例子:假设n = 5,则第一个数是12345, 而第二个是12354.
⑤每一次循环中都会优先找到比较小的数在高位所以排序从小到大。(可以通过改变1到n的顺序来改变)
⑥其实第一次循环时当不看调用DFS函数调用时可以看为是决定第一位上的数字,当决定好第一位时再从剩下的数字中决定第二位,依次递推。

接着我们看POJ 1564 Sum it up问题:
该问题要求首先输入总和total, 然后输入n个数,并打印出n个数中其中任意个数加起来等于total的加法表达式, 其中n个数中可以有重复。
该题目的思路是在全排列的基础上还要去重。

程序主体及变量解释:

#include<stdio.h>
#include<stdlib.h>
#define MAX 12
int total, n;
int flag;
int result[MAX];
int container[MAX];
int main(void){
    int i;
    scanf("%d%d", &total, &n);
    int currentSum, index, step;
    while(n != 0){
        currentSum = 0;
        index = 0;
        step = 0;
        flag = 0;
        for(i = 0;i < n;i++){
            scanf("%d", &container[i]);
        }
        printf("Sums of %d:\n", total);
        DFS(index, step, currentSum);
        if(flag == 0){
            printf("NONE\n");
        }
        scanf("%d%d", &total, &n);
    }
}

①flag哨兵标志着是否有解,无解则输出NONE。
②result数组存放输出结果,container数组存放n个数,这里为什么不需要全排列中的flag数组呢?因为n个数是按递减的顺序输入,求和的时候只需要考虑向后小的数,不会再加之前的数(之前的数要么已经跳过或已经加上)。
③index, step可参考全排列例子,currentSum 则代表当前DFS函数之前已经增加的数的和。

接下来给出DFS函数:

void DFS(int index, int step, int currentSum){
    int i;
    if(currentSum == total){
        flag = 1;
        printf("%d", result[0]);
        for(i = 1;i < step;i++){
            printf("+%d", result[i]);
        }
        printf("\n");
    }
    else{
        if(currentSum < total && index < n){
            for(i = index;i < n;i++){
                if(i == index || container[i] != container[i - 1]){
                    result[step] = container[i];
                    DFS(i + 1, step + 1, currentSum + container[i]);
                }
            }
        }
    }
}

①首先当前DFS判断结束的条件是curretSum = total, 其次currentSum < total && index < n代表当前函数还没有结束,只要其中一个条件不满足则代表无解(即currentSum > total 或者 index >= n)。
②接着老规矩step代表当前result数组上的第几位要存放什么数,但是在循环条件中i == index为什么呢?这跟不用flag数组的原因是一样的,之前的数不用考虑,只需要跳转到第i层就可以了(也就是把第i层的数放在step的位置上),所以下一个DFS函数跳转的层数也是i + 1,而并非index + 1。
③我们还要求去重,举个例子4 6 4 3 2 2 1 1,result上0位放3,1位放1,但是1有两个就重复了。所以每到新的一层时,第一个元素肯定是可以放入step位置的,但接下来的每一个元素都要进行判断是否跟前一个元素相等,若相等,则放在同一个step上会有重复。

这是其中解决问题的一种思路,我们接下来给出另一种思路。
假设这n个数中通过currentSum中记录当前的和,那么对于每一个数我们有两种选择,加或者不加。
我们看核心的函数DFS:

void DFS(int currentSum, int index, int skip){
    int i;
    if(index == n + 1){
        return ;
    }
    if(currentSum == total){
        flag = 1;
        for(i = 0;i < n;i++){
            if(status[i]){
                 printf("%d", container[i]);
                 i++;
                 break;
            }
        }
        for(;i < n;i++){
            if(status[i]){
                printf("+%d", container[i]);
            }
        }
        printf("\n");
    }
    else if(currentSum < total){
        if(skip != container[index]){
            status[index] = 1;
            DFS(currentSum + container[index], index + 1, -1);
        }
        status[index] = 0;
        DFS(currentSum, index + 1, container[index]);
    }
}

①index == n + 1因为最终判定结果在n + 1层,第n层判定的是上一个结果。
②skip作为标志意味着之前的数是否有跳过,当有跳过的时候保存上一个数,检查这一个位置是否放入相同的数。

版权声明:本文为博主原创文章,未经博主允许不得转载。

C++DFS方法全排列

前几天看纪磊的《啊哈!算法》一书,里面讲算法讲的特别通俗细致,真的是初中生都能读得懂的算法书(我大二才读:P)。这段代码很适合初学算法的同学。 #include using namespace...
  • a2524289
  • a2524289
  • 2017年11月13日 14:42
  • 58

【算法专题】【搜索】【DFS】枚举全排列

【算法专题】【搜索】【DFS】枚举全排列
  • Henry_2001
  • Henry_2001
  • 2016年08月15日 16:00
  • 164

算法基础-->图论(BFS,DFS)

本篇博文将总结和图相关的一些算法,其中又以广度优先搜索和深度优先搜索最为重要。图的表示: 邻接矩阵 n∗nn*n 的矩阵,有边是 11,无边是 00,nn 表示结点个数。 邻接表 为每个点建立一个...
  • Mr_tyting
  • Mr_tyting
  • 2017年08月31日 19:50
  • 345

Backward Digit Sums --- DFS+全排列

Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 4946   Accepted: 2850 D...
  • u012965373
  • u012965373
  • 2015年05月11日 11:16
  • 624

POJ 1564 Sum It Up——DFS

POJ DFS基础题
  • von_troy
  • von_troy
  • 2010年11月04日 19:38
  • 634

poj 1564 Sum It Up DFS

题目描述: DescriptionGiven a specified total t and a list of n integers, find all distinct sums using n...
  • sinat_34336698
  • sinat_34336698
  • 2016年09月01日 19:20
  • 93

poj 1564 dfs(Sum It Up)

题意:给定一个数sum,以及n个数s1
  • dumeichen
  • dumeichen
  • 2014年08月19日 20:11
  • 290

POJ 1564 Sum It Up(DFS)

题目链接: POJ 1564 Sum It Up 题意: 给你一个非递增数组,输出由数组中的数字组合的相加和等于给定t的所有可能组合。 每个组合内部按照非递增顺序输出,每个组合按照首数字从大到...
  • Ramay7
  • Ramay7
  • 2016年03月06日 21:01
  • 189

POJ 1564 Sum It Up dfs

来源:http://poj.org/problem?id=1564 题意:给你一个数sum,然后再给你一个数n,再给你n个数,从n个数中找出一些数,让这些数的和是n。所给的n个数是递减的,让输出的结...
  • wmn_wmn
  • wmn_wmn
  • 2012年08月13日 10:28
  • 694

POJ 1564 Sum It Up

简单DFS。搜一下某个数由那些加起来的。注意判重。
  • dongshimou
  • dongshimou
  • 2014年04月28日 11:38
  • 495
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:DFS入门:全排列算法及POJ 1564 Sum it up详解
举报原因:
原因补充:

(最多只允许输入30个字)