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

免责声明

不想打字了。。

题目

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

输入:

输入一行,有两个整数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)的操作,直到遍历完所有点。

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

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

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

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)。

 

有了初始的结构体,我们把它放到队列中,从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)
				setRobot(p,p.i+1,p.j);

			// 2、在本身放置。
			// 第一个判断条件是在它已经没有下方和右方的点的情况下,只能选择自身
			// 第二个判断条件是它的右边没有被监控
			if((p.i==m && p.j==n) || p.room_watched[p.i][p.j+1]==0)
				setRobot(p,p.i,p.j);

			// 3、在右方放置
			// 第一个判断条件是遍历点右边是没被监控的点
			// 第二个判断条件是遍历点右边的右边是没有监控的点
			if(p.j<n && (p.room_watched[p.i][p.j+1]==0 || p.room_watched[p.i][p.j+2]==0)) 
				setRobot(p,p.i,p.j+1);
        }
        // 如果房间全被监控了
        else(p.roomNum>=m*n)
        {
        	// 如果已安置的机器人数是目前最少的,更新结果ans
        	if(p.robotNum<ans)
        	{
	        	ans=p.robotNum;
	        	// 把这种安置方法保存到结果数组ans_arr里面
				memcpy(ans_arr, p.robot_position, sizeof(p.robot_position));
	        }	
        }
        	
    }

    // 打印结果和结果数组
	printf("%d\n",ans);
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=n;j++)
			printf("%d ",ans_arr[i][j]);
        printf("\n");
	}	
}

为什么那三个判断条件是那样的,俺也不太懂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<Node, vector<Node>, 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 <stdio.h>
#include <queue>

// 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<Node, vector<Node>, 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)
				setRobot(p,p.i+1,p.j);

			// 2、在本身放置。
			// 第一个判断条件是在它已经没有下方和右方的点的情况下,只能选择自身
			// 第二个判断条件是它的右边没有被监控
			if((p.i==m && p.j==n) || p.room_watched[p.i][p.j+1]==0)
				setRobot(p,p.i,p.j);

			// 3、在右方放置
			// 第一个判断条件是遍历点右边是没被监控的点
			// 第二个判断条件是遍历点右边的右边是没有监控的点
			if(p.j<n && (p.room_watched[p.i][p.j+1]==0 || p.room_watched[p.i][p.j+2]==0)) 
				setRobot(p,p.i,p.j+1);
        }
        // 如果房间全被监控了
        else(p.roomNum>=m*n)
        {
        	// 如果已安置的机器人数是目前最少的,更新结果ans
        	if(p.robotNum<ans)
        	{
	        	ans=p.robotNum;
	        	// 把这种安置方法保存到结果数组ans_arr里面
				memcpy(ans_arr, p.robot_position, sizeof(p.robot_position));
	        }	
        }
        	
    }

    // 打印结果和结果数组
	printf("%d\n",ans);
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=n;j++)
			printf("%d ",ans_arr[i][j]);
        printf("\n");
	}	
}

解空间树

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

搜索空间树

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

博物馆守卫问题可以用贪心算法来解决。具体来说,我们可以按照以下步骤来解决这个问题: 1. 将所有的展室按照面积从大到小排序。 2. 从最大的展室开始,依次安排守卫,直到所有的守卫都已经安排完毕为止。 3. 对于每一个展室,我们都选择离该展室最近的尚未被安排守卫守卫来进行安排。具体来说,我们可以维护一个守卫队列,每次选择队列头部的守卫来进行安排,并将该守卫从队列中删除。 下面是用C++实现的代码: ```c++ #include <iostream> #include <algorithm> #include <queue> #include <vector> using namespace std; const int MAX_N = 1000; // 展室结构体 struct Room { int area; // 展室的面积 int index; // 展室的下标 }; // 按照面积从大到小排序 bool cmp(Room a, Room b) { return a.area > b.area; } // 贪心算法求解 int solve(int n, int m, Room* rooms) { sort(rooms, rooms + n, cmp); // 按照面积从大到小排序 priority_queue<int, vector<int>, greater<int>> guards; // 守卫队列,使用小根堆来维护 for (int i = 0; i < m; i++) { guards.push(i); // 将所有守卫放入队列中 } int ans = 0; // 最小的最大展室面积 for (int i = 0; i < n; i++) { int guard = guards.top(); guards.pop(); ans = max(ans, rooms[i].area); // 更新最小的最大展室面积 // 将该守卫安排到当前展室 cout << "守卫" << guard << "负责展室" << rooms[i].index << endl; // 将该守卫所覆盖的展室标记为已经有守卫了 for (int j = i; j < n; j++) { if (rooms[j].index == rooms[i].index) { rooms[j].index = guard; } } guards.push(guard); // 将该守卫重新放入队列中 } return ans; } int main() { int n, m; Room rooms[MAX_N]; cin >> n >> m; for (int i = 0; i < n; i++) { cin >> rooms[i].area; rooms[i].index = i; } int ans = solve(n, m, rooms); cout << "最小的最大展室面积为:" << ans << endl; return 0; } ``` 注意,在实现中我们将展室按照面积从大到小排序,并且维护一个守卫队列,每次选择队列头部的守卫来进行安排。这个过程的时间复杂度是$O(n \log m)$,其中$n$是展室的数量,$m$是守卫的数量。如果展室数量很大,这个算法的效率可能会比较低。可以尝试使用其他更高效的算法来解决这个问题
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值