Now it comes to the Scale class. The main job of it is to weight both sides. So the class is pretty simple, sum the weights of balls and compare.
Now based on these classes, the translation of the algorithm is straight forward:
Though this is a direct translation of the algorithm described in the book. There are 3 trivial cases served as the basis for the recursion. Taking into account that they are the base cases, they are not as trivial as one thinks.
Now based on these classes, the translation of the algorithm is straight forward:
java 代码
- package solve;
- import scale.Ball;
- import scale.Scale;
- import java.util.Set;
- import java.util.Iterator;
- import java.util.HashSet;
- public class ProblemSolver
- {
- private Set orignalballSet;
- private Scale scale = new Scale();
- public ProblemSolver(Set orignalballSet)
- {
- this.orignalballSet = orignalballSet;
- }
- public void findWeight()
- {
- findBadBall(this.orignalballSet);
- }
- private void findBadBall(Set ballSet)
- {
- int ballSetSize = ballSet.size();
- // recursion defaults
- if (ballSetSize == 1)
- {
- OneBallCaseSolver solver = new OneBallCaseSolver();
- solver.setScale(scale);
- solver.setOriginalBallSet(orignalballSet);
- solver.findWeight(ballSet);
- return;
- }
- else if (ballSetSize == 2)
- {
- TwoBallCaseSolver solver = new TwoBallCaseSolver();
- solver.setScale(scale);
- solver.setOriginalBallSet(orignalballSet);
- solver.findWeight(ballSet);
- return;
- }
- else if (ballSetSize == 3)
- {
- ThreeBallCaseSolver solver = new ThreeBallCaseSolver();
- solver.setScale(scale);
- solver.findWeight(ballSet);
- return;
- }
- // recursion starts here.
- // check whether all elements are marked. If so, then by the lemma,
- // It takes at most n times of weighing to point out the defect one
- // with the weigh, if N < 3^n, where N is the number of balls.
- // In order to check all balls, it's sufficient to check just one
- // because we either mark all or none.
- // With the marks, we definitely weigh less times.
- Ball firstBall = (Ball)ballSet.iterator().next();
- if (!firstBall.getStatus().equals(Ball.UNKNOWN))
- {
- findWithMarks(ballSet);
- return;
- }
- // if we get here, this means no mark is found, so we blindly populate
- // An extra good ball would reduce the size of the group, said in this lemma:
- // If an extra genuine good is available, when the defect ball can be found with
- // the weigh in N ball by n trials, where N <= (3^n - 1)/2.
- // In fact, using these two lemmas we can prove, by deduction, the entire problem:
- // Given N balls, if N <= (3^n - 3)/2, then we can findBadBall it with weigh in n trials.
- // This call is confusing: even the above if block is not marked, the whole set could have a good ball
- // one case is when we have a even scale, and this is the third group.
- Ball goodBall = Utils.findGoodBall(this.orignalballSet);
- int subsize = ballSetSize / 3; // divided into 3 groups
- if (goodBall != null)
- {
- subsize++; // for the good ball we are adding in.
- }
- else if (ballSetSize % 3 == 2)
- {
- subsize++; // use the groups with equal sizes.
- }
- HashSet group1 = new HashSet(subsize);
- HashSet group2 = new HashSet(subsize);
- HashSet group3 = new HashSet(ballSetSize - 2*subsize);
- if (goodBall != null) group1.add(goodBall);
- Iterator it = ballSet.iterator();
- // fill in group1 first.
- while (it.hasNext())
- {
- if (group1.size() < subsize) group1.add(it.next());
- if (group1.size() >= subsize) break;
- }
- // fill in group2 first.
- while (it.hasNext())
- {
- if (group2.size() < subsize) group2.add(it.next());
- if (group2.size() >= subsize) break;
- }
- // put the rest into group3
- while (it.hasNext())
- {
- group3.add(it.next());
- }
- int scaleResult = scale.weigh(group1, group2);
- if (scaleResult == Scale.EVEN)
- {
- Utils.markStatus(group1, Ball.NORMAL);
- Utils.markStatus(group2, Ball.NORMAL);
- findBadBall(group3);
- }
- else // the counterfeit is not in group3, it's in group1 or group2
- {
- Utils.markStatus(group3, Ball.NORMAL);
- Utils.markStatus(group1, Utils.convertToBallStatus(scaleResult));
- Ball goodput = Utils.findGoodBall(group1);
- if (goodput != null)
- {
- group1.remove(goodput);
- }
- Utils.markStatus(group2, Utils.convertToBallStatus(-scaleResult));
- goodput = Utils.findGoodBall(group2);
- if (goodput != null)
- {
- group2.remove(goodput);
- }
- Set newSet = new HashSet(group1.size() + group2.size());
- Iterator itt = group1.iterator();
- while (itt.hasNext()) newSet.add(itt.next());
- itt = group2.iterator();
- while (itt.hasNext()) newSet.add(itt.next());
- findWithMarks(newSet);
- }
- }
- //=======================================================================
- // Some big ugly codes go to here
- //=======================================================================
- private void findWithMarks(Set src)
- {
- int size = src.size();
- int subsize = size / 3; // divided into 3 groups
- if (size % 3 == 2) subsize++; // use the groups with equal sizes.
- HashSet group1 = new HashSet(subsize);
- HashSet group2 = new HashSet(subsize);
- HashSet group3 = new HashSet(size - 2*subsize); // this is the off scale group
- // now we need to populate in such a way so that group1 and group2
- // have equal number of same weigh, both for 1 and -1. The reason
- // we need this is because in some cases, we need to switch them in
- // forming nextGroup.
- evenlyDistribute(src, group1, group2, group3, subsize);
- if (group1.size() > 0)
- {
- int result = scale.weigh(group1, group2);
- if (result == Scale.EVEN)
- {
- // mark group1 and group2
- Utils.markStatus(group1, Ball.NORMAL);
- Utils.markStatus(group2, Ball.NORMAL);
- findBadBall(group3);
- }
- // get lighter balls from lighter groups, get heavier balls from heavier groups
- else if (result == Scale.HEAVIER)
- {
- Utils.markStatus(group3, Ball.NORMAL);
- HashSet nextGroup = new HashSet();
- getHeavier(group1, nextGroup);
- getLighter(group2, nextGroup);
- findBadBall(nextGroup);
- }
- else // -1
- {
- Utils.markStatus(group3, Ball.NORMAL);
- HashSet nextGroup = new HashSet();
- getHeavier(group2, nextGroup);
- getLighter(group1, nextGroup);
- findBadBall(nextGroup);
- }
- }
- else
- {
- findBadBall(group3);
- }
- }
- private void getHeavier(Set src, Set des)
- {
- for (Iterator it=src.iterator(); it.hasNext();)
- {
- Ball ball = (Ball)it.next();
- if (ball.getStatus().equals(Ball.HEAVIER)) des.add(ball);
- else ball.setStatus(Ball.NORMAL);
- }
- }
- private void getLighter(Set src, Set des)
- {
- for (Iterator it=src.iterator(); it.hasNext();)
- {
- Ball ball = (Ball)it.next();
- if (ball.getStatus().equals(Ball.LIGHTER)) des.add(ball);
- else ball.setStatus(Ball.NORMAL);
- }
- }
- // This will distribute src to des1, des2, and des3. Assuming src is marked
- // with proper weigh 1, -1. des1 and des2 should have the same number of
- // balls with weigh 1, and should have the same number of balls of weigh
- // -1. The rest of src goes to des3, which should be the off scale group.
- // size is the size of des1, des2.
- private void evenlyDistribute(Set src, Set des1, Set des2, Set des3, int size)
- {
- int heavierSum1 = 0;
- int lighterSum1 = 0;
- int heavierSum2 = 0;
- int lighterSum2 = 0;
- Ball heavierPair = null;
- int heavierPairMarker = 0; // 0 for no pair, 1 for pair
- Ball lighterPair = null;
- int lighterPairMarker = 0; // 0 for no pair, 1 for pair
- Iterator itm1 = src.iterator();
- while (itm1.hasNext())
- {
- Ball ball = (Ball)itm1.next();
- if (des1.size() >= size)
- {
- des3.add(ball);
- }
- else if (ball.getStatus().equals(Ball.HEAVIER))
- {
- if (heavierSum1 > heavierSum2)
- {
- des2.add(ball);
- heavierSum2++;
- }
- else if (heavierSum1 < heavierSum2)
- {
- des1.add(ball);
- heavierSum1++;
- }
- else // heavierSum1==heavierSum2
- {
- if (heavierPairMarker == 0) // this is the first one
- {
- // save this, set marker, wait for the next one.
- heavierPair = ball;
- heavierPairMarker = 1;
- }
- else // ==1, already a ball there, now set one for each group
- {
- des1.add(heavierPair);
- des2.add(ball);
- // reset marker to 0
- heavierPair = null;
- heavierPairMarker = 0;
- }
- }
- }
- else if (ball.getStatus().equals(Ball.LIGHTER))
- {
- if (lighterSum1 > lighterSum2)
- {
- des2.add(ball);
- lighterSum2++;
- }
- else if (lighterSum1 < lighterSum2)
- {
- des1.add(ball);
- lighterSum1++;
- }
- else // lighterSum1==lighterSum2
- {
- if (lighterPairMarker == 0) // this is the first one
- {
- // save this, set marker, wait for the next one.
- lighterPair = ball;
- lighterPairMarker = 1;
- }
- else // ==1, already a ball there, now set one for each group
- {
- des1.add(lighterPair);
- des2.add(ball);
- // reset marker to 0
- lighterPair = null;
- lighterPairMarker = 0;
- }
- }
- }
- else
- {
- System.out.println("This ball is not marked: " + ball + " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
- }
- }
- // At the very last, if there are any left, add them to des3 since des1 and des2 are full, by now.
- if (lighterPair != null) des3.add(lighterPair);
- if (heavierPair != null) des3.add(heavierPair);
- }
- public Scale getScale() { return this.scale; }
- }
Though this is a direct translation of the algorithm described in the book. There are 3 trivial cases served as the basis for the recursion. Taking into account that they are the base cases, they are not as trivial as one thinks.