【OJ】DFS——自己动手写的第一个DFS

 

第一题

题目描述:

     排列与组合是常用的数学方法。 
先给一个正整数 ( 1 < = n < = 10 ) 
例如n=3,所有组合,并且按字典序输出: 
1 2 3 
1 3 2 
2 1 3 
2 3 1 
3 1 2 
3 2 1 

输入

输入一个整数n(  1<=n<=10)

输出

输出所有全排列

每个全排列一行,相邻两个数用空格隔开(最后一个数后面没有空格)

样例输入

3

样例输出

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

 

#include <iostream>
#include <string.h>
using namespace std;
const int maxn=11;
int n;
int num[maxn];
bool flag[maxn]={true};
int result[maxn];
void DFS(int index){
   // cout<<"index:"<<index<<endl;
    if(index==n){//墙  已经构造好了排列
        for(int i=0;i<n;i++){
            cout<<result[i]<<' ';
        }
        cout<<endl;
        return;
    }
    //分支
   // cout<<"here 1"<<endl;
    for(int i=0;i<n;i++){
        //cout<<i<<endl;
        if(flag[i]){
            //cout<<"flag["<<i<<"] is true"<<endl;
            result[index]=num[i];
            flag[i]=false;
            DFS(index+1);
            //cout<<"here 2"<<endl;
            flag[i]=true;
        }
    }
    return;
}
int main()
{
    while(cin>>n){
        for(int i=1;i<=n;i++){
            num[i-1]=i;
        }
        memset(flag,true,n);
        DFS(0);
    }
    return 0;
}

这里一开始写好了,但是一直执行不下去,后来才知道,原来我定义flag之后没有memset 导致只有第一个是true。

 

第二题

题目描述

排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r < = n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。 
现要求你不用递归的方法输出所有组合。 
例如n = 5 ,r = 3 ,所有组合为: 
1 2 3 
1 2 4 
1 2 5 
1 3 4 
1 3 5 
1 4 5 
2 3 4 
2 3 5 
2 4 5 
3 4 5 

输入

一行两个自然数n、r ( 1 < n < 21,1 < = r < = n )。

输出

所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,所有的组合也按字典顺序。

#include <iostream>
#include <string.h>
using namespace std;
const int maxn=21;
int n,r;
int num[maxn];
bool flag[maxn]={true};
int result[maxn];
void DFS(int index,int i){
   // cout<<"index:"<<index<<endl;
    if(index==r){//墙  已经构造好了排列
        for(int j=0;j<r;j++){
            cout<<result[j]<<' ';
        }
        cout<<endl;
        return;
    }
    //分支
   // cout<<"here 1"<<endl;
    for(;i<n;i++){
        //cout<<i<<endl;
        if(flag[i]){
            //cout<<"flag["<<i<<"] is true"<<endl;
            result[index]=num[i];
            flag[i]=false;
            DFS(index+1,i+1);
            //cout<<"here 2"<<endl;
            flag[i]=true;
        }
    }
    return;
}
int main()
{
    while(cin>>n>>r){
        for(int i=1;i<=n;i++){
            num[i-1]=i;
        }
        memset(flag,true,n);
        DFS(0,0);
    }
    return 0;
}

 

第三题

题目描述

已知 n 个整数b1,b2,…,bn

以及一个整数 k(k<n)。

从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。

例如当 n=4,k=3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:
    3+7+12=22  3+7+19=29  7+12+19=38  3+12+19=34。
  现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数:3+7+19=29。

 

输入

第一行两个整数:n , k (1<=n<=20,k<n) 
第二行n个整数:x1,x2,…,xn (1<=xi<=5000000) 

输出

一个整数(满足条件的方案数)。 

样例输入

4 3
3 7 12 19

样例输出

1
#include <iostream>
#include <string.h>
#include <math.h>
using namespace std;
const int maxn=21;
int n,k,c=0;
int num[maxn];
bool ifprime(int num){
    if(num==1||num==0)return false;
    int q=(int)sqrt((double)num);
    int i;
    for(i=2;i<=q;i++)if(num%i==0)break;
    if(i>q)return true;
    else return false;
}
void DFS(int index,int sum,int number){
    if(number==k){
        if(ifprime(sum))c++;
        return;
    }
    if(index==n||number>k)return;
    DFS(index+1,sum+num[index],number+1);
    DFS(index+1,sum,number);
}


int main()
{
    cin>>n>>k;
    for(int i=0;i<n;i++){
        cin>>num[i];
    }
    DFS(0,0,0);
    cout<<c<<endl;
    return 0;
}

代码还是差不多,判断素数的函数注意一下,写完要测试

在DFS上我也花了比较大的功夫,主要是一开始直接写墙

然后导致其实应该判断是否为素数的,而是直接碰到墙返回了,所以一定要注意写DFS的时候的顺序,操作,墙,分支

 

第四题

题目描述

       会下国际象棋的人都很清楚:皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。如何将8个皇后放在棋盘上(有8 * 8个方格),使它们谁也不能被吃掉!这就是著名的八皇后问题。 

 

输入

一个整数n( 1 < = n < = 10 ) 

输出

每行输出对应一种方案,按字典序输出所有方案。每种方案顺序输出皇后所在的列号,相邻两数之间用空格隔开。如果一组可行方案都没有,输出“no solute!”

样例输入

4

样例输出

2 4 1 3
3 1 4 2
#include <iostream>
#include <string.h>
#include <cmath>
#include <vector>
using namespace std;
const int maxn=11;
int n,count=0,P[maxn];
int hashTable[maxn]={false};
vector<int> v;

void DFS(int index){
    if(index==n+1){
        count++;
        for(int i=1;i<=n;i++){
            cout<<P[i];
            if(i!=n)cout<<' ';
            else cout<<endl;
        }
        return;
    }
    for(int x=1;x<=n;x++){
        if(hashTable[x]==false){
            bool flag=true;
            for(int pre=1;pre<index;pre++){
                if(abs(index-pre)==abs(x-P[pre])){
                    flag=false;
                    break;
                }
            }
            if(flag){
                P[index]=x;
                hashTable[x]=true;
                DFS(index+1);
                hashTable[x]=false;
            }
        }
    }
}


int main()
{
    cin>>n;
    memset(hashTable,false,n);
    DFS(1);
    if(count==0)cout<<"no solute!";
    return 0;
}

在这里用到了回溯,必须每次想要添加的时候判断一下与前面每一个棋子是不是在同一对角线

再同一对角线的条件是两个棋子的水平距离差=竖直距离差

加入index到3 然后1已经放了2 2已经放了4 现在判断3 有1 4 选择

如果选择1 那么 1到最左端的距离为2(3-1) 而第一列的2到最上端的距离为1(2-1) 不相等 所以 可以

如果选择4 4到最左边的距离为2(3-1)  而4到2的竖直距离差为2 (4-2) 相等,所以在同一对角线,回溯

 

第五题

 

题目描述

栈是常用的一种数据结构,有n令元素在栈顶端一侧等待进栈,栈顶端另一侧是出栈序列。你已经知道栈的操作有两•种:push和pop,前者是将一个元素进栈,后者是将栈顶元素弹出。现在要使用这两种操作,由一个操作序列可以得到一系列的输出序列。请你编程求出对于给定的n,计算并输出由操作数序列1,2,…,n,经过一系列操作可能得到的输出序列总数。 
 

输入

一个整数n(1<=n<=15) 

输出

一个整数,即可能输出序列的总数目。

样例输入

3

样例输出

5

提示

 

先了解栈的两种基本操作,进栈push就是将元素放入栈顶,栈顶指针上移一位,等待进栈队列也上移一位,出栈pop是将栈顶元素弹出,同时栈顶指针下移一位。 

用一个过程采模拟进出栈的过程,可以通过循环加递归来实现回溯:重复这样的过程,如果可以进栈则进一个元素,如果可以出栈则出一个元素。就这样一个一个地试探下去,当出栈元素个数达到n时就计数一次(这也是递归调用结束的条件)。 

#include <iostream>
#include <string.h>
#include <cmath>
#include <vector>
#include <stack>
using namespace std;
const int maxn=16;
int n,count=0;
void DFS(int waitnum,int stacknum,int outnum){
    //cout<<"outnum:"<<outnum<<endl;
    if(outnum==n){
        count++;
        //cout<<"count:"<<count<<endl;
        return;
    }
    if(waitnum>0)DFS(waitnum-1,stacknum+1,outnum);
    if(stacknum>0)DFS(waitnum,stacknum-1,outnum+1);
}
int main()
{
    cin>>n;
    DFS(n,0,0);
    cout<<count;
    return 0;
}

别看这题代码短小,但是我想了的确是很久(菜的一批),主要是

1.墙是什么,墙题目中提示说是输出到达n的时候,那么最好需要传入输出个数

2.如何分支?分支是push还是pop,但是push必须要等待队列有东西,pop必须要stack有东西,所以必须要用两个变量来表示这两个实体。(一开始真的没意识到这里有三个实体)

 

最后最后一题

题目描述


  有一个n*m格的迷宫(表示有n行、m列),其中有可走的也有不可走的,如果用1表示可以走,0表示不可以走,文件读入这n*m个数据和起始点、结束点(起始点和结束点都是用两个数据来描述的,分别表示这个点的行号和列号)。现在要你编程找出所有可行的道路,要求所走的路中没有重复的点,走时只能是上下左右四个方向。如果一条路都不可行,则输出相应信息(用-l表示无路)。 
  请统一用 左上右下的顺序拓展,也就是 (0,-1),(-1,0),(0,1),(1,0)
 

 

 

输入

第一行是两个数n,m( 1 < n , m < 15 ),接下来是m行n列由1和0组成的数据,最后两行是起始点和结束点。 

输出

  所有可行的路径,描述一个点时用(x,y)的形式,除开始点外,其他的都要用“->”表示方向。 
  如果没有一条可行的路则输出-1。

样例输入

5 6
1 0 0 1 0 1
1 1 1 1 1 1
0 0 1 1 1 0
1 1 1 1 1 0
1 1 1 0 1 1
1 1
5 6

样例输出

(1,1)->(2,1)->(2,2)->(2,3)->(2,4)->(2,5)->(3,5)->(3,4)->(3,3)->(4,3)->(4,4)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(2,4)->(2,5)->(3,5)->(3,4)->(4,4)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(2,4)->(2,5)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(2,4)->(3,4)->(3,3)->(4,3)->(4,4)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(2,4)->(3,4)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(2,4)->(3,4)->(4,4)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(3,4)->(2,4)->(2,5)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(3,4)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(3,4)->(4,4)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(4,3)->(4,4)->(3,4)->(2,4)->(2,5)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(4,3)->(4,4)->(3,4)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(4,3)->(4,4)->(4,5)->(5,5)->(5,6)

提示

 

【算法分析】 

  用一个a数组来存放迷宫可走的情况,另外用一个数组b来存放哪些点走过了。每个点用两个数字来描述,一个表示行号,另一个表示列号。对于某一个点(x,y),四个可能走的方向的点描述如下表: 

   2 

1  x,y  3 

   4 

  对应的位置为:(x, y-1),(x-1, y),(x, y+1),(x+1, y)。所以每个点都要试探四个方向,如果没有走过(数组b相应的点的值为0)且可以走(数组a相应点的值为1)同时不越界,就走过去,再看有没有到达终点,到了终点则输出所走的路,否则继续走下去。 

  这个查找过程用search来描述如下: 

procedure search(x, y, b, p);{x,y表示某一个点,b是已经过的点的情况,p是已走过的路} 

 begin 

   for i:=1 to 4 do{分别对4个点进行试探} 

   begin 

     先记住当前点的位置,已走过的情况和走过的路; 

     如果第i个点(xl,y1)可以走,则走过去; 

     如果已达终点,则输出所走的路径并置有路可走的信息, 

     否则继续从新的点往下查找search(xl,y1,b1,p1); 

   end; 

 end; 

  有些情况很明显是无解的,如从起点到终点的矩形中有一行或一列都是为0的,明显道路不通,对于这种情况要很快地“剪掉”多余分枝得出结论,这就是搜索里所说的“剪枝”。从起点开始往下的一层层的结点,看起来如同树枝一样,对于其中的“枯枝”——明显无用的节点可以先行“剪掉”,从而提高搜索速度。  

#include <iostream>
#include <string.h>
#include <cmath>
#include <vector>
#include <map>
#include <stack>
using namespace std;
const int maxn=16;
int n,m,count=0;
int X[4]={0,-1,0,1};
int Y[4]={-1,0,1,0};
int MAP[maxn][maxn];
bool passflag[maxn][maxn]={false};
bool ifOJBK(int x,int y){
    if(x<0||y<0||x>=n||y>=m)return false;
    if(MAP[x][y]==0)return false;
    if(MAP[x][y]==1){
        if(passflag[x][y]==true)return false;
        else return true;
    }
}
typedef vector<pair<int,int> > PATH;
int Sx,Sy,Ex,Ey;
bool NOPATH=true;
void DFS(int x,int y,PATH &p){
    for(int i=0;i<4;i++){
        if(ifOJBK(x+X[i],y+Y[i])){//如果可以走
            //cout<<x+X[i]<<' '<<y+Y[i]<<"isOJBK"<<endl;
            if((x+X[i]==Ex)&&(y+Y[i]==Ey)){//检查是否到end了
                //cout<<"reach end!!!!!!!!!!!"<<endl;
                p.push_back(make_pair(x,y));
                NOPATH=false;//如果到终点了
                for(int i=0;i<p.size();i++){
                    cout<<'('<<p[i].first+1<<','<<p[i].second+1<<")->";
                }
                cout<<'('<<Ex+1<<','<<Ey+1<<')';
                cout<<endl;
                p.pop_back();
                return;
            }//输出结束
            //cout<<"I think I can add"<<x<<' '<<y<<"to my path"<<endl;
            passflag[x][y]=true;
            p.push_back(make_pair(x,y));
            DFS(x+X[i],y+Y[i],p);
            p.pop_back();
            passflag[x][y]=false;
           // cout<<"I think I can remove"<<x<<' '<<y<<"from my path"<<endl;
        }
    }//四个点要么不可以走 要么我已经运行DFS 估计已经找到END了 那么
    //cout<<"here !!!"<<endl;
    return;
}
int main()
{
    PATH path;
    cin>>n>>m;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            cin>>MAP[i][j];
            passflag[i][j]=false;
        }
    }//没错
    cin>>Sx>>Sy>>Ex>>Ey;
    Sx-=1;Sy-=1;Ex-=1;Ey-=1;//ok
    //path.push_back(make_pair(Sx,Sy));
    DFS(Sx,Sy,path);
    if(NOPATH)cout<<-1;
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值