DFS的理解,从排列到组合

全排列问题

  • 全排列问题
    输入数组{1,2,3}的全排列,我们将这个问题形象化。
    假设手里有编号1,2,3的三个扑克牌,和1,2,3的三个盒子。需要将三个扑克牌分别放入这个三个盒子当中,并且每个盒子只有一个扑克牌可以放
    首先来到一号盒子旁边,每一个都要放一编。约定一个顺序,到一个盒子面前,都先放1号,再放2号,最后放3号。在一号盒子里面放了编号1,来到二号只能放入编号2,3,按照约定只能放入编号2,于是三号盒子只能放入编号3.当到了第四个盒子的时候,发现没有第四个盒子,将前面数字1,2,3记录下来输出,这就是一个全排列。但是没有结束,此时需要回到3号盒子前,将三号盒子的编号3取出,但是手中没有别的编号的牌,所以只能退到二号箱中取出二号箱子的东西。然后手中有2,3两个编号,可以向二号箱子中放入3号牌,然后三号箱子中放入2号牌,此时又产生一个新的排列。
    这个就是全排列的思路。首先解决怎么向小盒子中放入扑克牌,这里用一个for循环解决
for(i=1; i <= n; i++){
    a[step] = i;//将第i个扑克牌放入第step个箱子中
}

如果一个扑克牌只能放入一个盒子中,那么就将同样的扑克牌放入别的盒子中,因为已经没有这个小盒子了。所以需要用一个数组book来标记哪些牌已经使用过了。

for(i = 1; i <=n; i++){
    if(book[i] == 0) //表示扑克牌依旧在手上
    {
        a[step] = i;
        book[i] = 1; //扑克牌已经不在手上了
    }
}

处理完第step个·盒子了,接下来还要往前走一步,继续处理第step+1个小盒子,那么如何处理,和处理第step个盒子是一样的,因此将刚才的步骤封装成一个函数

void dfs(int step){ //step表示现在在第几个盒子面前
    for(i=1; i <= n; i++){
        if(book[i] == 0){ //表示i号扑克牌仍然在手上
            a[step]=i; //放入在step盒子中
            book[i] = 1; //表示已经放过
        }
    }
}  

处理完第step个盒子,去处理step+1个盒子

void dfs(int step){
    for(int i = 1; i <= n; i++){
        a[step] = i; 
        book[i] = 1;
        dfs(step+1);
        book[i] = 0;
    }
}

最后把结束条件加上

void dfs(int step)//step表示现在站在第几个盒子面前  
{  
    int i;  
    if(step == n+1)//如果站在第n+1个盒子面前,则表示前n个盒子已经放好了扑克牌  
    {  
        //输出一种排列  
        for(i=1;i<=n;i++)  
            put(a[i]);
        return;//返回之前的一步(最近一次调用dfs函数的地方)  
    }  

    //站在第step个盒子的面前,按照1、2、3...n的顺序一一尝试  
    for(i = 1; i <= n; i++)  
    {  
        if(book[i] == 0)//表示i号扑克牌仍然在手上  
        {  
            a[step]= i;//将i号扑克牌放入到第step个盒子中  
            book[i] = 1;//表示i号扑克牌已经不在手上了  
            dfs(step+1);  
            book[i] = 0;  
        }     
    }   
}  

上面的步骤就是dfs的应用,理解深度优先搜索的关键在于解决“当下该如何做”。至于“下一步如何做”则与“当下该如何做”是一样的。比如我们这里写的dfs(step)函数的主要功能就是解决当你在用step个盒子的时候你该怎么办。通常的方法就是把每一种可能都去尝试一遍(一般用for循环遍历)。当前这一步解决后便进入下一步dfs(step+1)。下一步的解决方法和当前这一步的解决方法是完全一样的。下面的代码就是深度优先搜索的基本模型。

void dfs(int step){
    判断边界
    尝试每一种可能(){
        继续下一步dfs(n+1);
    }
}

从n个数中取k个排列的问题

上面已经解决了全排列的问题,就是盒子数和排数相等,现在我们解决非全排列的问题。在这里k是小于等于n的,这个说明每个盒子放上了牌或者没有放上牌。


```void dfs(int step)//step表示现在站在第几个盒子面前  
{  
    int i;  
    if(step == k+1)//如果站在第k+1个盒子面前,则表示前n个盒子已经放好了扑克牌  
    {  
        //输出一种排列  
        for(i=1;i<=n;i++)  
             put(a[i]);
        return;//返回之前的一步(最近一次调用dfs函数的地方)  
    }  

    //站在第step个盒子的面前,按照1、2、3...n的顺序一一尝试  
    for(i = 1; i <= n; i++)  
    {  
        if(book[i] == 0)//表示i号扑克牌仍然在手上  
        {  
            a[step]= i;//将i号扑克牌放入到第step个盒子中  
            book[i] = 1;//表示i号扑克牌已经不在手上了  
            dfs(step+1);  
            book[i] = 0;  
        }     
    }   
}  




<div class="se-preview-section-delimiter"></div>

组合问题

组合问题和排列问题最大的区别就是:组合没有顺序,排列有顺序,123 和 132在组合中是同一种组合,但是在排列中确是不同的排列。假设有5个牌需要放到三个盒子中去。假设一号箱子里面已经有了一号牌,二号箱子里面已经有了二号牌,顺序是123,124,125的情况已经全部尝试过了,就不能考虑一号箱子是1号牌而其他箱子有2号牌出现的情况了。如同123和132
1-2-x的所有情况我们都考虑完后,我们就可以在排除2的情况下考虑所有1-3-x的情况。然后是1-4-x,但会出现1-5-x吗?不会啦~因为一共只有5张牌哦~而所有的牌此时都被标记为已用哦,即book为1,所以第三个箱子里是没有可以放的牌的!程序会直接跳过滴,我们就不用担心啦。此时带有1的所有组合我们都考虑完毕啦,于是给它对应的book标记上1。于是我们顺利退回到一号箱子,在一号箱子中放入了2号牌,接下来在不考虑1号的情况下排列出2-x-x的组合。思路已经和上面完全一样啦!我们将会得到234、235和245。得到的最后一个组合就是345了。

void dfs(int step){
    int i;
    if(step == k+1){ //输出一种组合
        for(i = i; i <= k; i++){
            put(a[i]);
        }
        return;//返回最近一次调用dfs的地方
    }

    //站在第step个盒子的前面,按照1,2,3...n的顺序一一尝试
    for(int i = 1; i <= n; i++){
        if(book[i] == 0) //表示扑克牌仍然在手上
        {
            a[step] = b[i]; //第i个扑克牌中吵架
            book[i] = 1;//表示i个扑克牌已经不在受伤了
            dfs(step+1);
            book[i] = 0;//收回手中的牌

            if(flag == 1){
                book[i] = 1;
                flag = 0;
            }
        }
        if(i == n) //表示在某个箱子已经遍历完成了从1到n号的所有扑克牌
        flag = 1;
    }
    return;
}




<div class="se-preview-section-delimiter"></div>

这样就造成了一个问题:比如说在6选4的组合中,得到1234、1235、1236后,3号牌被标记成了1后,就不会再得到1345、1346、1356这三个组合。所以我们需要将部分数字恢复成可用状态。我们用一个for循环消除从当前牌号的下一位到最后一张牌的标记,以便以后再次使用。

void dfs(int step)//step表示现在站在第几个盒子面前  
{  
    int i;  
    if(step == k+1)//如果站在第k+1个盒子面前,则表示前k个盒子已经放好了扑克牌  
    {  
        //输出一种排列  
        for(i=1;i<=k;i++)//注意这里只输出  
            puts(a[i]);  
        return;//返回之前的一步(最近一次调用dfs函数的地方)  
    }  

    //站在第step个盒子的面前,按照1、2、3...n的顺序一一尝试  
    for(i = 1; i <= n; i++)  
    {  
        if(book[i] == 0)//表示i号扑克牌仍然在手上  
        {  
            a[step]= b[i];//第i个扑克牌放入到第step个盒子中  
            book[i] = 1;//表示i号扑克牌已经不在手上了  
            dfs(step+1);//走到下一个小盒子面前  
            book[i] = 0;//收回盒子中的牌  

            if(flag == 1)  
            {  
                book[i] = 1;  
                flag = 0;  

                for(int j=i+1; j<=n; j++)//消除从当前牌号的下一位到最后一张牌的标记,以便以后再次使用  
                    book[j] = 0;  

            }  
        }  

        if(i == n)//表示在第step个箱子上已经遍历完了从1到n号的所有扑克牌  
            flag = 1;  
    }  

    return;  
}  

int main()  
{ 
    for(int i=1; i<=n; i++)  
       b[i] = i; 
    dfs(1);//首先站在1号小盒子面前  

    return 0;  
}  

转载:http://blog.csdn.net/yyyds/article/details/51712604

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值