前言 算是比较系统的重新学一遍算法,首先学算法,第一位接触的就是排序算法了。虽然很久以前用的就比较熟了,学习起来没什么难度。不过,通过看github那些大牛写的代码,同样受益匪浅,在一些代码处理上算是十分优秀了,也学到了一些测试方法——单元测试的AAA规则,Arrange,Act,Assert。同时还在自己编写修改代码时学上一些github工作流程,虽然只是学的毛皮,不过够用就行了。
排序算法上使用的API 所有排序算法的接口
public interface IComparisonSorter <T >{ void Sort (T[] array, IComparer<T> comparer ) ; }
comparer接口的比较规则
internal class IntComparer : IComparer <int >{ int IComparer<int >.Compare(int x, int y) { return x.CompareTo(y); } }
冒泡排序 冒泡排序算是最简单的排序算法,两两进行比较交换直到不需要被交换
public class BubbleSorter <T > : IComparisonSorter <T >{ public void Sort (T[] array, IComparer<T> comparer ) { for (var i = 0 ; i < array.Length - 1 ; i++) { var wasChanged = false ; for (var j = 0 ; j < array.Length - i - 1 ; j++) { if (comparer.Compare(array[j], array[j + 1 ]) > 0 ) { var temp = array[j]; array[j] = array[j + 1 ]; array[j + 1 ] = temp; wasChanged = true ; } } if (!wasChanged) { break ; } } } }
选择排序 选择排序也是特别简单的算法之一,即在未排序的找到最小(大)元素放在排序序列的起始位置,然后重复步骤。
namespace Algorithms.Sorters { public class SelectionSorter <T > : IComparisonSorter <T > { public void Sort (T[] array, IComparer<T> comparer ) { for (var i = 0 ; i < array.Length - 1 ; i++) { var jmin = i; for (var j = i + 1 ; j < array.Length; j++) { if (comparer.Compare(array[jmin], array[j]) > 0 ) { jmin = j; } } var temp = array[i]; array[i] = array[jmin]; array[jmin] = temp; } } } }
插入排序 插入排序的工作原理就是从前往后扫描,将未排序的元素插入到已排序元素中。
namespace Algorithms.Sorters { public class InsertionSorter <T > : IComparisonSorter <T > { public void Sort (T[] array, IComparer<T> comparer ) { for (var i = 1 ; i < array.Length; i++) { for (var j = i; j > 0 && comparer.Compare(array[j], array[j - 1 ]) < 0 ; j--) { var temp = array[j - 1 ]; array[j - 1 ] = array[j]; array[j] = temp; } } } } }
希尔排序 希尔排序是优化过的插入排序,相对于插入排序只能一小步一小步的交换,希尔排序可以一大步一大步的交换,取决于当前的步长。
namespace Algorithms.Sorters { public class ShellSorter <T > : IComparisonSorter <T > { public void Sort (T[] array, IComparer<T> comparer ) { var step = 1 ; while (step < array.Length / 3 ) step = 3 * step + 1 ; while (step >= 1 ) { GappedInsertionSort(array, comparer, step); step /= 3 ; } } private static void GappedInsertionSort (T[] array, IComparer<T> comparer, int step ) { for (var i = step; i < array.Length; i++) { for (var j = i; j >= step && comparer.Compare(array[j], array[j - step]) < 0 ; j -= step) { var temp = array[j]; array[j] = array[j - step]; array[j - step] = temp; } } } } }
归并排序
namespace Algorithms.Sorters { public class MergeSorter <T > : IComparisonSorter <T > { public void Sort (T[] array, IComparer<T> comparer ) { if (array.Length <= 1 ) { return ; } var (left, right) = Split(array); Sort(left, comparer); Sort(right, comparer); Merge(array, left, right, comparer); } private static void Merge (T[] array, T[] left, T[] right, IComparer<T> comparer ) { var mainIndex = 0 ; var leftIndex = 0 ; var rightIndex = 0 ; while (leftIndex < left.Length && rightIndex < right.Length) { var compResult = comparer.Compare(left[leftIndex], right[rightIndex]); array[mainIndex++] = compResult <= 0 ? left[leftIndex++] : right[rightIndex++]; } while (leftIndex < left.Length) { array[mainIndex++] = left[leftIndex++]; } while (rightIndex < right.Length) { array[mainIndex++] = right[rightIndex++]; } } private static (T[] left, T[] right ) Split (T[] array ) { var mid = array.Length / 2 ; return (array.Take(mid).ToArray(), array.Skip(mid).ToArray()); } } }
快速排序 namespace Algorithms.Sorters { public abstract class QuickSorter <T > : IComparisonSorter <T > { public void Sort (T[] array, IComparer<T> comparer ) => Sort(array, comparer, 0 , array.Length - 1 ); protected abstract T SelectPivot (T[] array, IComparer<T> comparer, int left, int right ) ; private void Sort (T[] array, IComparer<T> comparer, int left, int right ) { if (left >= right) { return ; } var mid = Partition(array, comparer, left, right); Sort(array, comparer, left, mid); Sort(array, comparer, mid + 1 , right); } private int Partition (T[] array, IComparer<T> comparer, int left, int right ) { var pivot = SelectPivot(array, comparer, left, right); var nleft = left; var nright = right; while (true ) { while (comparer.Compare(array[nleft], pivot) < 0 ) { nleft++; } while (comparer.Compare(array[nright], pivot) > 0 ) { nright--; } if (nleft >= nright) { return nright; } var temp = array[nleft]; array[nleft] = array[nright]; array[nright] = temp; nleft++; nright--; } } } }
随便选一位参数
namespace Algorithms.Sorters { public sealed class RandomPivotQuickSorter <T > : QuickSorter <T > { private readonly Random _random = new (); protected override T SelectPivot (T[] array, IComparer<T> comparer, int left, int right ) => array[_random.Next(left, right + 1 )]; } }
选中间为参数
namespace Algorithms.Sorters { public sealed class MiddlePointQuickSorter <T > : QuickSorter <T > { protected override T SelectPivot (T[] array, IComparer<T> comparer, int left, int right ) => array[left + (right - left) / 2 ]; } }
三向切分快速排序 namespace Algorithms.Sorters { public class Quick3waySorter <T > : IComparisonSorter <T > { public void Sort (T[] array, IComparer<T> comparer ) => Sort(array, comparer, 0 , array.Length - 1 ); private void Sort (T[] array, IComparer<T> comparer, int left, int right ) { if (left >= right) { return ; } var (lt, rt) = Partition(array, comparer, left, right); Sort(array, comparer, left, lt - 1 ); Sort(array, comparer, rt + 1 , right); } private (int lt, int rt) Partition(T[] array, IComparer<T> comparer, int left, int right) { var pivot = array[left]; var lt = left; var i = left + 1 ; var rt = right; while (i <= rt) { var cmp = comparer.Compare(array[i], pivot); if (cmp < 0 ) { Swap(array, lt++, i++); } else if (cmp > 0 ) { Swap(array, i, rt--); } else { i++; } } return (lt, rt); } private void Swap (T[] array, int x, int y ) { var temp = array[x]; array[x] = array[y]; array[y] = temp; } } }
堆排序 堆排序即是利用堆的特性来进行排序,得出得结果是让数组为一个大根堆数组,而不是有序数组,若要获得有序数组,需要将堆顶一个一个拿出来,拿出堆顶时间为O(1),维护剩余的堆需要logn,所以总的时间复杂度为nlogn
namespace Algorithms.Sorters { public class HeapSorter <T > : IComparisonSorter <T > { public void Sort (T[] array, IComparer<T> comparer ) => HeapSort(array, comparer); private static void HeapSort (IList<T> data, IComparer<T> comparer ) { var heapSize = data.Count; for (var p = (heapSize - 1 ) / 2 ; p >= 0 ; p--) { MakeHeap(data, heapSize, p, comparer); } for (var i = data.Count - 1 ; i >= 0 ; i--) { var temp = data[i]; data[i] = data[0 ]; data[0 ] = temp; heapSize--; MakeHeap(data, heapSize, 0 , comparer); } } private static void MakeHeap (IList<T> data, int heapSize, int index, IComparer<T> comparer ) { var largest = index; var left = 2 * (index + 1 ) - 1 ; var right = 2 * (index + 1 ); if (left < heapSize && comparer.Compare(data[left], data[largest]) > 0 ) { largest = left; } if (right < heapSize && comparer.Compare(data[right], data[largest]) > 0 ) { largest = right; } if (largest != index) { var temp = data[index]; data[index] = data[largest]; data[largest] = temp; MakeHeap(data, heapSize, largest, comparer); } } } }
总结
可以直白的说,大多数情况下,快速排序是最佳选择。我们在工程实践上通常用的是官方提供的库,除非是要写的语言没有提供官方库,或者是官方库的算法不太理想,才会去自己手写排序。