DFS的理解,从排列到组合

来源:互联网 发布:淘宝收货地址 编辑:程序博客网 时间:2024/05/16 11:56

全排列问题

  • 全排列问题
    输入数组{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