您的位置:首页 > 其它

关于《河边捡石头》问题的深夜随想

2012-06-10 01:11 232 查看
深夜郁闷失眠,见到同事UU在讨论河边捡石头的问题,随手写下个程序。

这个问题主要同爱情婚姻有关,但表现形式已经出现N多个版本了,果园摘苹果,麦田捡稻穗等等,总觉得是应试作文害的...UU版本的问题是这样的:

  沿着河走捡石头,不能回头,只能捡一次,捡了就不能换,捡尽量大(当时理解是最重)的那颗回来。

最近感情方面有所感触,一时兴起写下以下用于该问题策略可行性的检测程序。有想法的人只需要实现接口IStrategy描述自己的策略, 通过Test执行后可得到在该策略下如 多次运行的平均值,最优值等结果以验证其策略的合理性。测试代码大致说明如下:

  1. 河边共有STONE_NUM = 1000个石头

  2. 通过TEST_TIMES = 500轮随机测试得出结果,其中每轮的石头通过随机算法重新生成, 石头重量符合正态分布, 最大可能值为1000。

  3. 为了防止无意识下的作弊(某人一上手就默认最大的石头为1000而且乐于作弊ya囧),于是每轮石头的最大重量也是在1 - 1000之间随机的。

  4. 由3策略的存在,使用石头重量作为评分标准不大合理。于是想了个新的评分标准:每轮石头按重量升序排序,最大的石头分数p最高线性分布且归一化,p ε [0,1]。最小的石头分数应该是1/STONE_NUM的,而当一轮内放弃所有石头才拿到0。

  5. 代码中有两个策略实现示例:SimpleStrategy2捡第一个石头... SimpleStrategy1将前一半的石头作为统计样本,在后半段中见到比前半段最好的石头更好时就捡掉。

策略接口

public interface IStrategy {
/** index为石头序号, stone的值即非负数重量 */
public boolean isPickup(int index, double stone);

/** 为了strategy对象多次运行,需要重置环境 */
public void reset();
}


测试主程序

import java.util.Random;

/**
* 石头的得分gold由其重量排名决定,0 <= gold <= 1 </br>
* 通过实现IStrategy改写策略,见main函数 </br>
* 实验会通过多次计算得到, avgGold平均值,0 <= avgGold <= 1.0 </br>
* @author cjy
*/
public class Test {
public static final int STONE_NUM = 300; // 1k个石头的假设
public static int TEST_TIMES = 500; // 测试100次
public static double MAX_STONE = 100; // 石头重量的最大可能值

public static void main(String[] args) {
test(new SimpleStrategy1(0.5)); // 在此处定制策略
}

public static void test(IStrategy strategy) {
Test test = new Test(strategy);
long b = System.currentTimeMillis();
test.run();
long e = System.currentTimeMillis();
System.out.println("run time:" + (e - b));
System.out.println("avgGold = " + test.avgGold());
System.out.println("maxGold = " + test.maxGold());
}

private double[] stones = new double[STONE_NUM]; // 石头,以double类型表示其大小
private double[] stoneGolds = new double[STONE_NUM]; // 石头对应的分数, 通过排序决定.
private double[] goldsInEachTime = new double[TEST_TIMES];

final IStrategy strategy;

public Test(IStrategy strategy) {
if (strategy == null) {
throw new NullPointerException("ya?");
}
this.strategy = strategy;
}

public void run() {
for (int i = 0; i < TEST_TIMES; i++) {
resetRiverEnv();
this.goldsInEachTime[i] = riverTrav();
}
}

public double avgGold() {
if (goldsInEachTime == null) {
throw new IllegalArgumentException("please run.");
}
double sum = 0;
for (double gold : goldsInEachTime) {
sum += gold;
}
return sum / TEST_TIMES;
}

public double maxGold() {
if (goldsInEachTime == null) {
throw new IllegalArgumentException("please run.");
}
double max = 0;
for (double gold : goldsInEachTime) {
if (gold > max) {
max = gold;
}
}
return max;
}

private void resetRiverEnv() {
resetStones();
resetStoneGolds();
strategy.reset();
}

private void resetStones() {
Random r = new Random();
double baseStone = r.nextDouble() * MAX_STONE;
double rNum;
for (int i = 0; i < STONE_NUM; i++) {
while (Math.abs(rNum = r.nextGaussian()) > 3)
;
stones[i] = (rNum + 3) / 6 * baseStone;
}
}

// 请用力吐槽
private void resetStoneGolds() {
int t;
boolean loopOver;
int[] goldIndex = new int[STONE_NUM];
for (int i = 0; i < STONE_NUM; i++) {
goldIndex[i] = i;
}
for (int i = STONE_NUM; i > 0; i--) {
loopOver = true;
for (int j = 0; j < i - 1; j++) {
if (stones[goldIndex[j]] > stones[goldIndex[j + 1]]) {
t = goldIndex[j];
goldIndex[j] = goldIndex[j + 1];
goldIndex[j + 1] = t;
loopOver = false;
}
}
if (loopOver) {
break;
}
}

for (int i = 0; i < STONE_NUM; i++) {
int gold = i;
int index = goldIndex[i];
while (gold > 0 && (stones[goldIndex[gold]] == stones[goldIndex[gold - 1]])) {
gold--;
}
stoneGolds[index] = gold;
}

for (int i = 0; i < STONE_NUM; i++) {
stoneGolds[i] = (stoneGolds[i] + 1) / STONE_NUM; // 归一化
}
}

private double riverTrav() {
try {
for (int i = 0; i < STONE_NUM; i++) {
double stone = stones[i];
if (strategy.isPickup(i, stone)) {
return stoneGolds[i];
}
}
return 0.0;
} catch (Throwable t) {
t.printStackTrace();
return 0.0;
}
}
}


两个简单的策略示范

public class SimpleStrategy1 implements IStrategy {
double max = Double.MIN_VALUE;
final double rate;

public SimpleStrategy1(double rate) {
this.rate = rate;
}

@Override
public boolean isPickup(int index, double stone) {
if (index == Test.STONE_NUM - 1) {
return true;
}
if (index <= Test.STONE_NUM * rate) {
max = Math.max(stone, max);
return false;
} else {
if (stone >= max) {
return true;
} else {
return false;
}
}
}

@Override
public void reset() {
this.max = Double.MIN_VALUE;
}
}

public class SimpleStrategy2 implements IStrategy {
@Override
public boolean isPickup(int index, double stone) {
return true;
}

@Override
public void reset() {
}
}


策略2的平均得分是0.5,策略1的平均得分为0.75。据说策略1的rate值取1/e时最优,实测是到0.08左右最优,得分为0.91

欢迎大家提交各自的策略及测试结果。

在邹欣老师的建议对博文做出了修改,谢谢。

-------------------------华丽分割线以上是程序,以下是牢骚----------------------------

这个问题可以用概率论来解决的,不过想了一段时间无果后放弃了。

方案有很多,UU的3分样本和示例中的2分样本感觉没有很大区别。甚至还可以递归2分,每次取一半作为样本啊取1/4做研究对象之类的。

某人的作弊是将第n轮的结果应用到第n + 1轮~~~咋就那么可爱捏~ 逼得我改出个这么长的程序~

该问题原本是用于描述爱情与婚姻的,简单来说就是人的一生会碰上很多个人,苏格拉底以这个故事来隐喻选择过程中的各种困惑。

最近朋友受困于该问题(高富帅STONE_NUM大运行时间更长),而我则受困于另一个同属爱情婚姻的问题。刚好看到UU的微博于是苦闷的想到爱情与婚姻是个复杂的问题,哲学家苏格拉底将其模块化划分后得出的其中一个子模块就经已如此复杂.. 于是写个程序给大家YY一下自己的选择。

不过我的观点就如同UU所说,子问题的最优解并不代表它就能给出系统最优解。另感情不像石头苹果麦穗一般捡了就完,而是需要培养灌溉况且每段感情的可发展区间还不一样,这个事情确实难,编程简单多了思密达。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: