Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Algorithms (2006)

.pdf
Скачиваний:
252
Добавлен:
17.08.2013
Размер:
9.67 Mб
Скачать

Chapter 6

Figure 6-5: The entire group is sorted.

The ListSorter Interface

Like many interfaces, the ListSorter interface is extremely simple, consisting of a single operation to sort a list.

The Sort operation accepts a list as its input and produces as its result a sorted version of the list. Depending on the implementation, the returned list might be the same as the provided list — that is, some implementations sort the list in place, whereas others create a new list.

Here is the code for the ListSorter interface:

public interface ListSorter { public List sort(List list);

}

Testing AbstractListSorter

Even though you have not yet written a single sorting algorithm, in the next Try It Out, you write a test that exercises any implementation of the ListSorter interface. This example uses an abstract test class, meaning that it can’t be run until it has been extended for a specific sorting algorithm implementation.

The actual implementation of the test for each specific algorithm will be trivial as a result.

AbstractListSorterTest performs the following tasks:

Creates an unsorted list of strings

Creates a sorted list of the same strings to act as an expected result for the test

Creates a ListSorter (via an abstract method)

Uses the ListSorter to sort the unsorted list

Compares the sorted list with the expected result list

124

Basic Sorting

Try It Out

Testing AbstractSorterTest

Begin the code by declaring the two lists and using a setUp() implementation to fill each of them with strings:

package com.wrox.algorithms.sorting;

import junit.framework.TestCase; import com.wrox.algorithms.lists.List;

import com.wrox.algorithms.lists.LinkedList; import com.wrox.algorithms.iteration.Iterator;

public abstract class AbstractListSorterTest extends TestCase { private List _unsortedList;

private List _sortedList;

protected void setUp() throws Exception { _unsortedList = new LinkedList();

_unsortedList.add(“test”); _unsortedList.add(“driven”); _unsortedList.add(“development”); _unsortedList.add(“is”); _unsortedList.add(“one”); _unsortedList.add(“small”); _unsortedList.add(“step”); _unsortedList.add(“for”); _unsortedList.add(“a”); _unsortedList.add(“programmer”); _unsortedList.add(“but”); _unsortedList.add(“it’s”); _unsortedList.add(“one”); _unsortedList.add(“giant”); _unsortedList.add(“leap”); _unsortedList.add(“for”); _unsortedList.add(“programming”);

_sortedList = new LinkedList();

_sortedList.add(“a”); _sortedList.add(“but”); _sortedList.add(“development”); _sortedList.add(“driven”); _sortedList.add(“for”); _sortedList.add(“for”); _sortedList.add(“giant”); _sortedList.add(“is”); _sortedList.add(“it’s”); _sortedList.add(“leap”); _sortedList.add(“one”); _sortedList.add(“one”); _sortedList.add(“programmer”); _sortedList.add(“programming”);

125

Chapter 6

_sortedList.add(“small”); _sortedList.add(“step”);

_sortedList.add(“test”);

}

Next, implement tearDown(), which frees the references to the two List objects:

protected void tearDown() throws Exception { _sortedList = null;

_unsortedList = null;

}

Finally, define the abstract method to create the specific sorting algorithm and the test itself:

protected abstract ListSorter createListSorter(Comparator comparator);

public void testListSorterCanSortSampleList() {

ListSorter sorter = createListSorter(NaturalComparator.INSTANCE); List result = sorter.sort(_unsortedList);

assertEquals(result.size(), _sortedList.size());

Iterator actual = result.iterator(); actual.first();

Iterator expected = _sortedList.iterator(); expected.first();

while (!expected .isDone()) { assertEquals(expected.current(), actual.current());

expected.next();

actual.next();

}

}

How It Works

The first two lines of the test method create the sorting algorithm implementation and use it to sort the unsorted list. You pass a natural comparator because your expected results have been set up in the natural sequence of the strings themselves. The bulk of the test verifies that the result of the sort matches the expected result list. You do this by creating an iterator over the lists and comparing each item in turn to ensure an item-by-item exact match. Every one of your sorting algorithms must be able to pass this test or it will be of very little use in practice!

In the following Try It Out, you make a test that is specific to your bubble sort implementation.

Try It Out

Testing BubblesortListSorter

Extend the AbstractListSorterTest and implement the abstract createListSorter() method as shown here:

126

Basic Sorting

package com.wrox.algorithms.sorting;

public class BubblesortListSorterTest extends AbstractListSorterTest { protected ListSorter createListSorter(Comparator comparator) {

return new BubblesortListSorter(comparator);

}

}

That’s all you need to do to complete the test for the BubblesortListSorter. Of course, the preceding code won’t compile yet, as we don’t have a BubblesortListSorter class; that’s what we’ll do now. In the next Try It Out, you implement your bubble sort.

How It Works

Despite the fact that you only implemented a single method with a single line of code, the key point here is that you are extending the AbstractListSorterTest class in the preceding code. The abstract class provides the test data and several test methods; all you need to do is provide the ListSorter implementation for these tests to use, and that’s what you have done here.

Try It Out

Implementing BubblesortListSorter

The implementation of the bubble sort algorithm must meet the following design criteria:

Implement the ListSorter interface

Accept a comparator to determine the ordering of objects

Pass the unit test described in the preceding section

With these guidelines in place, you begin implementation with the constructor, as shown here:

package com.wrox.algorithms.sorting;

import com.wrox.algorithms.lists.List;

public class BubblesortListSorter implements ListSorter { private final Comparator _comparator;

public BubblesortListSorter(Comparator comparator) {

assert comparator != null : “comparator cannot be null”; _comparator = comparator;

}

...

}

You now need to implement the bubble sort algorithm itself. Recall from the description of the algorithm that it is comprised of a number of passes through the data, with each pass resulting in one item being moved into its final sorted position. The first thing to determine is how many passes are needed. When all but the last item have been moved into their final sorted position, the last item has nowhere to go and must therefore also be in its final position, so you need a number of passes that is one less than the number of items. The code that follows calls this the outer loop.

127

Chapter 6

On each pass, you compare each pair of items and swap them if they are out of order (as determined by the comparator you have been given). Remember, however, that on each pass, one item is moved into final sorted position and can therefore be ignored on subsequent passes. Therefore, each pass deals with one less item than the previous pass. If N is the number of items in the list, then on the first pass, the number of comparisons you need to make is (N – 1), on the second pass it is (N – 2), and so on. This is why the inner loop in the following code has the condition left < (size – pass) to control how many comparisons are performed:

public List sort(List list) {

assert list != null

: “list cannot be null”;

 

int size = list.size();

 

for (int pass = 1; pass < size; ++pass) {

// outer loop

for (int left = 0; left < (size - pass); ++left) {

// inner loop

int right =

left + 1;

 

if (_comparator.compare(list.get(left), list.get(right)) > 0) { swap(list, left, right);

}

}

}

return list;

}

The preceding code uses the supplied comparator to determine whether the two items under scrutiny are out of order. If they are, then it calls the swap() method to correct their relative placement in the list. Here is the code for swap():

private void swap(List list, int left, int right) { Object temp = list.get(left);

list.set(left, list.get(right));

list.set(right, temp);

}

}

When you compile and run this test, it passes with flying colors. Just to make sure, you can place a deliberate mistake in the test’s expectation and run it again to see that it will indeed catch you if you slip up when implementing your next sorting algorithm.

Working with a Selection Sor t

Imagine that you have a bookshelf filled with several books of varying sizes that are arranged haphazardly. Your mother is coming to visit, and to impress her with your housekeeping prowess, you decide to arrange the books neatly on the shelf in order from the tallest to the shortest. Figure 6-6 shows the bookshelf before you begin.

You’d be unlikely to use bubble sort in this case, because all that swapping would be a waste of time. You’d be taking each book out and putting it back on the shelf many times, and that would take too long. In this example, the cost of moving the items is relatively large when measured against the cost of comparing items. A selection sort is a better choice here, and you’ll soon see why.

128

Basic Sorting

Figure 6-6: A haphazard bookshelf.

Start by scanning the shelf for the tallest book. Pull it out, as it needs to move to the far left of the shelf. Rather than move all the other books along the shelf to make room for it, just pull out the book that happens to be in the space where you want this one to go and swap them. Of course, the rest of the books will have to move a little because the books vary in thickness, but that won’t matter in this software implementation, so just ignore that little issue. (Choosing to swap two books in this way, rather than slide all the books along, makes this implementation unstable, a topic covered later in this chapter, but don’t worry about that for now.) Figure 6-7 shows how the shelf looks after your first swap.

Figure 6-7: The tallest book is now at the far left position.

129

Chapter 6

Leaving the tallest book where it is, scan the shelf for the tallest of the remaining books. Once you’ve found it, swap it with the book that happens to be just to the right of the tallest book. You now have sorted two books that you won’t have to touch again. Figure 6-8 shows your shelf now.

Figure 6-8: The second tallest book is now in the second position.

Leaving the largest books where they are, continue to scan the remaining books for the tallest among them, each time swapping it with the book that is just to the right of the already sorted books at the left end of the shelf. Each time you scan the shelf, you are selecting the next book in order and moving it into its final sorted position. That’s why this algorithm is called selection sort. Figure 6-9 shows the shelf after each book is moved.

Sometimes, while scanning the unsorted books to find the tallest among them, you will find that it is already in position and no swap is required. You can see that after each book is moved, the set of sorted books grows, and the set of unsorted books shrinks until it is empty and the whole shelf is sorted. Each book is moved directly into its final sorted position, rather than taking small steps toward its final position (as in a bubble sort), which is a good reason to use this algorithm in this case.

130

Basic Sorting

Figure 6-9: The shelf after each position is filled with the appropriate book.

131

Chapter 6

You can re-use a lot of the work you did with the bubble sort algorithm to test your selection sort. In the next Try It Out, you start by creating a test case for it and then implementing the algorithm itself, making sure it passes the test to prove you implemented it correctly.

Try It Out

Testing SelectionSortListSorter

The test for the SelectionSortListSorter is almost exactly the same as its bubble sort equivalent. You extend your abstract test case and instantiate the selection sort implementation:

package com.wrox.algorithms.sorting;

public class SelectionSortListSorterTest extends AbstractListSorterTest { protected ListSorter createListSorter(Comparator comparator) {

return new SelectionSortListSorter(comparator);

}

}

In the next Try It Out, you implement SelectionSortListSorter.

How It Works

Despite the fact that you only implemented a single method with a single line of code, the key point here is that you are extending the AbstractListSorterTest class described earlier in this chapter. The abstract class provides the test data and several test methods; all you need to do is provide the ListSorter implementation for these tests to use, which is what you have done here.

Try It Out

Implementing SelectionSortListSorter

The implementation also shares much in common with its bubble sort counterpart. It too needs to implement the ListSorter interface, accept a Comparator to determine ordering, and pass the unit test described above. Create the following class declaration and constructor:

package com.wrox.algorithms.sorting;

import com.wrox.algorithms.lists.List;

public class SelectionSortListSorter implements ListSorter { private final Comparator _comparator;

public SelectionSortListSorter(Comparator comparator) { assert comparator != null : “comparator cannot be null”; _comparator = comparator;

}

...

}

How It Works

The implementation has both an outer loop and an inner loop, like bubble sort, but there are subtle differences that might escape your attention if you don’t look at this code closely. First, the outer loop index ranges between zero and (N – 2), rather than between 1 and (N – 1) in bubble sort. Note that this is still the same number of passes (N – 1), but it reflects your focus in the selection sort on filling a given “slot”

132

Basic Sorting

with the right object on each pass. For example, on the first pass, your goal is to get the right object into position zero of the list. On the second pass, the goal is to fill position 1, and so on. Once again, you can get by with only (N – 1) passes because the last object naturally ends up in sorted position as a result of sorting every other object first.

No swapping occurs during the inner loop, as it did in bubble sort. During the inner loop, the only requirement is to remember the position of the smallest item. When the inner loop finishes, you then swap the smallest item into the slot you are trying to fill. This is slightly different from the earlier bookshelf example, in which the books were sorted from largest to smallest, but the algorithm would work just as well in that case. In fact, you simply plug in the ReverseComparator you created earlier in this chapter:

public List

sort(List list) {

 

 

assert list != null : “list cannot be null”;

 

int size = list.size();

 

 

for (int slot = 0; slot <

size - 1; ++slot) {

// outer loop

int

smallest = slot;

 

 

for

(int check = slot

+ 1; check < size; ++check) {

// inner loop

if (_comparator.compare(list.get(check), list.get(smallest)) < 0) { smallest = check;

}

}

swap(list, smallest, slot);

}

return list;

}

There is also one small difference in the implementation of swap() for a selection sort when compared to a bubble sort. You add a guard clause to ignore requests to swap a slot with itself, which can occur quite easily with a selection sort, but not a bubble sort:

private void swap(List list, int left, int right) { if (left == right) {

return;

}

Object temp = list.get(left); list.set(left, list.get(right)); list.set(right, temp);

}

}

Understanding Inser tion Sor t

Insertion sort is the algorithm very commonly used by people playing cards to sort the hand they have been dealt. Imagine you have been given five cards face down and you want to sort them according to the following rules:

Separate into suits in the following order: spades, clubs, diamonds, and hearts

Within each suit, sort in ascending order: Ace, 2, 3, . . . , 9, 10, jack, queen, king

133