四个矿工过隧道的算法求解

四个矿工必须通过一条隧道。他们只有一盏提灯,隧道一次只能通过两人,而在通过隧道时必须提着提灯。他们不能在隧道里停下。一个矿工能用一分钟通过隧道,而其他人分别需要2、4和8分钟。当两个矿工一起通过时,只能按比较慢的矿工的速度前行。请问如何让四个矿工在15分钟内全部通过?请解释你的解决方案。
一看这样的题目,就是深度搜索加剪枝。
C++代码(为了更直观,都放在一个文件里,实际中应该分头文件和源文件)

#include 
#include 
#include 
#include 
#include

using namespace std;

const int maxTotalTime = 15; //总共耗时限制

struct MinersState
{
	int miners[4] = { 1, 2, 4, 8 }; //1, 2, 4, 8分别表示4个矿工的耗时
	bool bLocal = true;			    //true表示在本地,false表示去了对面
	int lastTime = 0;				//初始为0,这个变量表示需要的时间
	
	//最终状态,全部为负表示都去了对面
	bool IsFinalState()
	{
		if ((miners[0] < 0) && (miners[1] < 0) && (miners[2] < 0) && (miners[3] < 0)
			&& (bLocal == false)) 
		{
			return true;
		}
		return false;
	}

	//打印当前位置
	void PrintStates() 
	{
		cout << "[";
		for (size_t i = 0; i < 4; i++)
		{
			cout << miners[i] << ",";
		}
		cout << "]";
		if (bLocal) {
			cout << " light is here" << endl;
		}
		else
		{
			cout << " light is there" << endl;
		}
	}

	//是否是相同位置(包括矿工和灯的位置)
	bool IsSameState(const MinersState& state) const 
	{
		if ((miners[0] == state.miners[0]) &&
			(miners[1] == state.miners[1]  &&
			(miners[2] == state.miners[2]) &&
			(miners[3] == state.miners[3]) && 
				bLocal == state.bLocal))
		{
			return true;
		}
		return false;
	}
};

void SearchState(deque<MinersState>& states);

void PrintResult(deque<MinersState>& states)
{
	cout << "Find Result : " << endl;
	for_each(states.begin(), states.end(),
		mem_fun_ref(&MinersState::PrintStates));
	cout << endl << endl;
}

int calTotalTime(deque<MinersState>& states)
{
	int sum = 0;
	for (size_t i = 0; i < states.size(); ++i)
	{
		sum += states.at(i).lastTime;
	}
	return sum;
}

bool IsCurrentActionValid(deque<MinersState>& states, MinersState& next, int pos0, int pos1)
{
	if ( pos0 >= 0 && pos0 <= 3 && pos1 < 0 )
	{
		if ( (next.bLocal && (next.miners[pos0] > 0)) ||
			(!next.bLocal && (next.miners[pos0] < 0)))  //灯和带灯的矿工在隧道的同一边
		{
			int sum = calTotalTime(states);
			sum += next.lastTime;

			if (sum <= maxTotalTime)
			{
				return true;
			}
		}
	}
	
	if ((pos0 >= 0 && pos0 <= 3) && (pos1 >= 0 && pos1 <= 3))
	{
		if ((next.bLocal && next.miners[pos0] > 0 && next.miners[pos1] > 0) ||
			(!next.bLocal && next.miners[pos0] < 0 && next.miners[pos1] < 0))  //这次是这边 
		{
			int sum = calTotalTime(states);
			sum += next.lastTime;

			if (sum <= maxTotalTime)
			{
				return true;
			}
		}
	}
	
	//灯在这边,所有矿工却都在对面,这种情况不允许发生(多虑了)
	if ((next.bLocal && next.miners[0] < 0 && next.miners[1] < 0 && next.miners[2] < 0 && next.miners[3] < 0) ||
		(!next.bLocal && next.miners[0] > 0 && next.miners[1] > 0 && next.miners[2] > 0 && next.miners[3] > 0))
	{
		return false;
	}
	
	return false;
}

bool IsSameBucketState(MinersState state1, MinersState state2)
{
	return state1.IsSameState(state2);
}

//是否已经处理过?
bool IsProcessedState(deque<MinersState>& states, const MinersState& newState)
{
	deque<MinersState>::iterator it = states.end();

	it = find_if(states.begin(), states.end(),
		bind2nd(ptr_fun(IsSameBucketState), newState));

	return (it != states.end());
}

void SearchStateOnAction(deque<MinersState>& states, MinersState& current, int pos0, int pos1)
{
	MinersState next;
	next.bLocal = !current.bLocal;  //每次过隧道,灯的位置就要改变

	for (size_t i = 0; i < 4; i++)
	{
		next.miners[i] = current.miners[i];
	}

	if (pos0 >= 0 && pos0 <= 3)
	{
		next.miners[pos0] = -next.miners[pos0]; //每次符号变换,表示从隧道一头到另外一头
		next.lastTime = abs(next.miners[pos0]);
	}

	if (pos1 >= 0 && pos1 <= 3)
	{
		next.miners[pos1] = -next.miners[pos1];
		next.lastTime = abs(next.miners[pos1]);
	}

	//pos0和pos1都在0~3范围内,说明是2个矿工一起过隧道,取两者过隧道绝对值大的,作为2者过隧道的总时间
	if ((pos0 >= 0 && pos0 <= 3) && (pos1 >= 0 && pos1 <= 3))
	{
		next.lastTime = (abs(next.miners[pos0]) > abs(next.miners[pos1])) ? abs(next.miners[pos0]) : abs(next.miners[pos1]);
	}

	if (IsCurrentActionValid(states, next, pos0, pos1))
	{
		if (!IsProcessedState(states, next))
		{
			states.push_back(next);
			SearchState(states);
			states.pop_back();
		}
	}
}

void SearchState(deque<MinersState>& states)
{
	MinersState current = states.back();
	if (current.IsFinalState())
	{
		PrintResult(states);
		return;
	}

	//2个人过去,一个人把灯带回来;然后两个人过去,重复这个过程...
	if (current.bLocal) //灯在这边 
	{
		//从正数中选2个,正数表示还还没过隧道的矿工
		for (int i = 0; i < 3; ++i)
		{
			for (int j = i+1; j < 4; ++j)
			{
				if ((current.miners[i] > 0) && current.miners[j] > 0)
				{
					SearchStateOnAction(states, current, i, j);
				}
			}
		}
	}
	else 
	{
		//从负数中选1,负数表示隧道对面的矿工
		for (int i = 0; i < 4; ++i) 
		{
			if (current.miners[i] < 0) 
			{
				SearchStateOnAction(states, current, i, -1);
			}
		}
	}
}

int main()
{
	deque<MinersState> states;
	MinersState init;

	states.push_back(init);
	SearchState(states);

	cout << "seaching finished ---" << endl;
	return 0;
}

运行结果:
四个矿工过隧道的算法求解_第1张图片
解释:有两种方案:A:1,B:2,C:4;D:8,这四位矿工。
方案一:(1)A和B过去,耗时2分钟
(2)A带着灯返回,耗时1分钟;
(3)C和D带着灯过隧道,耗时8分钟;
(4)B带着灯返回,耗时2分钟;
(5)A和B一起通过隧道,到达目的地,耗时2分钟。
总耗时:2 + 1 + 8 + 2 + 2 = 15

方案二:(1)A和B过去,耗时2分钟
(2)B带着灯返回,耗时2分钟;
(3)C和D带着灯过隧道,耗时8分钟;
(4)A带着灯返回,耗时1分钟;
(5)A和B一起通过隧道,到达目的地,耗时2分钟。
总耗时:2 + 2 + 8 + 1 + 2 = 15

你可能感兴趣的:(数据结构与算法)