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]
|
[TestFixture]
|
||||||
internal class IListExtensionsTest {
|
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>
|
/// <summary>Tests whether the insertion sort algorithm can be applied to 'Text' property works as expected</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void InsertionSortCanSortWholeList() {
|
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>
|
/// <summary>Tests whether the insertion sort algorithm can be applied to 'Text' property works as expected</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void QuickSortCanSortWholeList() {
|
public void QuickSortCanSortWholeList() {
|
||||||
|
|
|
@ -118,21 +118,38 @@ namespace Nuclex.Support.Collections {
|
||||||
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
|
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
|
||||||
) {
|
) {
|
||||||
var remainingPartitions = new Stack<Partition>();
|
var remainingPartitions = new Stack<Partition>();
|
||||||
remainingPartitions.Push(new Partition(startIndex, startIndex + count - 1));
|
|
||||||
|
|
||||||
while(remainingPartitions.Count > 0) {
|
int lastIndex = startIndex + count - 1;
|
||||||
Partition current = remainingPartitions.Pop();
|
for(; ; ) {
|
||||||
int leftEnd = current.LeftmostIndex;
|
int pivotIndex = quicksortPartition(list, startIndex, lastIndex, comparer);
|
||||||
int rightEnd = current.RightmostIndex;
|
|
||||||
|
|
||||||
int pivotIndex = quicksortPartition(list, leftEnd, rightEnd, comparer);
|
// This block just queues the next partitions left of the pivot point and right
|
||||||
if(pivotIndex - 1 > leftEnd) {
|
// of the pivot point (if they contain at least 2 elements). It's fattened up
|
||||||
remainingPartitions.Push(new Partition(leftEnd, pivotIndex - 1));
|
// 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 < rightEnd) {
|
if(pivotIndex - 1 > startIndex) { // Are the elements to sort right of the pivot?
|
||||||
remainingPartitions.Push(new Partition(pivotIndex + 1, rightEnd));
|
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();
|
||||||
|
startIndex = current.LeftmostIndex;
|
||||||
|
lastIndex = current.RightmostIndex;
|
||||||
|
|
||||||
|
} // if sortable sub-partitions exist left/right/nowhere
|
||||||
|
} // for ever (termination inside loop)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -156,32 +173,45 @@ namespace Nuclex.Support.Collections {
|
||||||
QuickSort(list, 0, list.Count, Comparer<TElement>.Default);
|
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>(
|
private static int quicksortPartition<TElement>(
|
||||||
IList<TElement> list, int firstIndex, int lastIndex, IComparer<TElement> comparer
|
IList<TElement> list, int firstIndex, int lastIndex, IComparer<TElement> comparer
|
||||||
) {
|
) {
|
||||||
TElement pivot = list[lastIndex];
|
|
||||||
|
// Step through all elements in the partition and accumulate those that are smaller
|
||||||
// Set the high index element to its proper sorted position
|
// than the last element on the left (by swapping). At the end 'firstIndex' will be
|
||||||
int nextIndex = firstIndex;
|
// 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) {
|
for(int index = firstIndex; index < lastIndex; ++index) {
|
||||||
if(comparer.Compare(list[index], pivot) < 0) {
|
if(comparer.Compare(list[index], list[lastIndex]) < 0) {
|
||||||
TElement temp = list[nextIndex];
|
TElement temp = list[firstIndex];
|
||||||
list[nextIndex] = list[index];
|
list[firstIndex] = list[index];
|
||||||
list[index] = temp;
|
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];
|
TElement temp = list[firstIndex];
|
||||||
list[nextIndex] = list[lastIndex];
|
list[firstIndex] = list[lastIndex];
|
||||||
list[lastIndex] = temp;
|
list[lastIndex] = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the next sorting element location
|
// Return the index of the new pivot position
|
||||||
return nextIndex;
|
return firstIndex;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user