用集合模拟过河问题中的贪心算法

前段时间在看到个帖子问DNF主播4v4分组器的,然后自己模拟了下分组,最佳支付有但是没有很清晰的想法, 有人指点说贪心算法,奈何上学的时候没有好好学,只知道个大概,最后辗转想起了贪心算法的过河问题,就又搜了搜关于过河问题的帖子。居然又有了新发现,当n=4时,要看t1+t3与2*t2的大小来决定是只由t1来回还是t1与t2分别来回。先看别人的帖子,但大都是抄袭感觉,很明显的错误都是错的一样的,4个人过河总共5次的时间相加,5个人只用6次?假设我们都由t1来回,接人的顺序由小到大,那么时间分别为t2+t1+t3+t1+t4+t1+t5,这最少是7次啊,可是网上的帖子全都是T2+T3+T1+T4+T1+T5,那么t1有一次没回去就直接把人接过来了还是t1回去是瞬移回去的?罢了罢了,小问题无伤大雅。
但是他们帖子的核心都分析的没大问题,还有一个推广,值得学习,我虽然理解他的意思,但是还是推不出来,所以我就比较笨的模拟了一下从n=5到n=8的情况
在这里插入图片描述
推暂时就不推了吧,脑子笨推不出来那就用过程来模拟,图片里的背景是,t1<t2<t3<t4<t5<t6<t7<t8,并且默认不只使用t1传电筒,通过图片的分析情况来看,我发现一个规律,那就是只要t1与t2同在A那么这次就t1与t2一起到B,然后t1回来,然后最大的一组到B,也就是是只要t1与t2不同在A,那么就从A选取最大的两个到B,然后回t2。我看网上的其他帖子都是到了A这边的个数小于3的时候,每次都要做判断size分别采取不同的逻辑,但是我做这个没有发现需要做此处理,可能因为我是模拟的过程,唯一需要做处理的就是当A的size等于2的时候,A此时过去B2个人,此时B无需再返回一个人来。参考其他人的代码是用的数组,我第一想法是用集合,然后经常需要拿最大的数据和最小的数据,所以需要一直保证顺序,所以我决定采用TreeSet集合,因为添加进去的元素都会自动排序。接下来贴上代码:

package cn.jq.lee;

import java.util.Scanner;
import java.util.TreeSet;

public class PassRiver3 {
	public static void main(String[] args) {
		TreeSet<Integer> a = new TreeSet<>();
		TreeSet<Integer> b = new TreeSet<>();
		Scanner in = new Scanner(System.in);
		System.out.println("注意!仅录入一组整数数据。");
		System.out.println("请输入要录入的数据的个数:");
		int count = in.nextInt();
		int n;
		//循环录入每个人的耗时,无需按大小顺序,但不可重复
		while (count > 0) {
			count--;
			n = in.nextInt();
			a.add(n);
		}
		int sum = 0;
		int first;
		int second;
		//获取t1与t2的耗时,因为如果是t1与t2来回传,这两个数值会一直被用到
		first = a.first();
		second = a.higher(a.first());
		// 2*t2与t1+t(n-1)比较是用t1来回传还是用t1与t2来回传
		if (2 * a.higher(a.first()) < (a.first() + a.lower(a.last()))) {
			//t1与t2来回传
			while (a.size() > 0) {
				// 如果A这边这次往B大于2个人,那么每次A过去2个人,同时B还要返回耗时最小的一个人,这个人只能是t1与t2
				if (a.size() > 2) {
					// 如果最开始最小的和次小的都在a这边,则t1与t2过河,否则,最大的过河
					if (a.first() == first & a.higher(a.first()) == second) {
						// a->b
						b.add(a.first());
						a.remove(a.first());
						sum += a.first();
						b.add(a.first());
						a.remove(a.first());
					} else {
						// a->b
						sum += a.last();
						b.add(a.last());
						a.remove(a.last());
						b.add(a.last());
						a.remove(a.last());
					}
					// b->a,无论A过去的是最大的还是最小的一组,B返回给A的永远都是耗时最少的那个人,所以这一部分可以共用,没必要写到if语句内
					a.add(b.first());
					sum += b.first();
					b.remove(b.first());
				} else if (a.size() == 2) {
					// 如果A这边这次往B等于2个人,那么A过去以后,B无需再返回任何人员,且A过去最后2人之后,A.size()为0,while循环也该结束了
					// a->b
					b.add(a.first());
					a.remove(a.first());
					sum += a.first();
					b.add(a.first());
					a.remove(a.first());
				} else {
					System.out.println("如果走到这里说明逻辑出了问题");
				}
			}
		}else{
			//t1来回传
		}
		System.out.println(sum);

	}
}

以上代码的逻辑做一个说明,另外因为还有另一个版本,这个是在过河之前就对A的size做了一个是否等于2的判断。接下来另一个版本:

package cn.jq.lee;

import java.util.Scanner;
import java.util.TreeSet;

public class PassRiver {
	public static void main(String[] args) {
		TreeSet<Integer> a = new TreeSet<>();
		TreeSet<Integer> b = new TreeSet<>();
		Scanner in = new Scanner(System.in);
		System.out.println("注意!仅录入一组整数数据。");
		System.out.println("请输入要录入的数据的个数:");
		int count = in.nextInt();
		int n;
		// 循环录入每个人的耗时,无需按大小顺序,但不可重复
		while (count > 0) {
			count--;
			n = in.nextInt();
			a.add(n);
		}
		int sum = 0;
		int first;
		int second;
		// 获取t1与t2的耗时,因为如果是t1与t2来回传,这两个数值会一直被用到
		first = a.first();
		second = a.higher(a.first());
		// 2*t2与t1+t(n-1)比较是用t1来回传还是用t1与t2来回传
		if (2 * a.higher(a.first()) < (a.first() + a.lower(a.last()))) {
			// t1与t2来回传
			while (a.size() > 0) {
				int cur_Size=a.size();
				// 如果A这边这次往B大于2个人,那么每次A过去2个人,同时B还要返回耗时最小的一个人,这个人只能是t1与t2
				// 如果最开始最小的和次小的都在a这边,则t1与t2过河,否则,最大的过河
				if (a.first() == first & a.higher(a.first()) == second) {
					// a->b
					b.add(a.first());
					a.remove(a.first());
					sum += a.first();
					b.add(a.first());
					a.remove(a.first());
				} else {
					// a->b
					sum += a.last();
					b.add(a.last());
					a.remove(a.last());
					b.add(a.last());
					a.remove(a.last());
				}
				// b->a,无论A过去的是最大的还是最小的一组,B返回给A的永远都是耗时最少的那个人,所以这一部分可以共用,没必要写到if语句内
				if (cur_Size != 2) {
					a.add(b.first());
					sum += b.first();
					b.remove(b.first());
				}
			}
		} else {
			// t1来回传
		}
		System.out.println(sum);
	}
}

这个版本在过河之前先记录了当前A的size,在过河之后判断当前size是否等于2,不等于2则B需要回来一个,等于2则A过去2个B不回,至此while循环也结束了。(似乎可以不记录size,在执行完过河的操作以后判断A的size是否为0,为0代表没有人了,则B不需要回来人,不为0则B仍需要回来一个人)
好吧吃饭的时候左思右想,最后也验证了一下,被一个博客给误导了,该博客的结论为:以此类推,两种方案的差异,只与最快的人、次快的人和次慢的人的单独过桥时间有关,而与其他人的快慢无关。所以我在前面写了一个if (a.first() == first & a.higher(a.first()) == second) 的判断,等再想想以后再改。
到最后还是免不了对size的大小为0、1、2分别进行判断,这个方案真的只能说勉勉强强模拟了如何由A->B的过程,算法还谈不上,优点更不存在,需要处理和顾及的东西太多。代码贴出来,虽然没啥用,但是也是心血,哎

package cn.jq.lee;

import java.util.Scanner;
import java.util.TreeSet;

public class PassRiver {
	static TreeSet<Integer> a = new TreeSet<Integer>();
	static TreeSet<Integer> b = new TreeSet<Integer>();
	static int sum = 0;
	public static void main(String[] args) {
		
		Scanner in = new Scanner(System.in);
		System.out.println("注意!仅录入一组整数数据。");
		System.out.println("请输入要录入的数据的个数:");
		int count = in.nextInt();
		int a_b_1=-1;
		int a_b_2=-1;
		int n;
		// 循环录入每个人的耗时,无需按大小顺序,但不可重复
		while (count > 0) {
			count--;
			n = in.nextInt();
			a.add(n);
		}
		int first = -1;
		int second = -1;
		// 获取t1与t2的耗时,因为如果是t1与t2来回传,这两个数值会一直被用到
		if (a.size() >= 2) {
			first = a.first();
			second = a.higher(a.first());

			Object[] array = a.toArray();
			for (count = array.length - 1, n = 0; count > 2; count -= 2, n += 2) {
				sum += (Integer) array[count-1] + (Integer) array[0];
			}
			if (n * second < sum) {
				sum = 0;
				// t1与t2来回传
				while (a.size() > 0) {
					// int cur_Size=a.size();
					// 如果A大于2个人,那么每次A过去2个人,同时B还要返回耗时最小的一个人,这个人只能是t1与t2
					// 如果最开始最小的和次小的都在a这边,则t1与t2过河,否则,最大的过河
					if (a.contains(first) & a.contains(second)) {
						// a->b
						a_b_1=a.first();
						a_b_2=a.higher(a_b_1);
					} else {
						// a->b
						a_b_1=a.last();
						a_b_2=a.lower(a_b_1);
					}
					// b->a,无论A过去的是最大的还是最小的一组,B返回给A的永远都是耗时最少的那个人,所以这一部分可以共用,没必要写到if语句内
					move(a_b_1,a_b_2,a);
				}
			} else {
				// t1来回传
				sum = 0;
				while (a.size() > 0) {
					a_b_1=a.first();
					a_b_2=a.higher(a_b_1);
					move(a_b_1,a_b_2,a);
				}
			}
		} else if (a.size() == 1) {
			sum += a.first();
			b.add(a.first());
			a.remove(a.first());
		} else {
			System.out.println("可能没有输入任何东西");
		}
		System.out.println(sum);
	}
	private static void move(int a_b_1,int a_b_2,TreeSet<Integer> a) {
		int max=(a_b_1>a_b_2)?a_b_1:a_b_2;
		b.add(a_b_1);
		a.remove(a_b_1);
		b.add(a_b_2);
		a.remove(a_b_2);
		sum+=max;
		if (a.size() != 0) {
			a.add(b.first());
			sum += b.first();
			b.remove(b.first());
		}
		
	}
}

总结一下吧,第一,某些博客分析过程存在小问题,但无伤大雅;第二,某些博客分析结论存在重大问题。第三,还是得自己分析,引用别人的成果之前或者之后,还是得细究是否正确。写这个没有深入研究过别人的贪心算法的代码,看的最多的也就是那个分析结论存在重大问题的从数学角度分析的博客,个人觉得这个跟用算法得出最优时间还是差距很大的,只是模拟过程而已,也不是太适合用来进行过程分析,算经验积累吧。后续可能会专门对算法进行研究然后自己写一个过河问题的最优算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值