算法之穿越沙漠、狱吏问题

一、沙漠问题

1、问题描述

一辆吉普车来到1000km宽的沙漠边沿。吉普车的耗油量为1L/km,总装油量为500L。显然,吉普车必须用自身油箱中的油在沙漠中设几个临时 加油点,否则是通不过沙漠的。假设在沙漠边沿有充足的汽油可供使用,那么吉普车应在哪些地方、建多大的临的加油点,才能以最少的油耗穿过这块沙漠?

2、问题分析

算法之穿越沙漠、狱吏问题_第1张图片

从这个题目来看,这是一个极限问题,求得是最少耗油的量,所以只有唯一答案。

1、为了穿越这个沙漠,同时耗油量最少,那么吉普车就应该每次出发的时候都要满油量出发。

2、为了保证每一次都是满油量的从每个临时加油站出发,那么每一个临时加油站的油量储备都应该是500L的倍数。

3、为了保证耗油量最少,每一次建立一个临时加油站的时候,运输的次数也是越少越好,减少重复的路段。

4、如果这道题通过递推法正推的话很难确定第一个临时加油站的地点。

5、所以这里我们用倒推法来做。

6、假设我们已经到达了B点,这个时候B点的储油量应该0,这是可以倒推C1点的储油量应该为500L,这时候可以刚好到达终点B。好了现在我们知道C1点的储油量了,也就知道了C1到B点的距离为500米。

7、这时候我们应该想,如果向C1点运输500L的油量,C2点应该存储多少油呢?根据上面的步骤2(临时加油站的油量储备都应该是500L的倍数)和步骤3(运输的次数也是越少越好,这里肯定是两次或者两次以上才能向C1点运输500L油,这里取2次),那么我们就知道了C2点的储油量应该是500L*2,也就是1000L油。这时,C2到C1的距离为500/3。

8、如果想C2点运输1000L的油,重复步骤7可知,C3点存储油量为500×3L,C3到C2的距离为500/5。

9、所以到达临时加油站Cn的时候,储油量应该是500×n,Cn到Cn-1的距离为500/2n-1。
所以这里通过下面这个式子算出n:
500+500/3+500/5+500/(2n-1) = 1000;
然后就可以得出最少耗油量。

3.实现

public class Desert {
     
	public double totaldistance;//总长度
	public double speed;//单位距离的油耗速度
	public double oilper;//单次的最大负载

	public void result(double total,double speed,double oilper) {
     
		this.oilper=oilper;
		this.speed=speed;
		this.totaldistance=total;

		//假设都是满载行驶
		double distance = oilper/speed;//每段路的距离
		if(totaldistance <= distance) {
     
			System.out.println("总距离小于第一段距离,可以一次通过。");
		}

		//需要设置补给站点,每个站点的存储量为单次的最大负载的整数倍
		double dis=distance;
		int i=0;//站点个数
		while(dis < totaldistance) {
     
			//每一个站点的距离之和,即计算设置多少个站点才能通过totaldistance总长
			i++;
			dis+=1.0/(2*i+1)*distance;
		}
		int num=i;
		double totalOil = 0;
		while(i>0) {
     
			dis-=distance/(2*i+1);
			i--;
			if(totalOil==0) {
     
				totalOil=oilper*i+(totaldistance-dis)*(2*i+1)/speed;
			}
			System.out.println("第"+(num-i)+"个站点为距离起点"+(totaldistance-dis)+"处,存储量为"+oilper*i+"L");
		}
		System.out.println("总耗油量为"+totalOil+"L");
	} 

	public static void main(String[] args) {
     
		double total=1000.0;
		double speed=1.0;
		double oilper=500.0;
		Desert desert=new Desert();
		desert.result(total,speed,oilper);
	}

}

4.结果

1个站点为距离起点22.433122433122435处,存储量为3000.0L
第2个站点为距离起点60.89466089466089处,存储量为2500.0L
第3个站点为距离起点106.34920634920638处,存储量为2000.0L
第4个站点为距离起点161.90476190476193处,存储量为1500.0L
第5个站点为距离起点233.33333333333337处,存储量为1000.0L
第6个站点为距离起点333.33333333333337处,存储量为500.0L
第7个站点为距离起点500.0处,存储量为0.0L
总耗油量为3291.630591630592L

二、狱吏问题

1、问题描述

某王国对囚犯进行大赦,让一狱吏n次通过一排锁着的n间牢房,每通过一次按所定规则转动门锁,每转动一次,原来锁着的被打开,原来打开的被锁上;通过n次后,门锁开着的,牢房中的犯人放出,否则犯人不得获释.转动门锁的规则是这样的,第一次通过牢房,要转动每一把门锁,即把全部锁打开;第二次通过牢房时,从第二间开始转动,每隔一间转动一次;第k次通过牢房,从第k间开始转动,每隔k-1 间转动一次;问通过n次后,哪些牢房的锁仍然是打开的?

2、问题分析

牢房可以视作一个一元数组,1代表锁着,0代表开锁。
规律:当锁的标号为一个数的平方时,那么最后它会开着。

3.实现

public class Warder {
     
	public void WarderResult(int n) {
     
		//1表示锁着,0表示开锁
  		System.out.println("暴力法");
		int room[] = new int[n];

		int i;
		for (i=0;i<n;i++) {
     
			//初始状态都是锁着
			room[i]=1;
		}
		System.out.println("牢房数为"+n+"时");
		int k,j;
		for(i=1;i<=n;i++) {
     
			//i表示通过牢房次数
			System.out.print("第"+i+"次通过牢房");
			for(k=i;k<=n;k+=i) {
     
				if(room[k-1]==1) {
     
					room[k-1]=0;
				}else {
     
					room[k-1]=1;
				}
			}

			for(j=0;j<n;j++) {
     
				if(room[j]==0) {
     
					System.out.print(j+1+"、");
				}	
			}
		System.out.println("是开锁的");
		}
	}
	
	public void LawWarder(int n) {
     
		System.out.println("规律法");
		System.out.println("牢房数为"+n+"时");
		for(int i=1;i<+n;i++)
		{
     
			if(i*i<=n) {
     
				System.out.print(i*i+"、");
			}
		}
		System.out.println("是开锁的");
	}
	
	public static void main(String[] args) {
     
		int n=10;//牢房间数
		Warder warder= new Warder();
		warder.WarderResult(n);
		warder.LawWarder(n);
	}
}

4.结果

暴力法
牢房数为10时
第1次通过牢房12345678910、是开锁的
第2次通过牢房13579、是开锁的
第3次通过牢房1567、是开锁的
第4次通过牢房145678、是开锁的
第5次通过牢房1467810、是开锁的
第6次通过牢房147810、是开锁的
第7次通过牢房14810、是开锁的
第8次通过牢房1410、是开锁的
第9次通过牢房14910、是开锁的
第10次通过牢房149、是开锁的
规律法
牢房数为10149、是开锁的

你可能感兴趣的:(算法,算法)