Simplified and optimized the QuickSort() method for IList<T>; added unit test that verifies the sorting algorithm with a large number of elements both for insertion sort and quicksort
git-svn-id: file:///srv/devel/repo-conversion/nusu@341 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
41691ddf94
commit
0e49d46eab
|
@ -32,6 +32,27 @@ namespace Nuclex.Support.Collections {
|
|||
[TestFixture]
|
||||
internal class IListExtensionsTest {
|
||||
|
||||
/// <summary>Tests whether the insertion sort algorithm works on big lists</summary>
|
||||
[Test]
|
||||
public void InsertionSortCanSortBigList() {
|
||||
const int ListSize = 16384;
|
||||
|
||||
var testList = new List<int>(capacity: ListSize);
|
||||
{
|
||||
var random = new Random();
|
||||
for(int index = 0; index < ListSize; ++index) {
|
||||
testList.Add(random.Next());
|
||||
}
|
||||
}
|
||||
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
testListAsIList.InsertionSort();
|
||||
|
||||
for(int index = 1; index < ListSize; ++index) {
|
||||
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the insertion sort algorithm can be applied to 'Text' property works as expected</summary>
|
||||
[Test]
|
||||
public void InsertionSortCanSortWholeList() {
|
||||
|
@ -60,6 +81,27 @@ namespace Nuclex.Support.Collections {
|
|||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the quicksort algorithm works on big lists</summary>
|
||||
[Test]
|
||||
public void QuickSortCanSortBigList() {
|
||||
const int ListSize = 16384;
|
||||
|
||||
var testList = new List<int>(capacity: ListSize);
|
||||
{
|
||||
var random = new Random();
|
||||
for(int index = 0; index < ListSize; ++index) {
|
||||
testList.Add(random.Next());
|
||||
}
|
||||
}
|
||||
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
testListAsIList.QuickSort();
|
||||
|
||||
for(int index = 1; index < ListSize; ++index) {
|
||||
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the insertion sort algorithm can be applied to 'Text' property works as expected</summary>
|
||||
[Test]
|
||||
public void QuickSortCanSortWholeList() {
|
||||
|
|
|
@ -118,21 +118,38 @@ namespace Nuclex.Support.Collections {
|
|||
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
|
||||
) {
|
||||
var remainingPartitions = new Stack<Partition>();
|
||||
remainingPartitions.Push(new Partition(startIndex, startIndex + count - 1));
|
||||
|
||||
while(remainingPartitions.Count > 0) {
|
||||
int lastIndex = startIndex + count - 1;
|
||||
for(; ; ) {
|
||||
int pivotIndex = quicksortPartition(list, startIndex, lastIndex, comparer);
|
||||
|
||||
// This block just queues the next partitions left of the pivot point and right
|
||||
// of the pivot point (if they contain at least 2 elements). It's fattened up
|
||||
// a bit by trying to forego the stack and adjusting the startIndex/lastIndex
|
||||
// directly where it's clear the next loop can process these partitions.
|
||||
if(pivotIndex - 1 > startIndex) { // Are the elements to sort right of the pivot?
|
||||
if(pivotIndex + 1 < lastIndex) { // Are the elements left of the pivot as well?
|
||||
remainingPartitions.Push(new Partition(startIndex, pivotIndex - 1));
|
||||
startIndex = pivotIndex + 1;
|
||||
} else { // Elements to sort are only right of the pivot
|
||||
lastIndex = pivotIndex - 1;
|
||||
}
|
||||
} else if(pivotIndex + 1 < lastIndex) { // Are elements to sort only left of the pivot?
|
||||
startIndex = pivotIndex + 1;
|
||||
} else { // Partition was fully sorted
|
||||
|
||||
// Did we process all queued partitions? If so, the list is sorted
|
||||
if(remainingPartitions.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pull the next partition that needs to be sorted from the stack
|
||||
Partition current = remainingPartitions.Pop();
|
||||
int leftEnd = current.LeftmostIndex;
|
||||
int rightEnd = current.RightmostIndex;
|
||||
startIndex = current.LeftmostIndex;
|
||||
lastIndex = current.RightmostIndex;
|
||||
|
||||
int pivotIndex = quicksortPartition(list, leftEnd, rightEnd, comparer);
|
||||
if(pivotIndex - 1 > leftEnd) {
|
||||
remainingPartitions.Push(new Partition(leftEnd, pivotIndex - 1));
|
||||
}
|
||||
if(pivotIndex + 1 < rightEnd) {
|
||||
remainingPartitions.Push(new Partition(pivotIndex + 1, rightEnd));
|
||||
}
|
||||
}
|
||||
} // if sortable sub-partitions exist left/right/nowhere
|
||||
} // for ever (termination inside loop)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -156,32 +173,45 @@ namespace Nuclex.Support.Collections {
|
|||
QuickSort(list, 0, list.Count, Comparer<TElement>.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves an element downward over all elements that precede it in the sort order
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements stored in the sorted list</typeparam>
|
||||
/// <param name="list">List that is being sorted</param>
|
||||
/// <param name="firstIndex">Index of the first element in the partition</param>
|
||||
/// <param name="lastIndex">Index of hte last element in the partition</param>
|
||||
/// <param name="comparer">
|
||||
/// Comparison function that decides the ordering of elements
|
||||
/// </param>
|
||||
/// <returns>The index of the next pivot element</returns>
|
||||
private static int quicksortPartition<TElement>(
|
||||
IList<TElement> list, int firstIndex, int lastIndex, IComparer<TElement> comparer
|
||||
) {
|
||||
TElement pivot = list[lastIndex];
|
||||
|
||||
// Set the high index element to its proper sorted position
|
||||
int nextIndex = firstIndex;
|
||||
// Step through all elements in the partition and accumulate those that are smaller
|
||||
// than the last element on the left (by swapping). At the end 'firstIndex' will be
|
||||
// the new pivot point, left of which are all elements smaller than the element at
|
||||
// 'lastIndex' and right of it will be all elements which are larger.
|
||||
for(int index = firstIndex; index < lastIndex; ++index) {
|
||||
if(comparer.Compare(list[index], pivot) < 0) {
|
||||
TElement temp = list[nextIndex];
|
||||
list[nextIndex] = list[index];
|
||||
if(comparer.Compare(list[index], list[lastIndex]) < 0) {
|
||||
TElement temp = list[firstIndex];
|
||||
list[firstIndex] = list[index];
|
||||
list[index] = temp;
|
||||
|
||||
++nextIndex;
|
||||
++firstIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the high index value to its sorted position
|
||||
// The element at 'lastIndex' as a sort value that's in the middle of the two sides,
|
||||
// so we'll have to swap it, too, putting it in the middle and making it the new pivot.
|
||||
{
|
||||
TElement temp = list[nextIndex];
|
||||
list[nextIndex] = list[lastIndex];
|
||||
TElement temp = list[firstIndex];
|
||||
list[firstIndex] = list[lastIndex];
|
||||
list[lastIndex] = temp;
|
||||
}
|
||||
|
||||
// Returns the next sorting element location
|
||||
return nextIndex;
|
||||
// Return the index of the new pivot position
|
||||
return firstIndex;
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user