排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
我们这里说说八大排序就是内部排序。
当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
1.插入排序—直接插入排序(Straight Insertion Sort)
using System.Collections.Generic;
namespace SortAlgorithm
{
/// <summary>
/// 【插入排序--直接插入排序】
/// 将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。
/// 即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
/// 时间复杂度 O(n^2) 空间复杂度 O(1)
/// </summary>
public class StraightInsertionSort : SortBase
{
public override void Sort(List<int> nums)
{
int count = nums.Count;
for(int i = 1; i < count; i++)
{
if(nums[i] < nums[i-1])
{
int temp = nums[i];
int j = i - 1;
while(j >= 0 && nums[j] > temp)
{
nums[j + 1] = nums[j];
j--;
}
nums[j + 1] = temp;
}
}
}
}
}
2. 插入排序—希尔排序(Shell`s Sort)
using System.Collections.Generic;
namespace SortAlgorithm
{
/// <summary>
/// 【插入排序--希尔排序】
/// 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;
/// 随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
/// 不稳定 时间复杂度 O(n^(1.3—2)) 空间复杂度 O(1)
/// </summary>
public class ShellSort : SortBase
{
public override void Sort(List<int> nums)
{
int count = nums.Count;
for (int dk = count / 2; dk >= 1; dk /= 2)
{
// 增量为dk时,需要对dk个分组进行直接插入排序。
for(int groupIndex = 0; groupIndex < dk; groupIndex++)
ShellInsertSort(nums, dk, groupIndex);
}
}
// 使用直接插入排序做分组的排序
private void ShellInsertSort(List<int> nums, int dk, int groupIndex)
{
int count = nums.Count;
for(int i = groupIndex + dk; i < count; i += dk)
{
if(nums[i] < nums[i - dk])
{
int temp = nums[i];
int j = i - dk;
while(j >= 0 && nums[j] > temp)
{
nums[j + dk] = nums[j];
j -= dk;
}
nums[j + dk] = temp;
}
}
}
}
}
3. 选择排序—简单选择排序(Simple Selection Sort)
using System.Collections.Generic;
namespace SortAlgorithm
{
/// <summary>
/// 【选择排序--简单选择排序】
/// 在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,
/// 依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
/// 不稳定 时间复杂度 O (n^2) 空间复杂度 O(1)
/// </summary>
public class SimpleSelectSort : SortBase
{
public override void Sort(List<int> nums)
{
int count = nums.Count;
int minValueIndex = 0;
for (int i = 0; i < count; i++)
{
minValueIndex = i;
for (int j = i + 1; j < count; j++)
{
if(nums[j] < nums[minValueIndex])
minValueIndex = j;
}
if(minValueIndex != i)
{
int temp = nums[i];
nums[i] = nums[minValueIndex];
nums[minValueIndex] = temp;
}
}
}
}
}
4. 选择排序—堆排序(Heap Sort)
using System.Collections.Generic;
namespace SortAlgorithm
{
/// <summary>
/// 【选择排序--堆排序】
/// 堆排序是指利用堆这种数据结构所设计的一种排序算法。
/// 堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
/// 不稳定 时间复杂度 O(nlogn) 空间复杂度 O(1)
/// </summary>
public class BubbleSort : SortBase
{
public override void Sort(List<int> nums)
{
int count = nums.Count;
//这里元素的索引是从0开始的,所以最后一个非叶子结点array.length/2 - 1
for (int i = count / 2 - 1; i >= 0; i--)
{
AdjustHeap(nums, i, count); // 调整堆
}
// 上述逻辑,建堆结束
// 下面,开始排序逻辑
for (int j = count - 1; j > 0; j--)
{
// 元素交换,作用是去掉大顶堆
// 把大顶堆的根元素,放到数组的最后;换句话说,就是每一次的堆调整之后,都会有一个元素到达自己的最终位置
Swap(nums, 0, j);
// 元素交换之后,毫无疑问,最后一个元素无需再考虑排序问题了。
// 接下来我们需要排序的,就是已经去掉了部分元素的堆了,这也是为什么此方法放在循环里的原因
// 而这里,实质上是自上而下,自左向右进行调整的
AdjustHeap(nums, 0, j);
}
}
/// <summary>
/// 整个堆排序最关键的地方
/// </summary>
/// <param name="nums">待组堆</param>
/// <param name="rootIndex">起始结点</param>
private void AdjustHeap(List<int> nums, int rootIndex, int length)
{
// 先把当前元素取出来,因为当前元素可能要一直移动
int rootValue = nums[rootIndex];
for (int k = 2 * rootIndex + 1; k < length; k = 2 * k + 1)
{ //2*i+1为左子树i的左子树(因为i是从0开始的),2*k+1为k的左子树
// 让k先指向子节点中最大的节点
if (k + 1 < length && nums[k] < nums[k + 1])
{ //如果有右子树,并且右子树小于左子树
k++;
}
//如果发现结点(左右子结点)大于根结点,则进行值的交换
if (nums[k] > rootValue)
{
Swap(nums, k, rootIndex);
// 如果子节点更换了,那么,以子节点为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断
rootIndex = k;
}
else
{ //不用交换,直接终止循环
break;
}
}
}
private void Swap(List<int> nums, int i, int j)
{
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
}
5. 交换排序—冒泡排序(Bubble Sort)
using System.Collections.Generic;
namespace SortAlgorithm
{
/// <summary>
/// 【交换排序--冒泡排序】
/// 在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。
/// 即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
/// 稳定 时间复杂度 O (n^2) 空间复杂度 O(1)
/// </summary>
public class HeapSort : SortBase
{
public override void Sort(List<int> nums)
{
int count = nums.Count;
for(int i = 0; i < count - 1; i++)
{
for(int j = 0; j < count - i - 1; j++)
{
if(nums[j] > nums[j + 1])
{
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
}
/// <summary>
/// 冒泡排序的改进
/// 设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。
/// 由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。
/// </summary>
void Bubble_1(List<int> nums)
{
int n = nums.Count;
int i = n - 1; //初始时,最后位置保持不变
while (i > 0)
{
int pos = 0; //每趟开始时,无记录交换
for (int j = 0; j < i; j++)
if (nums[j] > nums[j + 1])
{
pos = j; //记录交换的位置
int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp;
}
i = pos; //为下一趟排序作准备
}
}
}
}
6. 交换排序—快速排序(Quick Sort)
using System.Collections.Generic;
namespace SortAlgorithm
{
/// <summary>
/// 【交换排序--快速排序】
/// 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,
/// 然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
/// 稳定 时间复杂度 O(nlogn) 空间复杂度 O(nlogn)
/// </summary>
public class QuickSort : SortBase
{
public override void Sort(List<int> nums)
{
DoQuickSort(nums, 0, nums.Count - 1);
}
private void DoQuickSort(List<int> nums, int low, int high)
{
if (low >= high)
return;
/*完成一次单元排序*/
int index = SortUnit(nums, low, high);
/*对左边单元进行排序*/
DoQuickSort(nums, low, index - 1);
/*对右边单元进行排序*/
DoQuickSort(nums, index + 1, high);
}
private int SortUnit(List<int> nums, int low, int high)
{
int key = nums[low];
while (low < high)
{
/*从后向前搜索比key小的值*/
while (nums[high] >= key && high > low)
--high;
/*比key小的放左边*/
nums[low] = nums[high];
/*从前向后搜索比key大的值,比key大的放右边*/
while (nums[low] <= key && high > low)
++low;
/*比key大的放右边*/
nums[high] = nums[low];
}
/*左边都比key小,右边都比key大。//将key放在游标当前位置。//此时low等于high */
nums[low] = key;
return high;
}
}
}
7. 归并排序(Merge Sort)
using System.Collections.Generic;
namespace SortAlgorithm
{
/// <summary>
/// 【归并排序】
/// 归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,
/// 每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
/// 稳定 时间复杂度 O(nlogn) 空间复杂度 O(n)
/// </summary>
public class MergeSort : SortBase
{
public override void Sort(List<int> nums)
{
Sort(nums, 0, nums.Count - 1);
}
public void Sort(List<int> nums, int f, int e)
{
if (f < e)
{
int mid = (f + e) / 2;
Sort(nums, f, mid);
Sort(nums, mid + 1, e);
MergeMethod(nums, f, mid, e);
}
}
private void MergeMethod(List<int> nums, int f, int mid, int e)
{
int[] t = new int[e - f + 1];
int m = f, n = mid + 1, k = 0;
while (n <= e && m <= mid)
{
if (nums[m] > nums[n]) t[k++] = nums[n++];
else t[k++] = nums[m++];
}
while (n < e + 1) t[k++] = nums[n++];
while (m < mid + 1) t[k++] = nums[m++];
for (k = 0, m = f; m < e + 1; k++, m++) nums[m] = t[k];
}
}
}
8. 桶排序/基数排序(Radix Sort)
using System.Collections.Generic;
namespace SortAlgorithm
{
/// <summary>
/// 【基数排序】
/// 是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。
/// 稳定 O (nlog(r)m),其中r为所采取的基数,而m为堆数
/// </summary>
public class RadixSort : SortBase
{
public override void Sort(List<int> nums)
{
int iMaxLength = GetMaxLength(nums);
DoRadixSort(nums, iMaxLength);
}
//排序
private void DoRadixSort(List<int> nums, int iMaxLength)
{
List<int> list = new List<int>(); //存放每次排序后的元素
List<int>[] listArr = new List<int>[10]; //十个桶
char currnetChar;//存放当前的字符比如说某个元素123中的2
string currentItem;//存放当前的元素比如说某个元素123
for (int i = 0; i < listArr.Length; i++) //给十个桶分配内存初始化。
listArr[i] = new List<int>();
for (int i = 0; i < iMaxLength; i++) //一共执行iMaxLength次,iMaxLength是元素的最大位数。
{
foreach (int number in nums)//分桶
{
currentItem = number.ToString(); //将当前元素转化成字符串
try
{
currnetChar = currentItem[currentItem.Length - i - 1]; //从个位向高位开始分桶
}
catch
{
listArr[0].Add(number); //如果发生异常,则将该数压入listArr[0]。比如说5是没有十位数的,执行上面的操作肯定会发生越界异常的,这正是期望的行为,我们认为5的十位数是0,所以将它压入listArr[0]的桶里。
continue;
}
switch (currnetChar)//通过currnetChar的值,确定它压人哪个桶中。
{
case '0':
listArr[0].Add(number);
break;
case '1':
listArr[1].Add(number);
break;
case '2':
listArr[2].Add(number);
break;
case '3':
listArr[3].Add(number);
break;
case '4':
listArr[4].Add(number);
break;
case '5':
listArr[5].Add(number);
break;
case '6':
listArr[6].Add(number);
break;
case '7':
listArr[7].Add(number);
break;
case '8':
listArr[8].Add(number);
break;
case '9':
listArr[9].Add(number);
break;
default:
throw new System.Exception("unknowerror");
}
}
for (int j = 0; j < listArr.Length; j++) //将十个桶里的数据重新排列,压入list
for (int k = 0; k < listArr[j].Count; k++)
{
list.Add(listArr[j][k]);
listArr[j].Clear();//清空每个桶
}
nums.Clear();
nums.AddRange(list);
list.Clear();//清空list
}
}
//得到最大元素的位数
private static int GetMaxLength(List<int> nums)
{
int iMaxNumber = System.Int32.MinValue;
foreach (int i in nums)//遍历得到最大值
{
if (i > iMaxNumber)
iMaxNumber = i;
}
return iMaxNumber.ToString().Length;//这样获得最大元素的位数是不是有点投机取巧了...
}
}
}