排序总览
概述
这短时间找各种排序学了下,头晕脑胀的。猴子掰玉米,学的时候原理都理解了,可是记住了这个,忘记了前面的。。。
所以特意做个总览,把优缺点和特性梳理下。也方便后续查找。
大概描述下排序算法思路简介,和各个排序的C#实现。重点是描述下各个排序的应用场景,面对哪些特点的数据时哪种算法最优。包括时间和空间上优先级上的选择。是否需要稳定排序等等…
所需辅助空间最多:归并排序
所需辅助空间最少:堆排序
平均速度最快:快速排序
1. 冒泡
属性:属于交换排序,稳定排序
时间复杂度:n ~ n2 平均:n2
空间复杂度:1
2. 直接插入排序
属性:属于插入排序,稳定排序
时间复杂度:n ~ n2 平均:n2
空间复杂度:1
3. 直接选择排序
属性:属于选择排序,不稳定排序
时间复杂度:n2
空间复杂度:1
4. 堆排序
属性:属于选择排序,不稳定排序
时间复杂度:平均:nlog2n
空间复杂度:1
5. 计数排序
属性:,稳定排序
时间复杂度:长度(关键字个数+关键字基数)
空间复杂度:关键字基数
6. 桶排序
7. 希尔排序
属性:属于插入排序,不稳定排序
时间复杂度:n1.3
空间复杂度:1
8. 快速排序
属性:属于交换排序,不稳定排序
时间复杂度:nlog2n ~ n2 平均:nlog2n
空间复杂度:log2n
9. 归并排序
属性:稳定排序
时间复杂度:平均:nlog2n
空间复杂度:n
以下是C#代码实现:
具体代码实现
1. 冒泡
原理:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
private static void Sort(int[] number)
{
for (int j = 0; j < number.Length - 1; j++)
{
bool ischange = true;
for (int i = 1; i < number.Length - j; i++)
{
if (number[i-1] >number[i])
{
int temp = number[i];
number[i] = number[i - 1];
number[i - 1] = temp;
ischange = false;
}
}
if (ischange)
{
break;
}
}
}
2. 直接插入排序
原理:
当插入第i(i >= 1)时,前面的V[0],V[1],……,V[i-1]已经排好序。这时,用V[I]的排序码与V[i-1],V[i-2],…的排序码顺序进行比较,找到插入位置即将V[i]插入,原来位置上的元素向后顺移。
private static void InsertSort(int[] dataArray)
{
for (int i = 1; i < dataArray.Length; i++)
{
bool isInsert = false;
int iValue = dataArray[i];
for (int j = i - 1; j >= 0; j--)
{
if (dataArray[j] > iValue)
{
dataArray[j + 1] = dataArray[j];
}
else
{
dataArray[j + 1] = iValue;
isInsert = true;
break;
}
}
if (!isInsert)
{
dataArray[0] = iValue;
}
}
}
3. 直接选择排序
- 找到数组中最小的那个元素,将它和数组的第一个元素交换位置。
- 在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复。
public void ChooseSort(int[] origin)
{
for (int i = 0; i < origin.Length - 1; i++)
{
int minindex = i;
for (int j = i; j < origin.Length; j++)
{
if (origin[j] < origin[minindex])
{
minindex = j;
}
}
int temp = origin[minindex];
origin[minindex] = origin[i];
origin[i] = temp;
}
}
4. 堆排序
采用二叉堆的自我调整原理:
1.将要排序的数组创建为一个大根堆。大根堆的堆顶元素就是这个堆中最大的元素。
2.将大根堆的堆顶元素和无序区最后一个元素交换,并将无序区最后一个位置例入有序区,然后将新的无序区调整为大根堆。
重复操作,无序区在递减,有序区在递增。
初始时,整个数组为无序区,第一次交换后无序区减一,有序区增一。
每一次交换,都是大根堆的堆顶元素插入有序区,所以有序区保持是有序的。
public static void sort(int[] array)
{
//1. 把无序数组构建成最大堆
//根据大顶堆的性质可知:数组的前半段的元素为根节点,其余元素都为叶节点
//从最底层的最后一个根节点,从下至上,从右至左,进行大顶推的调整
for (int i = array.Length / 2 - 1; i >= 0; i--)
{
downAdjust(array, i, array.Length);
}
// 2. 循环删除堆顶元素,移到集合尾部,调整堆产生新的堆顶
for (int j = array.Length - 1; j > 0; j--)
{
int temp = array[0];
array[0] = array[j];
array[j] = temp;
downAdjust(array, 0, j);// “下沉”调整最大堆
}
}
/// <summary>
/// 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
/// </summary>
/// <param name="array"></param>
/// <param name="parentIndex">要“下沉”的父节点</param>
/// <param name="length">堆的有效大小</param>
public static void downAdjust(int[] array, int parentIndex, int length)
{
// temp 保存父节点值,用于最后的赋值
int temp = array[parentIndex];
int childIndex = 2 * parentIndex + 1;
while (childIndex < length)
{
// 如果有右孩子,且右孩子大于左孩子的值,则定位到右孩子
if (childIndex + 1 < length && array[childIndex + 1] > array[childIndex])
{
childIndex++;
}
// 如果父节点大于任何一个孩子的值,则直接跳出
if (temp >= array[childIndex]) break;
//无须真正交换,单向赋值即可
array[parentIndex] = array[childIndex];
parentIndex = childIndex;
childIndex = 2 * childIndex + 1;
}
array[parentIndex] = temp;
}
5. 计数排序
计数排序是一个非基于比较的排序算法
public int[] CountSort(int[] origin)
{
int max = origin[0];
int min = origin[0];
int[] fina = new int[origin.Length];
foreach (int item in origin)
{
max = item > max ? item : max;
min = item < min ? item : min;
}
int[] count = new int[max - min + 1];
for (int i = 0; i < origin.Length; i++)
{
count[origin[i] - min] += 1;
}
for (int i = 1; i < count.Length; i++)
{
count[i] += count[i - 1];
}
for (int i = origin.Length - 1; i >= 0; i--)
{
//b[--o[a[i] - min]] = a[i];
fina[count[origin[i] - min] - 1] = origin[i];
count[origin[i] - min]--;
}
return fina;
}
6. 桶排序
排一系列有序的“桶”,然后把元素按照区间填入,规模减小后各自排序,最后组合排列。
public static void BucketSortInt(int[] arr)
{
if (arr == null || arr.Length < 2) return;
int n = arr.Length;
int max = arr[0];
int min = arr[0];
// 寻找数组的最大值与最小值
for (int i = 1; i < n; i++)
{
if (min > arr[i])
min = arr[i];
if (max < arr[i])
max = arr[i];
}
//和优化版本的计数排序一样,弄一个大小为 min 的偏移值
int d = max - min;
//创建 d / 5 + 1 个桶,第 i 桶存放 5*i ~ 5*i+5-1范围的数
int bucketNum = d / 5 + 1;
List<List<int>> bucketList = new List<List<int>>(bucketNum);
//初始化桶
for (int i = 0; i < bucketNum; i++)
{
bucketList.Add(new List<int>());
}
//遍历原数组,将每个元素放入桶中
for (int i = 0; i < n; i++)
{
int index = (arr[i] - min) / d;
bucketList[index].Add(arr[i] - min);
}
//对桶内的元素进行排序,我这里采用系统自带的排序工具
for (int i = 0; i < bucketNum; i++)
{
bucketList[i].Sort();
}
//把每个桶排序好的数据进行合并汇总放回原数组
int k = 0;
for (int i = 0; i < bucketNum; i++)
{
foreach (var item in bucketList[i])
{
arr[k++] = item + min;
}
}
}
7. 希尔排序
8. 快速排序
原理:
快速排序采用分治法:找到一个基准数,把数组划分为比它大和比它小的两部分,然后对这两部分继续划分大小,直到两部分只有一枚时结束。
//实现一:
private static void QuickSort2(int[] arr, int startIndex, int endIndex)
{
if (startIndex >= endIndex)
{
return;
}
int stand = arr[startIndex];
int left = startIndex;
int right = endIndex;
while (left != right)
{
for (; right > left; right--)
{
if (arr[right] < stand)
{
break;
}
}
for (; right > left; left++)
{
if (arr[left] > stand)
{
break;
}
}
if (left < right)
{
int temp1 = arr[left];
arr[left] = arr[right];
arr[right] = temp1;
}
}
arr[startIndex] = arr[left];
arr[left] = stand;
QuickSort2(arr, startIndex, left - 1);
QuickSort2(arr, left + 1, endIndex);
}
//实现二:
private static void QuickSort(int[] arr, int startIndex, int endIndex)
{
if (startIndex >= endIndex)
{
return;
}
int stand = arr[startIndex];
int left = startIndex;
int right = endIndex;
while (left != right)
{
while (left < right && arr[right] > stand)
{
right--;
}
while (left < right && arr[left] <= stand)
{
left++;
}
if (left < right)
{
int temp1 = arr[left];
arr[left] = arr[right];
arr[right] = temp1;
}
}
arr[startIndex] = arr[left];
arr[left] = stand;
QuickSort(arr, startIndex, left - 1);
QuickSort(arr, left + 1, endIndex);
}
9. 归并排序
通过递归的方式将大的数组一直分割,直到数组的大小为 1,此时只有一个元素,那么该数组就是有序的了,之后再把两个数组大小为1的合并成一个大小为2的,再把两个大小为2的合并成4的 … 直到全部小的数组合并起来。