算法学习--回朔和剪枝

骑士旅游问题

问题描述:在一个N*N 格子的棋盘上,有一只国际象棋的骑士在棋盘的左下角,骑士只能根据象棋的规则进行移动,要么横向跳动一格纵向跳动两格,要么纵向跳动一格横向跳动两格。骑士从第一个格子出发,每个格子只能访问一次,能否访问完所有的格子, 请找到一个解决方案。

骑士也就是类似中国象棋中的马的走法。这个问题和典型的N皇后问题的比较类似的,都可以通过回溯法来解决。还有其它的解决方法,参考:http://en.wikipedia.org/wiki/Knight%27s_tour#Computer_algorithms

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

bool backtracking(int x,int y,int cnt);

int isSafe(int x, int y, int solution[N][N])
{
    if ( x >= 0 && x < N && y >= 0 && y < N && solution[x][y] == -1)
        return 1;
    return 0;
}

/* 打印数组sol */
void printSolution(int sol[N][N])
{
	printf("find one solution :\n");
    for (int x = 0; x < N; x++)
    {
        for (int y = 0; y < N; y++)
            printf(" %2d ", sol[x][y]);
        printf("\n");
    }
}

//存储解决方案
int solution[N][N];

//马跳八方,共有8中走法
int xMove[8] = {  2, 1, -1, -2, -2, -1,  1,  2 };
int yMove[8] = {  1, 2,  2,  1, -1, -2, -2, -1 };

bool KnightTour()
{
    /* 初始化 */
    for (int x = 0; x < N; x++)
        for (int y = 0; y < N; y++)
            solution[x][y] = -1;

    if(backtracking(0, 0, 1) == false)
    {
        printf("Solution does not exist");
        return false;
    }
    else
        printSolution(solution);

    return true;
}

bool backtracking(int x,int y,int cnt){
	//存储访问记录
	solution[x][y] = cnt;

	//访问完所有的格子
	if(cnt == N*N){
		return true;
	}
	int next_x, next_y;
	//一次尝试所有可以移动的格子
	for(int i=0; i<8; i++){
		next_x = x + xMove[i];
		next_y = y + yMove[i];

		//判断该格子是否可以访问
		if( isSafe(next_x, next_y, solution) ){

			//找到解决方案 就直接返回
			if( backtracking(next_x,next_y,cnt+1) == true ) return true;
		}
	}
	solution[x][y] = -1; //回溯操作. 未找到解决方案
	return false;
}

int main()
{
	KnightTour();
    return 0;
}

装载问题

    问题描述有一批共n个集装箱要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为wi,且装载问题要求确定是否有一个合理的装载方案可将这些集装箱装上这2艘轮船。如果有,找出一种装载方案。

     例如:当n=3,c1=c2=50,且w=[10,40,40]时,则可以将集装箱1和2装到第一艘轮船上,而将集装箱3装到第二艘轮船上;如果w=[20,40,40],则无法将这3个集装箱都装上轮船。

   基本思路: 容易证明,如果一个给定装载问题有解,则采用下面的策略可得到最优装载方案。
    (1)首先将第一艘轮船尽可能装满;
    (2)将剩余的集装箱装上第二艘轮船。
    将第一艘轮船尽可能装满等价于选取全体集装箱的一个子集,使该子集中集装箱重量之和最接近C1。由此可知,装载问题等价于以下特殊的0-1背包问题。


    用回溯法设计解装载问题的O(2^n)计算时间算法。在某些情况下该算法优于动态规划算法。

    算法设计:

    用回溯法解装载问题时,用子集树表示其解空间显然是最合适的。用可行性约束函数可剪去不满足约束条件的子树。在子集树的第j+1层的结点z处,用cw记当前的装载重量,即cw=,则当cw>c1时,以结点z为根的子树中所有结点都不满足约束条件,因而该子树中的解均为不可行解,故可将该子树剪去。(该约束函数去除不可行解,得到所有可行解)。

     可以引入一个上界函数,用于剪去不含最优解的子树,从而改进算法在平均情况下的运行效率。设z是解空间树第i层上的当前扩展结点。cw是当前载重量;bestw是当前最优载重量;r是剩余集装箱的重量,即r=。定义上界函数为cw+r。在以z为根的子树中任一叶结点所相应的载重量均不超过cw+r。因此,当cw+r<=bestw时,可将z的右子树剪去。

     递归回溯具体代码如下:

#include "stdafx.h"
#include <iostream>
using namespace std; 

template <class Type>
class Loading
{
	//friend Type MaxLoading(Type[],Type,int,int []);
	//private:
	public:
		void Backtrack(int i);
		int n,			//集装箱数
            *x,			//当前解
			*bestx;		//当前最优解
			Type *w,	//集装箱重量数组
			c,			//第一艘轮船的载重量
			cw,			//当前载重量
			bestw,		//当前最优载重量
			r;          //剩余集装箱重量
};

template <class Type>
void  Loading <Type>::Backtrack (int i);

template<class Type>
Type MaxLoading(Type w[], Type c, int n, int bestx[]);

int main()
{   
	int n=3,m;
	int c=50,c2=50;

	int w[4]={0,10,40,40};
	int bestx[4];

    m=MaxLoading(w, c, n, bestx);

	cout<<"轮船的载重量分别为:"<<endl;
	cout<<"c(1)="<<c<<",c(2)="<<c2<<endl;

	cout<<"待装集装箱重量分别为:"<<endl;
	cout<<"w(i)=";
	for (int i=1;i<=n;i++)
	{
		cout<<w[i]<<" ";
	}
	cout<<endl;

	cout<<"回溯选择结果为:"<<endl;
	cout<<"m(1)="<<m<<endl;
	cout<<"x(i)=";

	for (int i=1;i<=n;i++)
	{
		cout<<bestx[i]<<" ";
	}
	cout<<endl;

	int m2=0;
	for (int j=1;j<=n;j++)
	{
		m2=m2+w[j]*(1-bestx[j]);
	}
   	cout<<"m(2)="<<m2<<endl;

	if(m2>c2)
	{
		cout<<"因为m(2)大于c(2),所以原问题无解!"<<endl;
	}
	return 0;
}

template <class Type>
void  Loading <Type>::Backtrack (int i)// 搜索第i层结点
{
	if (i > n)// 到达叶结点
	{  
		if (cw>bestw)
		{
			for(int j=1;j<=n;j++) 
			{
				bestx[j]=x[j];//更新最优解
				bestw=cw; 
			}
		} 
		return;
	}

	r-=w[i]; 
	if (cw + w[i] <= c) // 搜索左子树
	{ 
		x[i] = 1;
		cw += w[i];
		Backtrack(i+1);
		cw-=w[i];    
	}

	if (cw + r > bestw)
	{
		x[i] = 0;  // 搜索右子树
		Backtrack(i + 1);  
	}
	r+=w[i]; 
}

template<class Type>
Type MaxLoading(Type w[], Type c, int n, int bestx[])//返回最优载重量
{
	Loading<Type>X;
	//初始化X
	X.x=new int[n+1];
	X.w=w;
	X.c=c;
	X.n=n;
	X.bestx=bestx;
	X.bestw=0;
	X.cw=0;
	//初始化r
	X.r=0;

	for (int i=1;i<=n;i++)
	{
		X.r+=w[i];
	}

	X.Backtrack(1);
	delete []X.x;
	return X.bestw;
}


哈密顿回路-回溯法

哈密顿图(英语:Hamiltonian path,或Traceable path)是一个无向图,由天文学家哈密顿提出,由指定的起点前往指定的终点,途中经过所有其他节点且只经过一次。在图论中是指含有哈密顿回路的图,闭合的哈密顿路径称作哈密顿回路(Hamiltonian cycle),含有图中所有顶的路径称作哈密顿路径。
对一个给定的图确定其是否包含哈密顿回路。如果它包含,那么打印路径。以下是函数所需的输入和输出。
输入:
二维数组 graph[V][V]图的邻接矩阵表示。graph[i][j] = 1 如果有一条从 i 到 j的边, 否则graph[i][j] = 0。
输出:
如果该图包含哈密顿回路,则输出path[V],代表路径。否则返回FALSE。

例如:下面的一个哈密顿回路为 {0, 1, 2, 4, 3, 0},或者 {0, 3, 4, 2, 1, 0}

1 (0)--(1)--(2)
2  |   / \   |
3  |  /   \  |
4  | /     \ |
5 (3)-------(4)

下面的图没有哈密顿回路

1 (0)--(1)--(2)
2  |   / \   |
3  |  /   \  |
4  | /     \ |
5 (3)      (4)

哈密顿回路和N皇后等问题类似,属于NP难题。一般应用回溯法准确求解。

#include <iostream>
#include <stdio.h>
using namespace std;
const int V = 5;

void printSolution(int path[])
{
    printf ("Solution Exists:"
            " Following is one Hamiltonian Cycle \n");
    for (int i = 0; i < V; i++)
        printf(" %d ", path[i]);

    printf(" %d ", path[0]);
    printf("\n");
}

//path记录路径,visited记录顶点是否访问过,len记录当前路径的长度
bool hamCycleRecall(int graph[V][V], int path[V], bool visited[V],int len){
	if(len == V){ //访问到最后一个顶点
		if( graph[ path[V-1] ][0] == 1) //有到0点的边
			return true;
		else
			return false;
	}
	//遍历其它顶点
	for(int v = 1; v<V; v++){
		//如果没访问过,并且有边相连
		if(!visited[v] && graph[ path[len-1] ][v] ==1){
			visited[v] = true;
			path[len] = v;

			//找到了就直接返回
			if( hamCycleRecall(graph, path, visited, len+1) )
				return true;

			path[len] = -1;
			visited[v] = false;
		}
	}
	return false;
}

//查找从第一个顶点0开始,能否找到一条哈密顿回路。
bool hamCycle(int graph[V][V]){
	int path[V] = {-1};
	bool visited[V] = {0};
	path[0] = 0; 
	visited[V] = true; //第一个顶点标记为访问过

	//第一个顶点已确定,len从1开始
	if( hamCycleRecall(graph, path,visited, 1) == false){
		 printf("\nSolution does not exist");
		 return false;
	}

	printSolution(path);
	return true;
}

//测试
int main() {
/* 创建以下的图
      (0)--(1)--(2)
       |   / \   |
       |  /   \  |
       | /     \ |
      (3)-------(4)    */
	 int graph[V][V] = {{0, 1, 0, 1, 0},
	                      {1, 0, 1, 1, 1},
	                      {0, 1, 0, 0, 1},
	                      {1, 1, 0, 0, 1},
	                      {0, 1, 1, 1, 0},
	                     };

	hamCycle(graph);
	return 0;
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值