美文网首页
深搜 排列组合

深搜 排列组合

作者: LxxxR | 来源:发表于2018-05-05 14:42 被阅读0次

1.DFS

DFS(i=0){
    if(得到一条解路径){
        输出;
        return;                          //该分支结束
    }

    for(第i步的所有可能j){
        a[i]=j; //选取一种可能
        DFS(i+1); //走下一步            //j个分支都return之后,函数结束
    }
}

2.回溯

回溯法是剪枝的暴力法。

剪枝函数包括两类:
1.使用约束函数,剪去不满足约束条件的路径;
2.使用限界函数,剪去不能得到最优解的路径。

将问题转化为解空间树,解空间树分为两种:排列树和子集树(组合树)。
排列树: 所给的问题是确定n个元素满足某种性质的排列。
子集树: 所给的问题是从n个元素的集合S中找出满足某种性质的子集。

递归形式

//一般要三个参数,a[]用来存储一条解路径,n为搜索深度,i为当前深度,如果要保存所有解,还需要一个参数来存储所有解路径
void fun(int a[], int n, int i){    //第i步的走法

  if(i==n){    //得到一条路径
    输出解路径a[n];
    return;
   }

   for(j=下界;j<=上界;j++){   //用j枚举第i步的可能走法
       if(j满足条件){  //剪枝,没有则为深度搜索
          取走j;
          a[i]=j;   //这两步紧密连接
          fun(a,n,i+1);   
          释放j;
        }
     }

}

1.输出n的排列

递归

先放置第1位置,再对后面n-1个位置全排列。(输出不是字典序)

void permutaion(int a[],int n,int i)  //a={1...n},放置第i个位置,初始为0
{
    if (i==n) {
        for(int j=0;j<n;j++)
            cout<<a[j]<<" ";
        cout<<endl;
        return;
    }
                
    for(int j=i;j<n;j++) {   //第i个位置可放的是它及它后面的数,用j遍历之
        swap(a[i],a[j]); //该位置放置a[j]
        permutaion(a,n,i+1);  //开始下一位置
        swap(a[i],a[j]); //释放a[j],回到原先状态
    }   
}

若含有重复数字,需要在swap之前判断a[j]是否在a[i,j-1]之间出现过,未出现时才进行那三步操作。

非递归

按字典序输出,初始a[n]为字典序最小的,找下一个字典序。有重复元素也不影响。
步骤:2找,1换,1逆
1,从后往前找第一个升序位置i,a[i]<a[i+1],找不到时函数结束;(a[i]之后为降序)
2, 从后往前找第一个位置j(j在i后面),a[j]>a[i];
3,交换a[i],a[j];
4, a[i]之后的元素逆序。


void print(int a[],int n){
    for(int i=0;i<n;i++)
        cout<<a[i]<<' ';
    cout<<endl;
}

void reverse(int a[],int n,int strat){
    int l=strat,r=n-1;
    while(l<r){
        swap(a[l++],a[r--]);
    }
}


void permutaion(int a[],int n)  //a={1...n}。以a[i]开头,i初始为0
{
    if(n==1){
        cout<<a[0]<<endl;
        return;
      }
      
  int i,j;
  while(1){
        print(a,n);

        for(i=n-2;i>=0;i--){
            if(a[i]<a[i+1])
                break;
            else if(i==0)
                return;
        }
        for(j=n-1;j>i;j--){
            if(a[j]>a[i])
                break;
        }

        swap(a[i],a[j]);
        reverse(a,n,i+1);
  }
}

1.1 随机输出一个排列

    #include<time.h>
    #include<cstdlib> //生成随机数的头文件
 
    void randPermutaion(int a[],int n,int i){    
        srand((unsigned)time(NULL));

        for(int i=0;i<n;i++){  //每个位置随机放一个它及它之后的数
            int p=rand()%(n-i) + i;
            swap(a[i],a[p]);
        }
    }

2.输出n的组合(一个集合的所有子集)

第一个元素取或不取,确定之后,再对后面n-1个元素组合

void subsets(bool a[],int b[],int n,int i) //a为是否取的标记数组,b={1...n}为该集合
{
    if (i==n) {
        for(int j=0;j<n;j++){
            if(a[j])
                cout<<b[j]<<" ";
        }
        cout<<endl;
        return;
    }
                
    for(int j=0;j<=1;j++) {  //第i个元素,可以取或不取
        a[i]=j;
        subsets(a,b,n,i+1);  
    }
   
}

2.1.输出n的组合,且长度为m

取第一个元素,后面取m-1个;不取第一个元素,后面取m个

void subset(vector<int> ans,vector<int> a,int n,int m,int i){
    if(ans.size()>m) //元素个数超过,就停止向下递推,相当于剪枝
        return;
    if(i==n){  //扫描完数组
        if(ans.size()==m){  //元素个数等于m
            for(int j=0;j<m;j++)
                cout<<ans[j]<<" ";
            cout<<endl;
        }
        return;
    }
    //和上面一样,每个元素取或不取
    ans.push_back(a[i]);
    subset(ans,a,n,m,i+1);

    ans.pop_back();
    subset(ans,a,n,m,i+1);
}

3.八皇后

1,每行放且只放一个皇后:保证了不同行
2,八个皇后对应的列不相同:保证了不同列
例:0~7行的皇后所在的列为[0,1,2,3,4,5,6,7],一个全排列就保证了不同行,不同列。
3,两个皇后不在一条对角线:i-j != a[i]-a[j] && i-j != a[j]-a[i]

所以只要在全排列的代码中加一个判断条件即可。参考回溯。
或者枚举出所有排列,在一一判断是否满足条件,时间复杂度比回溯法稍微高。

#include<iostream>
using namespace std;

bool check(int a[],int i,int j){
    for(int k=0;k<i;k++){
        if(i-k==j-a[k]||i-k==a[k]-j)
            return false;
    }
    return true;
}

void permutaion(int a[],int n,int i)  
{
    if (i==n) {
        for(int j=0;j<n;j++)
            cout<<a[j]<<" ";
        cout<<endl;
        return;
    }
                
    for(int j=i;j<n;j++) {   
        if(check(a,i,a[j])){ //多了一个判断语句来剪枝。保证选取a[j]时,它和前面的皇后都不同对角线
            swap(a[i],a[j]); 
            permutaion(a,n,i+1);  
            swap(a[i],a[j]); 
        }
    }   
}

int main(){
    int a[]={0,1,2,3,4,5,6,7};
    permutaion(a,8,0);
    return 0;
}

相关文章

  • 深搜 排列组合

    1.DFS 2.回溯 回溯法是剪枝的暴力法。 剪枝函数包括两类:1.使用约束函数,剪去不满足约束条件的路径;2.使...

  • PAT 甲级 刷题日记|A 1103 Integer Fact

    思路 本题是深搜的一个应用:排列组合,选取给定的序列中的部分数字(可重复选择)使得满足给定条件。放到本题中:给定序...

  • CCF201512-3 画图(Java)

    深搜会爆栈的一道题(90分),宽搜可以过(100分) 深搜代码

  • 选书(深搜)

    问题描述 n个同学分n本书,每个人都有自己喜欢的书(即a[i][j]==1),求出所有的分配方案

  • 邮局(深搜+剪枝)

    题目如下: 这道题使用的方法是剪枝+深搜。解题步骤在于:先设定一个邮局是已确定的,得出所有的村民到该邮局的距离。再...

  • DFS(深搜)算法

    深度优先搜索算法(Depth-First-Search):是一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的...

  • 16-图的深搜和广搜二

    图的深搜和广搜 前面一篇是利用深搜和广搜进行图的顶点的遍历,今天我们则是根据起点和终点位置进行路径的搜索。 图的深...

  • 深度搜索与回溯

    深度搜索与回溯法的区别 回溯法 = 深搜 + 剪枝。一般大家用深搜时,或多或少都会剪枝。深搜一般用递归实现,比较简...

  • 训练训练者成为训练师

    投入多少,收获多少;参与多深,领悟多深!未曾经历,不成经验!太阳底下没有新鲜事,排列组合就是创新!做你所学,进而教...

  • Chapter1——搜索——深搜

    1. 题目列表 POJ1753(状态空间思想,简单深搜) POJ2965(同1753) POJ1573(深搜遍历 ...

网友评论

      本文标题:深搜 排列组合

      本文链接:https://www.haomeiwen.com/subject/bzosrftx.html