算法分析课设(十一)博物馆守卫问题、世界名画陈列馆问题(分支界限法)

免责声明

不想打字了。。

题目

在某博物馆中摆放了非常重要的文物,为了节省人力,该博物馆专门购买了警卫机器人来看管这些文物。该博物馆的房间排列整齐,房间的大小相同。每个警卫机器人能够巡查的范围除本身所在房间外,还包括其起始安放的房间的上下左右四个房间。为了减少摆放的机器人的数量,请你设计一种最佳的摆放方案,使得摆放的机器人数量最少。

输入:

输入一行,有两个整数m,n,分别表示该博物馆每行的房间数和每列的房间数。博物馆总房间数即为m*n。

输出:

输出的第一行表示需要的机器人的数量,其后m行,每行有n个元素,每个元素的值为0或1,分别表示对应的房间是否摆放机器人。0表示不摆放,1表示需要摆放。

样例输入:

4 4

样例输出:

4

0 0 1 0

1 0 0 0

0 0 0 1

0 1 0 0

要求

1、写出采用分支限界法求解样例输入时的求解过程。

2、写出采用分支限界法算法分析过程,编写程序求解上述问题,并分析算法的时间复杂度。

参考文章

虽然是参考文章但是先不要看。

https://blog.csdn.net/TheWise_lzy/article/details/111414048

https://www.cnblogs.com/Deribs4/p/5657746.html

分析

首先要介绍一下快照这个词。比如你给你的电脑系统保存一个快照,那么在你把系统删了之后,拿快照过来又可以恢复原样。

下面要讲的代码就是这样的。

(1)我们对初始化的博物馆(二维数组)保存“快照”(后面都不打引号了,快照其实是对结构体的比喻)。

(2)遍历第一个点,在3种可能的位置放置机器人(不要问为什么是3种,俺也不知道T_T),它们会改变二维数组,把它们存为3张快照,放入优先队列进行排序,它会把监控房间最多的那个放到队列顶端。

(3)继续下一个点的遍历。pop队列,取出最优的那个快照继续重复(2)的操作,直到遍历完所有点。

所以,分支界限法的特点就是要用到广度优先遍历,广度优先遍历通常需要队列。每次广度遍历完,会取出最优解,在此基础上进行下一次广度遍历。这个代码里面是没有递归的!

算法分析课设(十一)博物馆守卫问题、世界名画陈列馆问题(分支界限法)_第1张图片

注意上图有三个分支,这对于画解空间树和搜索空间树很重要。 

现在开始讲代码。如何构造这个快照(结构体)呢?

struct Node{
    // 机器人位置 
	int robot_position[30][30];
    // 被监视的房间位置
	int room_watched[30][30]; 
    // (i,j)为当前遍历到的坐标
	int i,j; 
    // 机器人数   被监视的房间个数
    int robotNum,roomNum;
};

 接下来对它进行初始化。

Node init(Node node)
{
	// 初始化robot_position数组全为0
	memset(node.robot_position,0,sizeof(node.robot_position)); 
	// 初始化room_watched数组全为0
	memset(node.room_watched,0,sizeof(node.room_watched)); 
	// 当前点在i=1,j=1
	node.i=1;node.j=1;
	// 当前的机器人数;当前的监控房间数
	node.robotNum=0;node.roomNum=0;

	// 在博物馆的四边扩充1个单位的行列
	for(int i=0;i<=n+1;i++)
		node.room_watched[i][0]=node.room_watched[i][m+1]=1;
	for(int i=0;i<=m+1;i++)
		node.room_watched[0][i]=node.room_watched[n+1][i]=1;
	return node;
}

我把这个结构体大致画了一下,初始化的时候,博物馆的二维数组外层一圈灰的是特意增加的,因为放置机器人的时候,会把机器人上下左右的点都设置成被监控的状态,为了边界不进行越界判断,就添加了一圈外围的行列。 所以遍历的点从(1,1)开始,到(m,n)结束(图里的n写错了,是m)。

 算法分析课设(十一)博物馆守卫问题、世界名画陈列馆问题(分支界限法)_第2张图片

有了初始的结构体,我们把它放到队列中,从while循环开始遍历了。

// m*n的房间
int m,n;
//最优结果 ans为机器人个数 ans_arr为每个机器人的位置
int ans_arr[30][30],ans;

int main()
{
	// 输入行列
	scanf("%d%d",&m,&n);
	// 机器人最多的数量
	ans=m*n/3+2;
	// 初始化
	Node node;
	node=init(node);
	// 快照放入队列
	q.push(node);
	// 如果队列不空
	while(!q.empty())
    {
    	// 返回队列第一个
        Node p=q.top();
        // 队列把第一个弹走
        q.pop();
        
        // 如果房间没有全被监控,则分别在当前遍历点的下方、本身、右方放置机器人
        // 注意这三种情况是互不干扰的,它们会生成三种快照,判断出用机器人最少的一个
        if(p.roomNum < m*n)
		{	
			// 1、在下方放置
			// 判断条件就是下方有位置可放,不能在最后一行)
			if(p.i=m*n)
        {
        	// 如果已安置的机器人数是目前最少的,更新结果ans
        	if(p.robotNum

为什么那三个判断条件是那样的,俺也不太懂T_T。

我们调用了setRobot函数,传入的参数是当前快照,要放置的坐标。放置完之后,这新的三个快照被放入优先队列,选择最优那个到队列顶端。

// 自身和上下左右5个方位
int position[5][2]={
	{0, 0}, //自身
	{0, 1}, //右方
	{0,-1}, //左方
	{1, 0}, //下方
	{-1,0}  //上方
};

// 重写优先队列的比较函数
struct cmp 
{
    bool operator() (Node a, Node b)
    {
    	// 比较哪个快照的被监控房间多(小顶堆)
        return a.roomNum > b.roomNum; 
    }
};

//C++的优先队列,存入快照时,cmp函数会自动执行
priority_queue, cmp> q;

void setRobot(Node p,int x,int y)
{
	// 以下几行都是在复制一份快照p
	Node node;
	node=init(node);
	node.i=p.i;
	node.j=p.j;
	node.roomNum=p.roomNum;
	memcpy(node.robot_position, p.robot_position, sizeof(p.robot_position));
	memcpy(node.room_watched, p.room_watched, sizeof(p.room_watched));
	// 在(x,y)点新增机器人,机器人数量要+1
	node.robot_position[x][y]=1;
	node.robotNum=p.robotNum+1;

	// 对这个新增机器人的上下左右和自身标记被监控
	for(int d=0;d<5;d++)
    {
    	// pos_x,pos_y表示机器人上下左右位置,我们标记这些位置的房间被监控
        int pos_x=x+position[d][0];
        int pos_y=y+position[d][1];
        node.room_watched[pos_x][pos_y]++;

        // 标记一个房间,roomNum就加一。
        // 一定要等于1,因为有的房间会被重复监控,那就是2了
        if(node.room_watched[pos_x][pos_y]==1) 
		{
			node.roomNum++;
		}
    }

    // 如果行数不越界 且 当前点被监控了
    while(node.i<=m && node.room_watched[node.i][node.j]) 
	{
		// 当前点的列右移一个单位
		node.j++;
		// 如果右移之后越界了,就换行
		if(node.j>n)
			node.i++,node.j=1; 
	}

	// 把当前快照存到优先队列里,会调用cmp排序,保证最顶端的是最优的快照
	q.push(node);
	return;
}

完整代码

#include 
#include 

// m*n的房间
int m,n;
// 自身和上下左右5个方位
int position[5][2]={
	{0, 0},
	{0, 1},
	{0,-1},
	{1, 0},
	{-1,0}
};
//最优结果 ans为机器人个数 ans_arr为每个机器人的位置
int ans_arr[30][30],ans;

struct Node{
    // 机器人位置 
	int robot_position[30][30];
    // 被监视的房间位置
	int room_watched[30][30]; 
    // (i,j)为当前遍历到的坐标
	int i,j; 
    // 机器人数   被监视的房间个数
    int robotNum,roomNum;
};

// 优先队列的比较函数重写
struct cmp 
{
    bool operator() (Node a, Node b)
    {
    	// 比较哪个快照被监视房间多(小顶堆)
        return a.roomNum > b.roomNum; 
    }
};

//优先队列,cmp函数会自动执行
priority_queue, cmp> q;

Node init(Node node)
{
	// 初始化robot_position数组全为0
	memset(node.robot_position,0,sizeof(node.robot_position)); 
	// 初始化room_watched数组全为0
	memset(node.room_watched,0,sizeof(node.room_watched)); 
	// 当前点在i=1,j=1
	node.i=1;node.j=1;
	// 当前的机器人数;当前的监控房间数
	node.robotNum=0;node.roomNum=0;

	// 在博物馆的上下扩充两行
	for(int i=0;i<=m+1;i++)
		node.room_watched[i][0]=node.room_watched[i][m+1]=1;
	// 在博物馆的左右扩充两列
	for(int i=0;i<=n+1;i++)
		node.room_watched[0][i]=node.room_watched[n+1][i]=1;
	return node;
}

void setRobot(Node p,int x,int y)
{
	// 以下几行都是在复制一份快照p
	Node node;
	node=init(node);
	node.i=p.i;
	node.j=p.j;
	node.roomNum=p.roomNum;
	memcpy(node.robot_position, p.robot_position, sizeof(p.robot_position));
	memcpy(node.room_watched, p.room_watched, sizeof(p.room_watched));

	// 在(x,y)点新增机器人,机器人数量要+1
	node.robot_position[x][y]=1;
	node.robotNum=p.robotNum+1;

	// 对这个新增机器人的上下左右和自身标记被监控
	for(int d=0;d<5;d++)
    {
    	// pos_x,pos_y表示机器人上下左右位置,我们标记这些位置的房间被监控
        int pos_x=x+position[d][0];
        int pos_y=y+position[d][1];
        node.room_watched[pos_x][pos_y]++;

        // 标记一个房间,roomNum就加一。
        // 一定要等于1,因为有的房间会被重复监控,那就是2了
        if(node.room_watched[pos_x][pos_y]==1) 
		{
			node.roomNum++;
		}
    }

    // 如果行数不越界 且 当前点被监控了
    while(node.i<=m && node.room_watched[node.i][node.j]) 
	{
		// 当前点的列右移一个单位
		node.j++;
		// 如果右移之后越界了,就换行
		if(node.j>n)
			node.i++,node.j=1; 
	}

	// 把当前快照存到优先队列里,会调用cmp排序,保证最顶端的是最优的快照
	q.push(node);
	return;
}

int main()
{
	// 输入行列
	scanf("%d%d",&m,&n);
	// 机器人最多的数量
	ans=m*n/3+2;
	// 初始化
	Node node;
	node=init(node);
	// 快照放入队列
	q.push(node);
	// 如果队列不空
	while(!q.empty())
    {
    	// 返回队列第一个
        Node p=q.top();
        // 队列把第一个弹走
        q.pop();
        
        // 如果房间没有全被监控,则分别在当前遍历点的下方、本身、右方放置机器人
        // 注意这三种情况是互不干扰的,它们会生成三种快照,判断出用机器人最少的一个
        if(p.roomNum < m*n)
		{	
			// 1、在下方放置
			// 判断条件就是下方有位置可放,不能在最后一行)
			if(p.i=m*n)
        {
        	// 如果已安置的机器人数是目前最少的,更新结果ans
        	if(p.robotNum

解空间树

我不太会,应该是个三叉数,把每种情况都画出来。

搜索空间树

它应该是在解空间的基础上剪枝。

你可能感兴趣的:(算法设计与分析)