根据排序过程中涉及的存储器的不同,可将排序方法分为两大类:内部排序和外部排序。
- 内部排序:指待排序记录存放在计算机随机存储器中进行的排序过程
- 外部排序:待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。
- 根据排序过程中依据的不同原则对内部排序方法进行分类,大致分为:插入排序、交换排序、选择排序、归并排序和计数排序等
- 根据内部排序过程中所需的工作量区分,可分为:简单的排序方法(时间复杂度:O(n2))、先进的排序方法(时间复杂度:O(nlogn))、基数排序(时间复杂度:O(d*n))
插入排序
直接插入排序:
将一个记录插入到已排序的有序表中,从而得到一个新的、记录数增1的有序表。
空间上,需要增加一个存储空间,用于保存带排序的关键字
时间上,基本操作为比较两个关键字的大小和移动记录。最好的情况是待排序列为正序(关键字非递减有序排列),只需比较n-1次(即
),不需要移动,最坏的情况是待排序列为逆序(关键字非递增有序排列),比较次数为(n+2)(n-1)/2(即
),记录需要移动次数也最多,即(n+4)(n-1)/2(即
),所以,直接插入排序的时间复杂度为O(n2)。
直接插入排序算法简单,容易实现,当排序记录的数量n很小时,这种排序方法很好,但是当n很大时,不宜采用此方法。
直接插入排序代码如下:
/// <summary>
/// 直接插入排序(时间复杂度O(n2))
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
private int[] StraightInsertion(int[] arr)
{
for (int i = 1, j = arr.Length; i < j; i++)
{
int ing = arr[i];
int m = i - 1;
while (m >= 0 && arr[m] > ing)
{
arr[m + 1] = arr[m];
arr[m] = ing;
m--;
}
}
return arr;
}
折半插入排序:
在进行关键字查找比较时,采用“折半查找”实现。排序过程中,所需附加存储空间和直接插入排序相同,只是折半插入排序减少了关键字间的比较次数,而移动次数不变。比较次数为O( nlog 2 n),且与原始数据的排列无关,移动次数为O(n 2),即时间复杂度为O(n 2)。/// <summary>
/// 折半插入排序(时间复杂度O(n2))
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
private static int[] BinaryInsertion(int[] arr)
{
for (int i = 1, j = arr.Length; i < j; i++)
{
int ing = arr[i];
int low = 0, high = i - 1;
while (low <= high)
{
int m = (low + high) / 2;
if (ing < arr[m])
{
high = m - 1;
}
else
{
low = m + 1;
}
}
for (int n = i - 1; n >= high + 1; n--)
{
arr[n + 1] = arr[n];
}
arr[high + 1] = ing;
}
return arr;
}
2-路插入排序:
此排序是在折半插入排序基础上改进的,目的是减少排序过程中移动记录的次数,但需要n个记录的辅助空间。private static int[] TwoWayInsertion(int[] arr)
{
int[] d = new int[arr.Length];//辅助数组
int first = arr.Length, last = 1;//first:记录排序过程中得到的有序序列中的第一个记录,last:最后一个记录在d中的位置
d[0] = arr[0];//把数据第0个付给辅助数组第0位,并将其看成是排好序的序列中处于中间位置的记录
for (int i = 1, j = arr.Length; i < j; i++)//从第一位遍历数组
{
if (arr[i] > d[0])//如果关键字大,则需放在辅助数组d[0]的右侧
{
d[last] = arr[i];//向d中添加关键字
int tlast = last;
while (tlast > 1 && d[tlast] < d[tlast - 1])//让d[1]-d[last]之间的关键字有序
{
d[tlast] = d[tlast - 1];//后移数组
d[tlast - 1] = arr[i];
tlast--;
}
last++;
}
else//关键字小,需放在d[0]的"左侧",即辅助数组的末尾,
{
int tfirst = first;
first--;
d[first] = arr[i];
while (tfirst < d.Length && d[tfirst - 1] > d[tfirst])//让d[first]-d[d.length]之间的关键字有序
{
d[tfirst] = d[tfirst - 1];//后移数组
d[tfirst - 1] = arr[i];
tfirst++;
}
}
}
return d;
}
返回的结果如图(first指向1所在位置):
private static int[] TwoWayInsertion(int[] arr)
{
int[] d = new int[arr.Length];//辅助数组
int first = arr.Length, last = 1;//first:记录排序过程中得到的有序序列中的第一个记录,last:最后一个记录在d中的位置
d[0] = arr[0];//把数据第0个付给辅助数组第0位,并将其看成是排好序的序列中处于中间位置的记录
for (int i = 1, j = arr.Length; i < j; i++)//从第一位遍历数组
{
if (arr[i] > d[0])//如果关键字大,则需放在辅助数组d[0]的右侧
{
d[last] = arr[i];//向d中添加关键字
int tlast = last;
while (tlast > 1 && d[tlast] < d[tlast - 1])//让d[1]-d[last]之间的关键字有序
{
d[tlast] = d[tlast - 1];//后移数组
d[tlast - 1] = arr[i];
tlast--;
}
last++;
}
else//关键字小,需放在d[0]的"左侧",即辅助数组的末尾,
{
int tfirst = first;
first--;
d[first] = arr[i];
while (tfirst < d.Length && d[tfirst - 1] > d[tfirst])//让d[first]-d[d.length]之间的关键字有序
{
d[tfirst] = d[tfirst - 1];//后移数组
d[tfirst - 1] = arr[i];
tfirst++;
}
}
}
return d;
}
希尔排序:又称缩小增量排序
先将整个待排记录序列分割成若干个子序列分别进行直接插入排序,待整个序列中的关键字“基本有序”时,在对全体记录进行一次直接插入排序。其特点是子序列的构成不是简单的“逐段分割”,而是将相隔某个“增量”的记录组成一个子序列。但是希尔排序的分析是一个复杂的问题,因为它的时间是所取“增量”序列的函数。
/// <summary>
/// 核心
/// </summary>
/// <param name="arr">待排数组</param>
/// <param name="skep">增量</param>
private static void ShellInsert(int[] arr, int skep)
{
for (int i = skep; i <arr.Length; i++)//去增量后数据比较
{
if (arr[i] < arr[i - skep])//跟对应增量的前一个关键字比较
{
int init = arr[i];
for (int j = i - skep; j >= 0 && init < arr[j]; j -= skep)//对按增量所取的关键字进行排序
{
arr[j + skep] = arr[j];
arr[j] = init;
}
}
}
}
/// <summary>
/// 希尔排序
/// </summary>
/// <param name="arr">待排数组</param>
/// <param name="skeps">增量数组</param>
/// <param name="t">增量数组长度</param>
/// <returns></returns>
private static int[] ShellSort(int[] arr, int[] skeps, int t)
{
for (int i = 0; i < t; i++)
{
ShellInsert(arr, skeps[i]);
}
return arr;
}
增量序列可以可以有各种取法,但需注意,应使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须等于1。