tags:

views:

304

answers:

5

The catch: only comparisons between elements of the list is allowed. For example, suppose we have 1,000,000 chess players, and we are assigned the task of finding the best chess player in the group. We can play one chess player against any other chess player. Now, we want to minimize the maximum number of games any player plays.

If player A beats player B, and B beats C, we can assume that A is better than C. What is the smallest n such that no player plays more than n games?

@Carl: This is not homework; it's actually a subproblem of a larger problem from SPOJ.

+10  A: 

I would wager a guess that the answer is the binary log of the number of people.

You set up a binary tree as a tournament ladder. This means the most games anyone plays is the height of the tree. The height of the binary tree would be log n

Jamie Wong
Yes, it is a basic divide-and-conquer just like merge sort.
Justin Peel
I do think that this is the most conceptually concise and fastest solution, given the constraints of the asker. I believe that `log n` can be proven to be the optimum.
Justin L.
While the worst case number of comparisons for a given element is O(log n), the total number of comparisons is O(n), for an average number of comparisons of of O(1) per element.If the cost is comparing elements you might as well just walk the list in order. If there is some side effect of comparing a given element (like a player gets to play a game) then simulating a tournament ladder works, but keep in mind that it will require more space, since you have to copy the list.
Alex M.
This is indeed the solution with the smallest maximum number of comparisons per element. You can prove this by demonstrating that, working bottom-up, it recursively halves the number of elements, which is the best one can do with a binary comparison.
Nick Johnson
@Nick, @Jamie: This post proves nothing. It just tells us that n <= log M by demonstrating an algorithm which achives the max to be log M, M being the number of people. The crucial (and more difficult to prove) part that n >= log M is completely missing. (n is the notation used by OP, so I am using that).
Moron
+1. For the nice answer badge :) and at least attempting to partly answer the question as stated.
Moron
+1  A: 

As far as I know there is no algorithm to solve your problem without any additional outside information to rank the players (such as seeding). If you could seed the players appropriately you can find the best player in less rounds than the worst case suggested by J. Wong.

Example of the results of 2 rounds of 10 players: A is the best, ceil(log 10) = 4

A > B; C > D; E > F; G > H; I > J

A > C; B > E; F > G; D > I

Skcus Isc
+1, a good example that if you know something extra about a problem you can often make it easier. This solution doesn't apply directly to the OP's question (since you lose generality), but it's a point worth making I think.
Carl Norum
A: 

How do I find the biggest element of a list

If the list is ordered, then the biggest element is the first (or last) element of the list.

If the list is not ordered then:

Element biggest = list.get(0);
for (Element e : list) {
    if (e.compareWith(biggest) > 0) {
        biggest = e;
    }
}

For example, suppose we have 1,000,000 chess players, and we are assigned the task of finding the best chess player in the group. Now, we want to minimize the maximum number of games any player plays.

With the new constraint of the last sentence ...

Answer #1: zero games played. Compare the chess player's rankings and the one with the best ranking is the objectively best player ... according to the ranking.

Answer #2: at most ceiling(log2(nos_players)) games played per player. A "knock out" / elimination tournament eliminates half the players in each round, so the number of rounds and hence the maximum number of games played by any one player is ceiling(log2(nos_players)).

The corresponding algorithm is trivially:

List players = ...
while (players.size() > 1) {
    List winners = new ArrayList();
    Iterator it = players.iterator();
    while (it.hasNext()) {
        Player p1 = it.next();
        if (it.hasNext()) {
            Player p2 = it.next();
            int result = p1.compareTo(p2);
            if (result < 0) {
                winners.add(p2);
            } else if (result > 0) {
                winners.add(p1);
            } else {
                throw new Exception("draws are impossible in chess");
            }
        } else {
            winners.add(p1); // bye
        }
    }
    players = winners;
}

(Aside: if you also have a predetermined ranking for the players and the number of players N is at least 2 less than ceiling(log2(N)), you can arrange that the best 2 players get a bye in one round. If the best 2 players meet in the final, then everyone will have played less than ceiling(log2(N)) games ... which is an improvement on the solution where the byes are allocated randomly.)

In reality, answer #2 does not work for the game of chess because it does not take account of the fact that a significant percentage of real chess games are draws; i.e. neither player wins. Indeed, the fact that player A beat player B in one game does not mean A is a better player than B. To determine who is the better of any two players they need to play a number of games and tally the wins and losses. In short, the notion that there is a "better than" relation for chess players is TOTALLY unrealistic.


Not withstanding the points above, knock-out is NOT a practical way to organize a chess tournament. Everyone will be camped out on the tournament organizer's desk complaining about having to play games against players much better (or worse) than themselves.

The way a real chess (or similar) tournament works is that you decide on the number of rounds you want to play first. Then in a "round-robin" tournament, you select the top N players by ranking. and arrange that each player plays each other player. The player with the best win / draw score is the winner, and in the event of a tie you use (say) "sum of opponents scores" as a tie breaker. There are other styles of tournament as well that cater for more players / less rounds.

Stephen C
I see the two questions as inherently linked; the asker is simply asking for an algorithm to find the largest element in the list with an access cost for the element's value. Your first solution will find the max, but it has a worst-case scenario of *n* accesses to the largest element, if it is at the beginning of the list. Clearly, there is a better algorithm with a much lower worst-case scenario.The tournament method, I believe, is optimal, because its worst-case is *log n* instead of *n*. Furthermore, the constraint of the asker is that if A>B and B>C, then A>C. It works.
Justin L.
Justin L.
@Justin. L - I know that. But it is a more realistic solution to running a chess tournament than is the knock-out meta-heuristic.
Stephen C
point taken =).
Justin L.
@Justin. L - Also, if the OP wants to give an (IMO) bogus example, and everyone else chimes in to say that this is a "good" question, then the bogus example is what I'm going to use in my answer ... if only to illustrate the example's bogosity.
Stephen C
@Justin It's not an access cost for the element's value - we don't even know the value. It's a cost to compare two elements.
Nick Johnson
It's a computational cost to compare the two elements, but the asker's constraints is that there is a cost to the element itself to compare it with another. He is trying to find a solution to find the largest while minimizing the maximum cost to any element.
Justin L.
@Wram: How is this the accepted answer? No offense to Stephen, but this does not really answer the question. What if there was some algorithm which achieved a max of 15 games per person? There is no proof here that such a thing is not possible.
Moron
... Of course, it is your question, so you are free to do as you please :-)
Moron
@Stephen +1 for "Bogosity"
Jamie Wong
@Jamie: It is a very good question. So what if the questioner used chess? Who cares if in real life there might not be transitivity? The question clearly stated that you can assume A>B and B>C implies A>C. It is ridiculous to dispute the assumptions given, and is in fact, in a way, insulting to the OP. Calling this question Bogus is pretty silly, IMO. Anyway... sorry for the rant.
Moron
@Moron I wasn't commenting on how bogus it was, I just like the word "Bogosity"
Jamie Wong
-1: Two reasons: 1) The relevant part of this answer is a subset of Jamie's answer (which appeared earlier) 2) This has a lot of irrelevant garbage thrown in. Chess was just an _example_ and an attempt by OP to make the question clearer. Talking about how chess tournaments should be conducted or some sorting algorithms which do not even attempt to minimize the max times a player plays is just noise.
Moron
A: 

Instead of building an Abstract Data Structure such as a binary tree and resolving a tournament, you could re-interpret your goal in a different light:

Eliminate all the elements on the list that are not the largest

You will find that doing this may be much more algorithmically expedient than building a tree and seeding a "tournament".

I can demonstrate that eliminating all elements on a list that are not the largest can be done with a worst-case scenario of log n calls/comparisons per element.

Work on a copy of your original list if possible.

  1. Pair off consecutive elements and remove from the list the lower-valued of the two. Ignore the unpaired element, if there is one.

    This can be done by iterating from 0 <= i < int(n/2) and comparing indices 2i and 2i+1.

    i.e., for n=7, int(n/2) = 3, i = 0,1,2; compare indices 0 and 1, 2 and 3, 4 and 5.

  2. There should be a total of int(n/2) indices eliminated. Subtract that count from n. Then, repeat 1 until there is only one index remaining. This will be your largest.

Here is an implementation in Ruby:

def find_largest(list)

  n = list.size
  working_list = list.clone()

  while n > 1

    temp_list = Array.new()

    for i in (0...n/2)          # remember to cast n/2 to integer if not automatic
      if working_list[2*i] > working_list[2*i+1]
        new_list.push(working_list[2*i])
      else
        new_list.push(working_list[2*i+1])
      end
    end

    working_list = temp_list

    n -= n/2                    # remember to cast n/2 to integer if not automatic

  end

  return working_list[0]

end
Justin L.
A: 

None of the other answers answers this correctly (i.e. if I understood the question correctly).

All they have done is to show an algorithm to achieve the logarithmic bound. This only proves that it is only an upper bound and not the minimum possible.

In fact, we can show that the logn bound is optimal, by showing that in the worst case, any algorithm which uses comparisons has to compare the maximum element at least ceil(logn) times, i.e. no matter how you conduct the tournament, there would be cases (depending on your algorithm) which will have the winner play at least that many games.

To show this assume each player started out with 1$. Now each time two people play, the loser hands over all his money to the winner.

In the end, the winner gets all the money! If there were two people with non-zero amounts of money, that means they haven't lost a game yet and haven't played against each other and so the winner is not decided yet.

Now consider the case, where it so happens that the player with the lower amount of money always loses. (If they had equal non-zero amounts, you can take the player with smaller player number loses and if they both are zero (both lost some game ealier), you can decide what the consistent outcome is, based on the past games.)

In this case, the winner will at most double his money in each game, and thus would need to play at least ceil(logn) games.

The fact that this bound is achievable is clear from the other answers.

Incidentally, this lower bound proof can probably be modified to show that in the worst case you require at least n + ceil(logn) - 1 total comparisons to find the largest and second largest elements.

Moron