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

回溯法:输出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 8
int 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";

 }



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值