本系列所有代码https://github.com/YIWANFENG/Algorithm-github
回溯法思想
回溯法运行起来类似于遍历,只不过会在遍历过程中去除一部分不可能的无效遍历()。
解决的问题的答案一般可以由一个向量表示,例如V= {x1,x2,x3….},其中x1,x2,x3…的取值便为最优解。
解空间即该问题所有可能的解的集合,在表示上分为子集树与排列树。
子集树即时该问题的x1,x2,x3….的取值影响最优解,而排列数是该问题的x1,x2,x3….的排列影响最优解。
在问题求解过程中,可以遍历整个树求最优解,但是一般情况我们都会用限界函数与剪枝函数对搜索加以限制,来节省我们的时间。
(相关名词。活结点:孩子未遍历完的结点 扩展结点:正在生成孩子的结点,
死结点:孩子遍历完毕的结点)
N皇后的安排问题
算法分析思路以及相关数学公式:
设x[i] 表示皇后i放在棋盘的第i行的第X[j]列。
则可由题目规则可知,任意两个X[I]互不相同。并且2皇后不可放于同一斜线上。
那么可得约束条件2,( i –j) != (x[i] – x[j] ) 同一斜线上的行相减与列相减结果相等。
算法可为,从第一行遍历,检查该行的某处是否可以安排皇后,是的话则进行下一行的遍历查找。等到找到一个解向量后向前回溯一层继续寻找,直到寻找完所有的解向量。
程序源代码:
#include <iostream>
#include <stdlib.h>
using namespace std;
class NQueenSolver {
private:
intn; //皇后数量
int*x; //每一行的皇后所在的列
intsum; //当前解的数量
private:
boolValidate(int k) {
//验证第k行皇后位置是否合理
for(int i=1; i<k; ++i) {
if((k-i)==abs(x[i]-x[k])|| x[i]==x[k])
return false;
}
return true;
}
voidBacktrack(int t) {
if(t>n) { //到达解空间树的叶子
++sum;
if(sum==1)show_plan();
} else {
//遍历所有的子结点
for(inti=1; i<=n; ++i) {
x[t] = i;
if(Validate(t))
Backtrack(t+1);
}
}
}
voidBacktrack_Iterative() {
int k =1 ; //处理层数
x[1] = 0;
while(k>0) {
++x[k];
//遍历所有子结点,寻找满足约束的子结点
while(x[k]<=n&& !Validate(k)) ++x[k];
if(x[k]<=n){ //找到满足约束的子结点
if(k==n) { //到达叶子结点
++sum;
if(sum== 1) show_plan();
} else {
//进入下一层结点
++k;
x[k]= 0;
}
}else {
//所有子结点遍历完毕,回溯
--k;
}
}
}
voidshow_plan() {
for(int i=1; i<=n; ++i) {
for(intj=1; j<=n; ++j) {
if(x[i]==j) cout<<"Q ";
else cout<<"* ";
}
cout<<endl;
}
}
public:
intSolve(int num_queens) {
//num_queens皇后数量
//解的数量
n = num_queens;
sum = 0;
x = new int[n+1];
Backtrack(1);
delete []x;
return sum;
}
intSolve_iterative(int num_queens) {
//num_queens皇后数量
//解的数量
n = num_queens;
sum = 0;
x = new int[n+1];
Backtrack_Iterative();
delete []x;
return sum;
}
};
int main()
{
intnum = 0,sum;
cout<<"输入皇后数\n";
cin>> num;
NQueenSolverqs;
sum= qs.Solve(num);
//sum= qs.Solve_iterative(num);
cout<<num<<"皇后问题解的数量:"<<sum<<endl;
return0;
}
//结果正确。由于解数量过多,在这里只输出了一组解。
最优装载问题
题目:
最优装载问题
求解一该问题解向量,使得该装载的最大重量最大。
算法分析思路以及相关数学公式:
子集树表示问题的解空间。
完全遍历解空间,每一次遍历到叶子结点,查看此时解是否比已知解更优,若更优则更新解。
限界函数1:当前选择可以装入,即当前解不超过额定最大载重量
限界函数2:选择当前解后可以存在最优解,即当前选择完后加上所有后面等待选择的解大于最优解(即才可能存在最优解)。
程序源代码:
#include <iostream>
#include <stdlib.h>
#include <stack>
using namespace std;
template <class T_>
class MaxLoading {
private:
// in
int n;
const T_ *w; //每个集装箱重量
T_ c; //可载重量
// out
T_ bestw; //当前最优载重量
int *bestx; //当前最优解
//interior
int *x; //当前解
T_ cw; //当前载重量
T_ r; //当前剩余物品重量
void Backtrack(int i) {
if(i>n) {
//到达叶子
bestw = cw;
for(int j=1; j<=n; ++j) bestx[j] = x[j];
return ;
}
//更新剩余重量
T_ r_backup = r;
r -= w[i];
//搜索左子节点
if(cw+w[i]<=c) {//约束函数
x[i] = 1;
T_ cw_backup = cw;
cw+=w[i];
Backtrack(i+1);
cw = cw_backup;
}
//搜索右子结点
if(cw+r>bestw) { //限界函数
x[i] = 0;
Backtrack(i+1);
}
r = r_backup;
}
public:
int Solve(int n_,T_ c_,const T_ *w_,int *bestx_) {
//n_ 物品数量
//c_ 最大载重量
// w_[] 物品重量表
// bestx_[] 最优解
n = n_;
c = c_;
w = w_;
bestx = bestx_;
x = new int[n+1];
cw = 0;
bestw = 0;
r = 0;
for(int i=1; i<=n; ++i) r+=w[i];
Backtrack(1);
delete [] x;
return bestw;
}
};
int main()
{
int n = 3,sum;
MaxLoading<float> ml;
float w[] = {0,10,40,40};
int x[n+1];
float bestw = ml.Solve(n,60.0,w,x);
//float bestw = ml.Solve_Iterative(n,60.0,w,x);
cout<<"最优值:"<<bestw<<endl<<"装载方式:\n";
for(int i=1; i<=n; ++i) {
cout<<x[i]<<' ';a
}
cin.get();
return 0;
}
0-1背包问题
算法分析思路以及相关数学公式:
此问题类似最优装载。在搜索子集树的解空间时,只要其做儿子结点是可行的一个结点,就进入左子树,当右子树可能包含最优解时才进入右子树,否则减去右子树。设r为当前剩余物品的价值总和,cp是当前价值,bestp是当前最优价值。当CP+r<=bestp时,可减去右子树。计算右子树中解的上界可以利用贪心选择法求背包问题的思想。
程序源代码:
#include <iostream>
#include <stdlib.h>
#include <algorithm>
using namespace std;
class Object {
public:
intID; //原始编号
floatd; //单位重量的价值
booloperator< (const Object & a) const {
return d>=a.d;
}
};
template <class T_,class W_>
class Knapsack {
private:
//in
intn;
T_*w; //每个物品重量
W_*p; //每个物品价值 ,需要从大到小排序
T_c; //背包可载重量
//out
W_bestp; //当前最优载价值
int*bestx; //当前最优解
//interior
int*x; //当前解
T_cw; //当前载重量
W_cp; //当前价值
private:
W_Bound(int i) { //计算上界
T_ c_left = c- cw;
W_ b = cp;
while(i<=n &&w[i]<=c_left) {
c_left-= w[i];
b+=p[i];
++i;
}
if(i<=n) b+=p[i]*c_left/w[i];
return b;
}
voidBacktrack(int i) {
if(i>n) {
//到达叶子
bestp= cp;
for(intj=1; j<=n; ++j) bestx[j] = x[j];
return;
}
//搜索左子节点
if(cw+w[i]<=c) {//约束函数
x[i]= 1;
//T_cw_backup = cw;
//W_cp_backup = cp;
cw+=w[i];
cp+=p[i];
Backtrack(i+1);
cw-=w[i];
cp-=p[i];
//cw= cw_backup;
//cp= cp_backup;
}
//搜索右子结点
if(Bound(i+1)>bestp) { //限界函数
x[i]= 0;
Backtrack(i+1);
}
}
/*voidBacktrack_Iterative(int i) {
//
int k = 1;
bool flag = true;
while(k>0) {
//左子树
if(flag&& cw+w[k]<=c) {//约束函数
x[k] = 1;
T_ cw_backup = cw;
cw+=w[i];
Backtrack(i+1);
cw = cw_backup;
}
//右子树
if(flag&& cw+r>bestw) { //限界函数
x[i] = 0;
Backtrack(i+1);
}
}
}*/
public:
W_Solve(int n_,T_ c_,const T_ *w_,const W_ *p_,int *bestx_) {
//n_ 物品数量
//c_ 背包最大载重量
// w_[] 物品重量表
// p_[] 物品价值
// bestx_[] 最优解
//返回最优价值
n = n_;
c = c_;
w = new T_[n+1];
p = new W_[n+1];
bestx = bestx_;
x = new int[n+1];
cw = 0;
cp = 0;
bestp = 0;
Object *Q = new Object[n];
T_ w_total = 0; //总重量
W_ p_total = 0; //总价值
for(int i=1; i<=n; i++) {
Q[i-1].ID= i;
Q[i-1].d= p_[i]/(float)w_[i]; ////!!!
w_total+= w_[i];
p_total+= p_[i];
}
//检查是否全可装下
if(w_total < c) {
delete[] Q;
for(inti=1; i<=n; ++i) x[i] = 1;
returnp_total;
}
sort(Q,Q+n); //物品按单位价值从大到小排序 ,以便Bound()
/*for(int i=0;i<n;++i)
{
cout<<Q[i].ID<<''<<Q[i].d<<endl;
}*/
//一般情况
for(int i=1; i<=n; ++i) {
p[i]= p_[ Q[i-1].ID ];
w[i]= w_[ Q[i-1].ID ];
}
/*for(int i=1;i<=n;++i)
{
cout<<p[i]<<''<<w[i]<<endl;
}*/
Backtrack(1);
//最优解
for(int i=1; i<=n; ++i)
x[Q[i-1].ID]= bestx[i];
for(int i=1; i<=n; ++i)
bestx_[i]= x[i];
delete [] p;
delete [] w;
delete [] x;
return bestp;
}
};
int main()
{
intn = 4,sum;
Knapsack<int,float>ml;
intw[] = {0,3,5,2,1};
floatp[] = {0,9,10,7,4};
intx[n+1];
floatbestp = ml.Solve(n,7,w,p,x);
cout<<"最优值:"<<bestp<<endl<<"装载方式:\n";
for(inti=1; i<=n; ++i) {
cout<<x[i]<<' ';
}
cin.get();
return0;
}