回溯法:输出n的全排列,解哈密顿问题,马的遍历问题,解八后问题

来源:互联网 发布:三国杀风火山林淘宝 编辑:程序博客网 时间:2024/05/17 13:05

回溯法:输出n的全排列

2012-07-15 20:09 by 有心故我在, 4 阅读, 0 评论,收藏,编辑

1. 问题描述: 输出自然数1到n的所有不重复的排列,即n的全排列。

2. 问题分析: 
(1) 解空间: n的全排列是一组n元一维向量(x1, x2, x3, ... , xn),搜索空间是:1<=xi<=n i=1,2,3,...,n 

(2) 约束条件: xi互不相同。技巧:采用"数组记录状态信息", 设置n个元素的一维数组d,其中的n个元素用来记录数据1-n的使用情况,已经使用置0,未使用置1

 

 

复制代码
#include<iostream>#include<memory>using namespace std;int count=0;//解个数int n;//输入数据int *a ;//解向量int *d ;//解状态void clear( ){    for(int i=1;i<=n+1;i++)    {        d[i]=0;    }}void output()//第0个不存{    for(int i=1;i<=n;i++)        cout<<a[i]<<ends;    cout<<endl;}void tryArrangement(int k){         for(int i=1;i<=n;i++)    {        if(d[i]==0)        {            a[k]=i;            d[i]=1;                    }        else         { //表明已用过            continue;        }              if(k<n) //没搜索到底        {            tryArrangement(k+1);        }      else        {            count++;            output();                     }      d[a[k]]=0; //回溯        }          }int main(){        cin>>n;    a=new int[n+5];//解向量    d=new int[n+5];//解状态         clear( );    tryArrangement(1);    }
复制代码

最开始是这样置d[ ]为0的:

memset(d,0,sizeof(d));

 不行,为什么?

记住,memset是按字节赋值的,第3个参数指定要赋值的字节的大小,应该改成:

 memset(d,0,sizeof(int)*n);

算法说明:k为当前处理的第k个元素。上面的复杂度为O(n^n),不是一个好的算法,因此不可能用它去搜索排序树。

调试分析

k=1 if(d[1]==0) true a[1]=1; d[1]=1 try(2)
k=2 if(d[2]==0) a[2]=2 d[2]=1 try(3)
k=3 if(d[3]==0) a[3]=3; d[3]=1;
3==n; count=1; output 1 2 3

d[3]=0;
} 跳到for( )处运行 F11运行都函数末尾,
F11又跳到try(3) k=2 d[a[2]]=0
} 跳到For处运行
if(d[3]==0) a[2]=3 d[3]=1;
if(k<3) try(3)

 

另一种解法==也是搜索排列树的算法框架。

设计:根据全排列的概念,定义数组初始值为(1,2,3,4,。。n),这是全排列的一种结果,然后通过数据间的交换,则可产生所有不同的排列:

复制代码
#include<iostream>using namespace std;int a[100],n,s=0;void output(){    for(int j=1;j<=n;j++)        cout<<a[j]<<ends;    cout<<endl;}void  tryArrange(int k){    int j;    if(k>n)    {        output();    }    else        for(j=k;j<=n;j++)        {            swap(k,j);            tryArrange(k+1);            swap(k,j); //回溯时,恢复原来的排列        }}int main(){    int i;    cin>>n;    for(i=1;i<=n;i++)        a[i]=i;    tryArrange(1);    cout<<"换行符"<<"s="<<s;}
复制代码

注意c++try是关键字。swap在ios文件中。

-----------------------------------------------------------------------------------------------

回溯法解哈密顿问题

2012-07-12 17:46 by 有心故我在, 8 阅读, 0 评论,收藏,编辑

回溯法求解:

    首先把回路中的所有顶点编号初始化为0,然后,把顶点0当做回路中的第0个顶点,即x0=0;搜索与0邻接的编号最小的顶点,作为它的后续顶点,判断是否满足约束条件,是则到达该顶点,x1取值为满足条件的顶点编号,然后再以同样的方式搜索。

 

     假设在搜索过程中已经生成了通路L=x0x1....,xi-1,在继续搜索某个顶点作为通路的xi顶点时,
根据约束条件,在V中选择与xi-1邻接的并且不属于L的编号最小的顶点

  如果搜索成功,则把该顶点作为xi,否则就把L中的xi-1删去(回溯),从xi-1顶点编号加1的位置开始,继续搜索与xi-2相邻接且不属于L的顶点。这个过程一直继续下去。


当搜索到xn-1时,如果xn-1与x0相邻接,就得到了一天hamilton回路,否则把xi-1删去,继续回溯。
最后,在回溯过程中,L中只剩下一个顶点x0,表明不存在哈密顿回路。

 

复制代码
#include<iostream>#include<cstdio> using namespace std; void hamilton(int **a,int n,int x[])//x[] 存放回路的顶点序号 a不能写成二维数组形式,否则报错:不能从int **转为 int [ ]]{    bool *s=new bool[n]; //s记录顶点的使用与否    for(int i=0;i<n;i++)    {        x[i]=-1;        s[i]=false;    }    s[0]=true;    x[0]=0; //从序号0开始搜索    int k=1; //初始深度为1,因为有n个节点且第一个节点已给出(k=0),故空间搜索树的深度为n-1(1到n-1)    while(k>=0)     {        x[k]=x[k]+1;        while(x[k]<n)        {            if((!s[x[k]])&&(a[x[k-1]][x[k]]==1))            {                //顶点x[k]未被使用而起与前一节点x[k-1]有连线                break;            }            else                 x[k]=x[k]+1;            }        if(x[k]<n)        {            if(k!=n-1) //搜索成功,深度加1            {                s[x[k]]=true;                k=k+1;            }            else                break;        }        else        {            x[k]=-1;            k=k-1;            s[x[k]]=false;        }    }} int main(){    int n;    freopen("货郎担问题42.txt","r",stdin);    cin>>n;  //顶点数     int **a=new int *[n];      for(int i=0;i<n;i++)        a[i]=new int[n];    for(int i=0;i<n;i++)        for(int j=0;j<n;j++)            cin>>a[i][j];    int *x=new int[n];    hamilton(a,n,x);    for(int i=0;i<n;i++)        cout<<x[i]<<ends;    cout<<endl;}
复制代码

 

文件如下:

4
0 0 1 1
1 0 1 1
1 1 0 1

 

输出0 2 1 3

 

-------------------------------------------------------------------------------------------------------------------------------------

回溯法解马的遍历问题

2012-06-08 15:36 by 有心故我在, 9 阅读, 0 评论,收藏,编辑

  马的遍历问题:在n*m的棋盘上,马只能走日字。马从位置(x,y)处出发,把棋盘的每一点都走一次,且只走一次,找出所有路径。

问题分析:行n,列m,马在不出边界的情况下有8个方向可以行走(走日字),如当前坐标为(x,y),则行走后的坐标可以为:

(x+1,y+2) (x+1,y-2), (x+2,y+1) (x+2,y-1)

(x-1,y-2) (x-1,y+2) (x-2,y-1) (x-2,y+1)

 

回溯法算法设计: 搜索空间是n*M个点,约束条件是不出边界且每个点只经过一次,节点的扩展规则如上所述。

   搜索过程是从任一点(x,y)出发,按深度优先原则,从8个方向尝试一个可以走的点,直到走过n*m个点。用递归容易实现。

注意:问题要求找出所有可能的解。就要注意回溯过程的清理现场工作,就是置当前位置为未经过。

数据结构设计:用一个变量dep记录递归深度,也就是走过的点数。当dep=n*m;找到一组解。

用n*m的二维数组记录马走过的过程,初始值为0表示未经过,起点存储的是1,终点存储的是n*m。

复制代码
#include<iostream>using namespace std;void output();int n=5,m=4;int fx[8]={1,2,2,1,-1,-2,-2,-1};int fy[8]={2,1,-1,-2,-2,-1,1,2};int a[5][4]; //下标从1开始int dep,x,y,count; bool check(int x,int y){     if(x>=1&&x<=n&&y>=1&&y<=m&&(!a[x][y]))         return true;     else         return false;}    void find(int x,int y,int dep){    int i,xx,yy;    for(i=1;i<=8;i++) //加上方向增量,形成新的坐标    {        xx=x+fx[i];        yy=y+fy[i];        if(check(xx,yy)) //判断新坐标是否出界,是否已走过        {            a[xx][yy]=dep;             if(dep==n*m)                output();            else                find(xx,yy,dep+1);                       a[xx][yy]=0; //回溯,恢复未走未走                    }             }}void output(){    count++;    cout<<"\n";    cout<<"count="<<count;    for(y=1;y<=n;y++)    {        cout<<endl;        for(x=1;x<=m;x++)            cout<<a[y][x]<<ends;    }}int main(){    int i,j;    count=0;    dep=1;    cout<<"please input x,y";    cin>>x>>y;    if(x>n||y>m||x<1||y<1)    {        cout<<"input error";        return -1;    }    for(i=1;i<=n;i++)        for(j=1;j<=m;j++)            a[i][j]=0;    a[x][y]=1;    find(x,y,2);    if(count==0)        cout<<"no solution!";    else        cout<<"nambers of solution="<<count<<endl;} 
复制代码

代码输出好像有错误。


回溯法解八后问题

2012-05-20 22:58 by 有心故我在, 19 阅读, 0 评论,收藏,编辑

    在一个8×8国际象棋盘上,有8个皇后,每个皇后占一格;要求皇后间不会出现相互“攻击”的现象,即不能有两个皇后处在同一行、同一列或同一对角线上。问共有多少种不同的方法。

 

  我们用回溯法,现在的目的不是找有多少种解法,而是只要找出一种合适的解法输出即可。

先写一个place函数,判断当前位置是否合法:

复制代码
bool place(int x[],int k){    int i;    for(i=1;i<k;i++)        if((x[i]==x[k])||(abs(x[i]-x[k])==abs(i-k)))            return false;    return true;}
复制代码

  这个函数以解向量x[]和皇后的行号k做参数,判断第k个皇后当前的列位置x[k]是否满足关系式,这样,他必须和第1~k-1行的所有皇后位置进行比较。

n皇后算法如下:

复制代码
/*n 后问题输入:皇后个数n输出: n后问题的解向量*/void n_queens(int n,int x[])  {    int k=1;          //x[0]不要    x[1]=0;    while(k>0)    {        x[k]=x[k]+1; //在当前列加1的位置开始搜索        while(x[k]<=n&&(!place(x,k)))   //当前列是否满足条件           x[k]=x[k]+1;                    if(x[k]<=n)  //存在满足条件的列        {            if(k==n) break; //是最后一个皇后,完成搜索            else            {                k=k+1; x[k]=0; //不是,处理下一个皇后            }        }        else                 //已判断完n列,均没有满足条件        {            x[k]=0; k=k-1;   //第k行复位为0,回溯到前一行 ,前一行列加1 x[k]=x[k]+1        }    }//end while}
复制代码

 

   main函数如下:

复制代码
int main(){    int n;    cout<<"请输入皇后的数";    cin>>n;    int *a=new int[n+2];    n_queens(n,a);    cout<<"解向量为"<<endl;    for(int i=1;i<=n;i++)        cout<<a[i]<<ends;    cout<<endl;    for(int i=1;i<=n;i++)        {            for(int j=1;j<=n;j++)            {                if(j==a[i])                    cout<<"* "<<ends; //要在*后留一个空格,口字占2格                else                    cout<<""<<ends;            }            cout<<endl;    }}    
复制代码

 

用递归求解有:

解向量 (x1,x2,......xn)
显约束 xi=1,2.....n
隐约束:
1)不同列 xi!=xj
2)不出来同一正,反对角线 |i-j|!=|xi-xj|

 

复制代码
#include<iostream>#include<cstdlib>using namespace std;#define NumQueen 8int queen[NumQueen];int sum=0; //解决方案总数 8后有92组解void display(){    int i,j;         cout<<""<<sum+1<<"个解决方案-->";    for(i=0;i<NumQueen;i++)        {            for(j=0;j<NumQueen;j++)                if(queen[i]==j)                    cout<<"("<<i+1<<","<<queen[i]+1<<")";        }    cout<<endl;    sum++;//解的组数}bool check(int k){    int i;    for(i=0;i<k;i++)        if((queen[i]==queen[k])||(abs(queen[i]-queen[k])==abs(i-k)))            return false;    return true;} void putQueen(int k) {     int i;     for(i=0;i<NumQueen;i++)     {         queen[k]=i;         if(check(k))         {             if(k==NumQueen-1)                  display();             else                 putQueen(k+1);         }     } } int main() {     cout<<"方按,其中(行标,列标)为皇后的位置\n\n";     putQueen(0);     cout<<"\n共有"<<sum<<"个方案\n"; }



原创粉丝点击