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

全排列算法是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作为标志意味着之前的数是否有跳过,当有跳过的时候保存上一个数,检查这一个位置是否放入相同的数。

你可能感兴趣的:(算法,poj,DFS)