diff --git a/src/Numerics/Sorting.cs b/src/Numerics/Sorting.cs index 13475410..b57b7063 100644 --- a/src/Numerics/Sorting.cs +++ b/src/Numerics/Sorting.cs @@ -38,7 +38,7 @@ namespace MathNet.Numerics public static class Sorting { /// - /// Sort a list of keys, in place using the quick sort algorithm using the quick sort algorithm. + /// Sort a list of keys, in place using the quick sort algorithm using the introsort algorithm. /// /// The type of elements in the key list. /// List to sort. @@ -46,42 +46,12 @@ namespace MathNet.Numerics public static void Sort(IList keys, IComparer comparer = null) { int count = keys.Count; - if (count <= 1) - { - return; - } if (null == comparer) { comparer = Comparer.Default; } - if (count == 2) - { - if (comparer.Compare(keys[0], keys[1]) > 0) - { - Swap(keys, 0, 1); - } - return; - } - - // insertion sort - if (count <= 10) - { - for (int i = 1; i < count; i++) - { - var key = keys[i]; - int j = i - 1; - while (j >= 0 && comparer.Compare(keys[j], key) > 0) - { - keys[j + 1] = keys[j]; - j--; - } - keys[j + 1] = key; - } - return; - } - // array case if (keys is T[] keysArray) { @@ -97,7 +67,7 @@ namespace MathNet.Numerics } // local sort implementation - QuickSort(keys, comparer, 0, count - 1); + IntroSort(keys, null, comparer, 0, count - 1); } /// @@ -111,46 +81,12 @@ namespace MathNet.Numerics public static void Sort(IList keys, IList items, IComparer comparer = null) { int count = keys.Count; - if (count <= 1) - { - return; - } if (null == comparer) { comparer = Comparer.Default; } - if (count == 2) - { - if (comparer.Compare(keys[0], keys[1]) > 0) - { - Swap(keys, 0, 1); - Swap(items, 0, 1); - } - return; - } - - // insertion sort - if (count <= 10) - { - for (int i = 1; i < count; i++) - { - var key = keys[i]; - var item = items[i]; - int j = i - 1; - while (j >= 0 && comparer.Compare(keys[j], key) > 0) - { - keys[j + 1] = keys[j]; - items[j + 1] = items[j]; - j--; - } - keys[j + 1] = key; - items[j + 1] = item; - } - return; - } - // array case if (keys is TKey[] keysArray && items is TItem[] itemsArray) { @@ -159,7 +95,7 @@ namespace MathNet.Numerics } // local sort implementation - QuickSort(keys, items, comparer, 0, count - 1); + IntroSort(keys, items, comparer, 0, count - 1); } /// @@ -470,6 +406,132 @@ namespace MathNet.Numerics QuickSortAll(primary, secondary, primaryComparer, secondaryComparer, 0, primary.Count - 1); } + /// + /// Recursively reorders the given list to satisfy the max heap property. + /// + /// The type of elements in the key list. + /// The type of elements in the item list. + /// The list which is turned into a heap. + /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. + /// The method with which to compare two elements of the heap. + /// The index of the heap to heapify. + /// The left boundary of the heapify. + /// The right boundary of the heapify. + static void MaxHeapify(IList keys, IList satelliteData, IComparer comparer, int i, int left, int right) + { + int leftChild = 2 * i + 1; + int rightChild = 2 * i + 2; + + int largest = i; + + if (leftChild >= left && leftChild <= right && comparer.Compare(keys[leftChild], keys[largest]) > 0) + { + largest = leftChild; + } + + if (rightChild >= left && rightChild <= right && comparer.Compare(keys[rightChild], keys[largest]) > 0) + { + largest = rightChild; + } + + if (largest != i) + { + Swap(keys, satelliteData, largest, i); + MaxHeapify(keys, satelliteData, comparer, largest, left, right); + } + } + + /// + /// + /// + /// The type of elements in the key list. + /// The type of elements in the item list. + /// The list which is turned into a heap. + /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. + /// The method with which to compare two elements of the heap. + /// The left boundary of the heap. + /// The right boundary of the heap. + + static void BuildMaxHeap(IList keys, IList satelliteData, IComparer comparer, int left, int right) + { + for (int i = (right - left) / 2; i >= 0; i--) + { + MaxHeapify(keys, satelliteData, comparer, left + i, left, right); + } + } + + /// + /// Recursive implementation for an in place introspective sort on a list. + /// + /// The type of elements in the key list. + /// The type of elements in the item list. + /// The list which is sorted using intro sort. + /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. + /// The method with which to compare two elements of the intro sort. + /// The left boundary of the intro sort. + /// The right boundary of the intro sort. + /// Tracks the number of recursions entered. + static void IntroSort(IList keys, IList satelliteData, IComparer comparer, int left, int right, int recursions = 0) + { + if (left >= right) + { + return; + } + + Random.CryptoRandomSource rand = new MathNet.Numerics.Random.CryptoRandomSource(); + double ln2 = Math.Log(2); + double max_recursion_depth = 2 * Math.Log(keys.Count) / ln2; // This is the cap on recursion depth used by the GNU STL + + if (right - left < 16) // Insertion Sort is faster on very small sequences + { + for (int i = left + 1; i <= right; i++) + { + int j = i; + while (j > 0 && comparer.Compare(keys[j - 1], keys[j]) > 0) + { + Swap(keys, satelliteData, j, j - 1); + j--; + } + } + } + else if (recursions > max_recursion_depth) // Heapsort is guaranteed O(n log n) + { + BuildMaxHeap(keys, satelliteData, comparer, left, right); + + int heapBoundary = right; + + for (int i = right - left; i > 0; i--) + { + Swap(keys, satelliteData, left, left + i); + MaxHeapify(keys, satelliteData, comparer, left, left, --heapBoundary); + } + } + else // Quicksort + { + int pivot_index = (rand.Next() % (right - left)) + left; // Don't need to worry about negatives because rand.Next() returns a non-negative number + Swap(keys, satelliteData, pivot_index, right); + + TKey pivot = keys[right]; + + int i = left - 1; + for (int j = left; j < right; j++) + { + if (comparer.Compare(keys[j], pivot) <= 0) + { + i++; + Swap(keys, satelliteData, i, j); + } + } + + i++; + Swap(keys, satelliteData, i, right); + + recursions++; + IntroSort(keys, satelliteData, comparer, left, i - 1, recursions); + IntroSort(keys, satelliteData, comparer, i + 1, right, recursions); + } + + } /// /// Recursive implementation for an in place quick sort on a list. @@ -862,5 +924,33 @@ namespace MathNet.Numerics keys[a] = keys[b]; keys[b] = local; } + + /// + /// Performs an in place swap of two elements in a list. + /// + /// The type of elements in the key list. + /// The type of elements in the item list. + /// The list in which the elements are stored. + /// List of sattelliteData to "drag along" with the keys. Set to null to indicate no such data. + /// The index of the first element of the swap. + /// The index of the second element of the swap. + static void Swap(IList keys, IList satelliteData, int a, int b) + { + if (a == b) + { + return; + } + + TKey local = keys[a]; + keys[a] = keys[b]; + keys[b] = local; + + if (satelliteData != null) + { + TItem tmp = satelliteData[a]; + satelliteData[a] = satelliteData[b]; + satelliteData[b] = tmp; + } + } } }