C#之ArraySort (一)
本文主要记录一下自己的学习过程,如有误,请指正
C#里的 Array.Sort 主要采用了 3 种排序算法,根据每种算法的特性,合理使用,达到更优。
1. 插入排序(InsertionSort)
插入排序在数据量少,且大多有序的情况下,效率不错。
-
时间复杂度 :
- best - case :O(n).
- worst - case : O(n2).
-
wiki上的算法图解:
2. 快速排序(QuickSort)
快速排序,如其名字一样,它很快。
-
基本思想
采用分治思想,选区一个pivot作为基准,左边元素都小于它,右边则大与它,分割成两个区间。再按此方法进行分割,直到排序完成。
-
时间复杂度 :
- best - case : O(nlogn).
- worst - case : O(n2).
- avg - case : O(nlogn).
-
算法图解 :
- 选取一个pivot作为基准,这里取最右边的元素。
- left指针右移,直到 T[left] >= pivot;right指针左移,直到T[right] <= pivot。
交换 T[right] 、T[left]。 - 重复步骤 2,直到 left >= right。交换T[left]、 pivot。T[left]作为新基准,分割区间。
- 重复步骤2 步骤3。
-
优化:三数中值法
对最左,最右,以及中间的元素进行排序,如红框所示,pivot-> M,right->红箭头。
此外,还进行了一次额外交换,保证pivot的位置在右端,如蓝框所示,pivot值不变(仍然为5),位置改变,right->蓝箭头。
细心的读者,参考源码后可能会发现,.net中Sort采用的递归方式与一些教科书的写法有一点差异。有兴趣的童鞋,可以自行研究。
3. 堆排序(HeapSort)
堆排利用了堆这种数据结构。
-
堆的性质
- 总是一颗完全二叉树
- 大顶堆、小顶堆 满足任意一个。
- 大顶堆,堆中节点的值 总大于或等于 其孩子节点的值。
- 小顶堆,堆中节点的值 总小于或等于 其孩子节点的值。
-
性质简析
- 非叶节点个数 n = arr.Length / 2, 每个非叶节点元素则为 arr[n - 1], 其孩子节点则为 arr[2n - 1]、 arr[2n]。 当第n个节点 只有一个孩子时, arr[2n] 不存在
- 大顶堆: arr[n - 1] >= arr[2n - 1] , arr[n - 1] >= arr[2n]
- 小顶堆: arr[n - 1] <= arr[2n - 1] , arr[n - 1] <= arr[2n]
-
基本思想
构建大顶堆,交换首尾元素。断尾,重构大顶堆,再交换首尾元素。直到没有尾巴去断。
-
时间复杂度:
- any - case : O(nlogn).
-
算法图解:
- 无序堆和大根堆
- 构建大根堆
- 排序过程
- 构建大顶堆。从最后一个非叶节点arr[n - 1]开始。
判断两个 孩节点 的大小,其中最大的孩节点 继续与 非叶节点比较,若大于,则交换。接着调整 arr[n -2],直到arr[0],即根节点。 - 交换首元素和末元素,去除末元素。
- 重构大顶堆,由于交换的是根节点,所以我们从 arr[0] 开始, 判断方法与 步骤 1 类似。但这里,由于是从根节点arr[0]开始的,所以需要执行到到最后一个非叶节点,即 arr[n - 1]。
- 重复 步骤2 步骤3 直到完成排序。
4. 简单测试
- 测试条件
模式: release
次数:2k
数组:随机数组,可重复,大小N(100,1000,5000,10000,100000,1000000)
时间单位:s - 表
5. 全部代码
using System;
namespace Sort {
public class Sorts<T> where T : IComparable<T> {
#region 插排
public static void InsertionSort (T[] keys, int l, int r) {