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

来源:互联网 发布:sql语句删除列 编辑:程序博客网 时间:2024/05/17 23:12

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

0 0