使用回溯算法解决N皇后问题以及间隔排列问题

回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。


在编程中,最容易出错的地方,便是迭代和递归。那么回溯便是这两种方法的结合。按照我的理解,使用回溯法需要心中有结构。回溯这个词用的很形象,按照人的理解就是往后走,但是计算机算法中的回溯是一个相对的概念,不向前走便是往回走。最外层是一个迭代循环,代表的是上一步,迭代中的代码有向前走的递归,如果在向前走的过程中,没有满足相应的约束函数,那么便继续上一步的迭代过程,这便称为回溯。下面我们结合实例来说一下算法的核心思想,这是我自己写的代码,所以有些地方可能质量不是很高。


在下面的代码中,我都把约束函数定义为Place,回溯函数的名称可以随便定义。注意观察回溯函数的结构,大体都一致。


n皇后问题:

在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于再n×n的棋盘上放置n个皇后,任何2个皇后不妨在同一行或同一列或同一斜线上。


//n-Quene
bool Place(int k,int i,int* x){
for(int j=0;j<k;j++){//decide wether the current queen conflict with other before queens
if(x[j] == i || abs(x[j]-i)==abs(j-k))
return false;
return true;
}
}


void NQueens(int k,int n,int* x){
for(int i=0;i<n;i++){
if(Place(k,i,x)){
x[k] = i;
if(k == n-1){
for(int i=0;i<n;i++) cout<<i<<" ";
cout<<endl;
}else
NQueens(k+1,n,x);
}
}
}
void NQueens(int n,int* x){
NQueens(0,n,x);
}


int main(){
int n;
do{
cout<<"请输入n的大小:(n不小于3)"<<endl;
cin>>n;
}while(n<3);
int* x = (int*)malloc(sizeof(int)*n);


NQueens(n,x);

}


约束函数的作用是不让第k个皇后和以前的皇后发生冲突,那么这里的约束函数很好写啦。注意回溯函数。最外层的for循环是将这个皇后在所有可能的列上进行try,如果可以放那么就通过递归向前走。一旦退出递归,并且没有达到n-1,那么就说面前面有个地方走不通了,继续for循环,这样便很形象的展示出了什么是回溯。


下面再来看一个实例问题:

给定n个数,从1-n,每个数取两个行成2n个数。然后将这2n个数进行排列,使得1之间有1个数,....n之间有n个数。列出所有可能的排序结果。

这道题目可以使用上述类似的方法进行解答。



#include <stdio.h>
#include <iostream>
using namespace std;


//constraint function
bool Place(int k,int i,int* x){//to see whether the selected index i is valid
if(x[i] != 0 || x[i+k+1] != 0)
return false;
return true;
}


void refresh(int k,int n,int* x){
for(int j=0;j<2*n;j++){
if(x[j] <= k)
x[j] = 0;
}

}
//backtrack function
void Backtrack(int k,int n,int* x){
for(int i=0;i<2*n-k-1;i++){
refresh(k,n,x);
if(Place(k,i,x)){
x[i] = x[i+k+1] = k;
if(k == 1){//get the answer state
cout<<"============================"<<endl;
for(int j=0;j<2*n;j++){
if(j == 2*n-1)
cout<<x[j]<<endl;
else
cout<<x[j]<<"\t";
}
}else{
Backtrack(k-1,n,x);
}
}
}
}
int main(){
int n;
do{
cout<<"请输入n的大小:(n不小于3)"<<endl;
cin>>n;
}while(n<3);


cout<<"排序结果如下所示:"<<endl;
int* x = (int*)malloc(sizeof(int)*n*2);//define the array
//int* x = new int[n];
for(int i=0;i<n*2;i++)//initialize the array
x[i] = 0;


Backtrack(n,n,x);


char a; 
cin>>a;
return 0;
}


这里我的约束函数写的比较简单,但是在for循环之下,必须加上refresh函数对数组的状态进行恢复,否则算法无法进行,因为我的约束函数是根据数组的状态进行判定的。


回溯法在很多情况下都要用到,所以需要好好理解并且灵活的运用。















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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值