18 Star 133 Fork 63

编程语言算法集 / C-Sharp

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
TimSorter.cs 19.80 KB
一键复制 编辑 原始数据 按行查看 历史
Gerson Jr 提交于 2024-01-05 20:45 . Switch to file-scoped namespaces (#433)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
using System;
using System.Collections.Generic;
namespace Algorithms.Sorters.Comparison;
/// <summary>
/// Timsort is a hybrid stable sorting algorithm, derived from merge sort and insertion sort, designed to perform well on many kinds of real-world data.
/// It was originally implemented by Tim Peters in 2002 for use in the Python programming language.
///
/// This class is based on a Java interpretation of Tim Peter's original work.
/// Java class is viewable here:
/// http://cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/raw_files/new/src/share/classes/java/util/TimSort.java
///
/// Tim Peters's list sort for Python, is described in detail here:
/// http://svn.python.org/projects/python/trunk/Objects/listsort.txt
///
/// Tim's C code may be found here: http://svn.python.org/projects/python/trunk/Objects/listobject.c
///
/// The underlying techniques are described in this paper (and may have even earlier origins):
/// "Optimistic Sorting and Information Theoretic Complexity"
/// Peter McIlroy
/// SODA (Fourth Annual ACM-SIAM Symposium on Discrete Algorithms),
/// pp 467-474, Austin, Texas, 25-27 January 1993.
/// </summary>
/// <typeparam name="T">Type of array element.</typeparam>
public class TimSorter<T> : IComparisonSorter<T>
{
private readonly int minMerge;
private readonly int initMinGallop;
private readonly int[] runBase;
private readonly int[] runLengths;
private int minGallop;
private int stackSize;
private IComparer<T> comparer = default!;
/// <summary>
/// Private class for handling gallop merges, allows for tracking array indexes and wins.
/// </summary>
/// <typeparam name="Tc">Type of array element.</typeparam>
private class TimChunk<Tc>
{
public Tc[] Array { get; set; } = default!;
public int Index { get; set; }
public int Remaining { get; set; }
public int Wins { get; set; }
}
public TimSorter(int minMerge = 32, int minGallop = 7)
{
initMinGallop = minGallop;
this.minMerge = minMerge;
runBase = new int[85];
runLengths = new int[85];
stackSize = 0;
this.minGallop = minGallop;
}
/// <summary>
/// Sorts array using specified comparer
/// worst case performance: O(n log(n)),
/// best case performance: O(n),
/// See <a href="https://en.wikipedia.org/wiki/Timsort">here</a> for more info.
/// </summary>
/// <param name="array">Array to sort.</param>
/// <param name="comparer">Compares elements.</param>
public void Sort(T[] array, IComparer<T> comparer)
{
this.comparer = comparer;
var start = 0;
var remaining = array.Length;
if (remaining < minMerge)
{
if (remaining < 2)
{
// Arrays of size 0 or 1 are always sorted.
return;
}
// Don't need to merge, just binary sort
BinarySort(array, start, remaining, start);
return;
}
var minRun = MinRunLength(remaining, minMerge);
do
{
// Identify next run
var runLen = CountRunAndMakeAscending(array, start);
// If the run is too short extend to Min(MIN_RUN, remaining)
if (runLen < minRun)
{
var force = Math.Min(minRun, remaining);
BinarySort(array, start, start + force, start + runLen);
runLen = force;
}
runBase[stackSize] = start;
runLengths[stackSize] = runLen;
stackSize++;
MergeCollapse(array);
start += runLen;
remaining -= runLen;
}
while (remaining != 0);
MergeForceCollapse(array);
}
/// <summary>
/// Returns the minimum acceptable run length for an array of the specified
/// length.Natural runs shorter than this will be extended.
///
/// Computation is:
/// If total less than minRun, return n (it's too small to bother with fancy stuff).
/// Else if total is an exact power of 2, return minRun/2.
/// Else return an int k, where <![CDATA[minRun/2 <= k <= minRun]]>, such that total/k
/// is close to, but strictly less than, an exact power of 2.
/// </summary>
/// <param name="total">Total length remaining to sort.</param>
/// <returns>Minimum run length to be merged.</returns>
private static int MinRunLength(int total, int minRun)
{
var r = 0;
while (total >= minRun)
{
r |= total & 1;
total >>= 1;
}
return total + r;
}
/// <summary>
/// Reverse the specified range of the specified array.
/// </summary>
/// <param name="array">the array in which a range is to be reversed.</param>
/// <param name="start">the index of the first element in the range to be reversed.</param>
/// <param name="end">the index after the last element in the range to be reversed.</param>
private static void ReverseRange(T[] array, int start, int end)
{
end--;
while (start < end)
{
var t = array[start];
array[start++] = array[end];
array[end--] = t;
}
}
/// <summary>
/// Left shift a value, preventing a roll over to negative numbers.
/// </summary>
/// <param name="shiftable">int value to left shift.</param>
/// <returns>Left shifted value, bound to 2,147,483,647.</returns>
private static int BoundLeftShift(int shiftable) => (shiftable << 1) < 0
? (shiftable << 1) + 1
: int.MaxValue;
/// <summary>
/// Check the chunks before getting in to a merge to make sure there's something to actually do.
/// </summary>
/// <param name="left">TimChunk of the left hand side.</param>
/// <param name="right">TimChunk of the right hand side.</param>
/// <param name="dest">The current target point for the remaining values.</param>
/// <returns>If a merge is required.</returns>
private static bool NeedsMerge(TimChunk<T> left, TimChunk<T> right, ref int dest)
{
right.Array[dest++] = right.Array[right.Index++];
if (--right.Remaining == 0)
{
Array.Copy(left.Array, left.Index, right.Array, dest, left.Remaining);
return false;
}
if (left.Remaining == 1)
{
Array.Copy(right.Array, right.Index, right.Array, dest, right.Remaining);
right.Array[dest + right.Remaining] = left.Array[left.Index];
return false;
}
return true;
}
/// <summary>
/// Moves over the last parts of the chunks.
/// </summary>
/// <param name="left">TimChunk of the left hand side.</param>
/// <param name="right">TimChunk of the right hand side.</param>
/// <param name="dest">The current target point for the remaining values.</param>
private static void FinalizeMerge(TimChunk<T> left, TimChunk<T> right, int dest)
{
if (left.Remaining == 1)
{
Array.Copy(right.Array, right.Index, right.Array, dest, right.Remaining);
right.Array[dest + right.Remaining] = left.Array[left.Index];
}
else if (left.Remaining == 0)
{
throw new ArgumentException("Comparison method violates its general contract!");
}
else
{
Array.Copy(left.Array, left.Index, right.Array, dest, left.Remaining);
}
}
/// <summary>
/// Returns the length of the run beginning at the specified position in
/// the specified array and reverses the run if it is descending (ensuring
/// that the run will always be ascending when the method returns).
///
/// A run is the longest ascending sequence with:
///
/// <![CDATA[a[lo] <= a[lo + 1] <= a[lo + 2] <= ...]]>
///
/// or the longest descending sequence with:
///
/// <![CDATA[a[lo] > a[lo + 1] > a[lo + 2] > ...]]>
///
/// For its intended use in a stable mergesort, the strictness of the
/// definition of "descending" is needed so that the call can safely
/// reverse a descending sequence without violating stability.
/// </summary>
/// <param name="array">the array in which a run is to be counted and possibly reversed.</param>
/// <param name="start">index of the first element in the run.</param>
/// <returns>the length of the run beginning at the specified position in the specified array.</returns>
private int CountRunAndMakeAscending(T[] array, int start)
{
var runHi = start + 1;
if (runHi == array.Length)
{
return 1;
}
// Find end of run, and reverse range if descending
if (comparer.Compare(array[runHi++], array[start]) < 0)
{ // Descending
while (runHi < array.Length && comparer.Compare(array[runHi], array[runHi - 1]) < 0)
{
runHi++;
}
ReverseRange(array, start, runHi);
}
else
{ // Ascending
while (runHi < array.Length && comparer.Compare(array[runHi], array[runHi - 1]) >= 0)
{
runHi++;
}
}
return runHi - start;
}
/// <summary>
/// Find the position in the array that a key should fit to the left of where it currently sits.
/// </summary>
/// <param name="array">Array to search.</param>
/// <param name="key">Key to place in the array.</param>
/// <param name="i">Base index for the key.</param>
/// <param name="len">Length of the chunk to run through.</param>
/// <param name="hint">Initial starting position to start from.</param>
/// <returns>Offset for the key's location.</returns>
private int GallopLeft(T[] array, T key, int i, int len, int hint)
{
var (offset, lastOfs) = comparer.Compare(key, array[i + hint]) > 0
? RightRun(array, key, i, len, hint, 0)
: LeftRun(array, key, i, hint, 1);
return FinalOffset(array, key, i, offset, lastOfs, 1);
}
/// <summary>
/// Find the position in the array that a key should fit to the right of where it currently sits.
/// </summary>
/// <param name="array">Array to search.</param>
/// <param name="key">Key to place in the array.</param>
/// <param name="i">Base index for the key.</param>
/// <param name="len">Length of the chunk to run through.</param>
/// <param name="hint">Initial starting position to start from.</param>
/// <returns>Offset for the key's location.</returns>
private int GallopRight(T[] array, T key, int i, int len, int hint)
{
var (offset, lastOfs) = comparer.Compare(key, array[i + hint]) < 0
? LeftRun(array, key, i, hint, 0)
: RightRun(array, key, i, len, hint, -1);
return FinalOffset(array, key, i, offset, lastOfs, 0);
}
private (int offset, int lastOfs) LeftRun(T[] array, T key, int i, int hint, int lt)
{
var maxOfs = hint + 1;
var (offset, tmp) = (1, 0);
while (offset < maxOfs && comparer.Compare(key, array[i + hint - offset]) < lt)
{
tmp = offset;
offset = BoundLeftShift(offset);
}
if (offset > maxOfs)
{
offset = maxOfs;
}
var lastOfs = hint - offset;
offset = hint - tmp;
return (offset, lastOfs);
}
private (int offset, int lastOfs) RightRun(T[] array, T key, int i, int len, int hint, int gt)
{
var (offset, lastOfs) = (1, 0);
var maxOfs = len - hint;
while (offset < maxOfs && comparer.Compare(key, array[i + hint + offset]) > gt)
{
lastOfs = offset;
offset = BoundLeftShift(offset);
}
if (offset > maxOfs)
{
offset = maxOfs;
}
offset += hint;
lastOfs += hint;
return (offset, lastOfs);
}
private int FinalOffset(T[] array, T key, int i, int offset, int lastOfs, int lt)
{
lastOfs++;
while (lastOfs < offset)
{
var m = lastOfs + (int)((uint)(offset - lastOfs) >> 1);
if (comparer.Compare(key, array[i + m]) < lt)
{
offset = m;
}
else
{
lastOfs = m + 1;
}
}
return offset;
}
/// <summary>
/// Sorts the specified portion of the specified array using a binary
/// insertion sort. It requires O(n log n) compares, but O(n^2) data movement.
/// </summary>
/// <param name="array">Array to sort.</param>
/// <param name="start">The index of the first element in the range to be sorted.</param>
/// <param name="end">The index after the last element in the range to be sorted.</param>
/// <param name="first">The index of the first element in the range that is not already known to be sorted, must be between start and end.</param>
private void BinarySort(T[] array, int start, int end, int first)
{
if (first >= end || first <= start)
{
first = start + 1;
}
for (; first < end; first++)
{
var target = array[first];
var targetInsertLocation = BinarySearch(array, start, first - 1, target);
Array.Copy(array, targetInsertLocation, array, targetInsertLocation + 1, first - targetInsertLocation);
array[targetInsertLocation] = target;
}
}
private int BinarySearch(T[] array, int left, int right, T target)
{
while (left < right)
{
var mid = (left + right) >> 1;
if (comparer.Compare(target, array[mid]) < 0)
{
right = mid;
}
else
{
left = mid + 1;
}
}
return comparer.Compare(target, array[left]) < 0
? left
: left + 1;
}
private void MergeCollapse(T[] array)
{
while (stackSize > 1)
{
var n = stackSize - 2;
if (n > 0 && runLengths[n - 1] <= runLengths[n] + runLengths[n + 1])
{
if (runLengths[n - 1] < runLengths[n + 1])
{
n--;
}
MergeAt(array, n);
}
else if (runLengths[n] <= runLengths[n + 1])
{
MergeAt(array, n);
}
else
{
break;
}
}
}
private void MergeForceCollapse(T[] array)
{
while (stackSize > 1)
{
var n = stackSize - 2;
if (n > 0 && runLengths[n - 1] < runLengths[n + 1])
{
n--;
}
MergeAt(array, n);
}
}
private void MergeAt(T[] array, int index)
{
var baseA = runBase[index];
var lenA = runLengths[index];
var baseB = runBase[index + 1];
var lenB = runLengths[index + 1];
runLengths[index] = lenA + lenB;
if (index == stackSize - 3)
{
runBase[index + 1] = runBase[index + 2];
runLengths[index + 1] = runLengths[index + 2];
}
stackSize--;
var k = GallopRight(array, array[baseB], baseA, lenA, 0);
baseA += k;
lenA -= k;
if (lenA <= 0)
{
return;
}
lenB = GallopLeft(array, array[baseA + lenA - 1], baseB, lenB, lenB - 1);
if (lenB <= 0)
{
return;
}
Merge(array, baseA, lenA, baseB, lenB);
}
private void Merge(T[] array, int baseA, int lenA, int baseB, int lenB)
{
var endA = baseA + lenA;
var dest = baseA;
TimChunk<T> left = new()
{
Array = array[baseA..endA],
Remaining = lenA,
};
TimChunk<T> right = new()
{
Array = array,
Index = baseB,
Remaining = lenB,
};
// Move first element of the right chunk and deal with degenerate cases.
if (!TimSorter<T>.NeedsMerge(left, right, ref dest))
{
// One of the chunks had 0-1 items in it, so no need to merge anything.
return;
}
var gallop = minGallop;
while (RunMerge(left, right, ref dest, ref gallop))
{
// Penalize for leaving gallop mode
gallop = gallop > 0
? gallop + 2
: 2;
}
minGallop = gallop >= 1
? gallop
: 1;
FinalizeMerge(left, right, dest);
}
private bool RunMerge(TimChunk<T> left, TimChunk<T> right, ref int dest, ref int gallop)
{
// Reset the number of times in row a run wins.
left.Wins = 0;
right.Wins = 0;
// Run a stable merge sort until (if ever) one run starts winning consistently.
if (StableMerge(left, right, ref dest, gallop))
{
// Stable merge sort completed with no viable gallops, time to exit.
return false;
}
// One run is winning so consistently that galloping may be a huge win.
// So try that, and continue galloping until (if ever) neither run appears to be winning consistently anymore.
do
{
if (GallopMerge(left, right, ref dest))
{
// Galloped all the way to the end, merge is complete.
return false;
}
// We had a bit of a run, so make it easier to get started again.
gallop--;
}
while (left.Wins >= initMinGallop || right.Wins >= initMinGallop);
return true;
}
private bool StableMerge(TimChunk<T> left, TimChunk<T> right, ref int dest, int gallop)
{
do
{
if (comparer.Compare(right.Array[right.Index], left.Array[left.Index]) < 0)
{
right.Array[dest++] = right.Array[right.Index++];
right.Wins++;
left.Wins = 0;
if (--right.Remaining == 0)
{
return true;
}
}
else
{
right.Array[dest++] = left.Array[left.Index++];
left.Wins++;
right.Wins = 0;
if (--left.Remaining == 1)
{
return true;
}
}
}
while ((left.Wins | right.Wins) < gallop);
return false;
}
private bool GallopMerge(TimChunk<T> left, TimChunk<T> right, ref int dest)
{
left.Wins = GallopRight(left.Array, right.Array[right.Index], left.Index, left.Remaining, 0);
if (left.Wins != 0)
{
Array.Copy(left.Array, left.Index, right.Array, dest, left.Wins);
dest += left.Wins;
left.Index += left.Wins;
left.Remaining -= left.Wins;
if (left.Remaining <= 1)
{
return true;
}
}
right.Array[dest++] = right.Array[right.Index++];
if (--right.Remaining == 0)
{
return true;
}
right.Wins = GallopLeft(right.Array, left.Array[left.Index], right.Index, right.Remaining, 0);
if (right.Wins != 0)
{
Array.Copy(right.Array, right.Index, right.Array, dest, right.Wins);
dest += right.Wins;
right.Index += right.Wins;
right.Remaining -= right.Wins;
if (right.Remaining == 0)
{
return true;
}
}
right.Array[dest++] = left.Array[left.Index++];
if (--left.Remaining == 1)
{
return true;
}
return false;
}
}
C#
1
https://gitee.com/TheAlgorithms/C-Sharp.git
git@gitee.com:TheAlgorithms/C-Sharp.git
TheAlgorithms
C-Sharp
C-Sharp
master

搜索帮助