排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。
内部排序和外部排序:
若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。内部排序的过程是一个逐步扩大记录的有序序列长度的过程。
在计算机科学所使用的排序算法通常被分类为:
(a)计算的复杂度(最差、平均、和最好性能),依据列表(list)的大小(n)。
一般而言,好的性能是 O(nlogn),且坏的性能是 O(n^2)。对于一个排序理想的性能是 O(n)。
而仅使用一个抽象关键比较运算的排序算法总平均上总是至少需要 O(nlogn)。
(b)存储器使用量(空间复杂度)(以及其他电脑资源的使用)
(c)稳定度(稳定性):稳定的排序算法会依照相等的关键(换言之就是值)维持纪录的相对次序。
一个排序算法是稳定的,就是当有两个相等记录的关键字R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
当相等的元素是无法分辨的,比如像是整数,稳定度并不是一个问题。然而,假设以下的数对将要以他们的第一个数字来排序。
(4,1)(3,1)(3,7)(5,6)
在这个状况下,有可能产生两种不同的结果,一个是依照相等的键值维持相对的次序,而另外一个则没有:
(3,1)(3,7)(4,1)(5,6) (维持次序)
(3,7)(3,1)(4,1)(5,6) (次序被改变)
其中冒泡,插入,基数,归并属于稳定排序,选择,快速,希尔,堆属于不稳定排序。
(d)就地排序:若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间为O(1),则称为就地排序。
(e)一般的方法:插入、交换、选择、合并等等。交换排序包含冒泡排序和快速排序。插入排序包含希尔排序,选择排序包括堆排序等。(百度百科)
一、交换排序
1.1、冒泡排序
其大体思想就是通过与相邻元素的比较和交换来把小的数交换到最前面。这个过程类似于水泡向上升一样,因此而得名。例如,对5,3,8,6,4这个无序序列进行冒泡排序。首先从后向前冒泡,4和6比较,把4交换到前面,序列变成5,3,8,4,6。同理4和8交换,变成5,3,4,8,6,3和4无需交换。5和3交换,变成3,5,4,8,6,3.这样一次冒泡就完了,把最小的数3排到最前面了。对剩下的序列依次冒泡就会得到一个有序序列。冒泡排序的时间复杂度为O(n^2)。理论上总共要进行n(n-1)/2次交换。
实现代码:
1 /// <summary> 2 /// 冒泡排序 3 /// </summary> 4 /// <param name="arry">要排序的整数数组</param> 5 public static void BubbleSort(this int[] arry) 6 { 7 for (int i = 0; i < arry.Length; i++) 8 { 9 for (int j = 0; j < arry.Length - 1 - i; j++) 10 { 11 //比较相邻的两个元素,如果前面的比后面的大,则交换位置 12 if (arry[j] > arry[j + 1]) 13 { 14 int temp = arry[j + 1]; 15 arry[j + 1] = arry[j]; 16 arry[j] = temp; 17 } 18 } 19 } 20 }
测试代码:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 int[] arry = new int[] { 1, 2, 3, 4, 1, -1 }; 6 arry.BubbleSort(); 7 for (int i = 0; i < arry.Length; i++) 8 { 9 Console.Write(" " + arry[i]); 10 } 11 Console.Read(); 12 } 13 }
输出:-1 1 1 2 3 4
小结:
这里采用的是为整型数组添加扩展方法实现的冒泡排序。
优点:稳定
缺点:慢,每次只移动相邻的两个元素。
时间复杂度:理想情况下(数组本来就是有序的),此时最好的时间复杂度为o(n),最坏的时间复杂度(数据反序的),此时的时间复杂度为o(n*n) 。冒泡排序的平均时间负责度为o(n*n)。
1.2、快速排序
分析问题:
快速排序算法是公认的最为优秀的内部排序算法之一,其实实现思想很简单,并且在一般情况下性能较高。
基本思想:
a、假设待排序的序列为L[m...n],其中L[m...midlle-1]中的每个元素都小于L[midlle],而L[midlle+1...n]中的每个元素都大于L[midlle]。
b、递归调用快速排序算法,对L[m...midlle-1]和L[midlle+1...n]分别进行排序。
c、由于是原地排序,所以递归结束后自然形成了有序序列。
方案一:
算法思路:
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]互换;
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。
实现代码:
1 /// <summary> 2 /// 快速排序 3 /// </summary> 4 /// <param name="arry">要排序的数组</param> 5 /// <param name="left">低位</param> 6 /// <param name="right">高位</param> 7 public static void QuickSort(this int[] arry, int left, int right) 8 { 9 //左边索引小于右边,则还未排序完成 10 if (left < right) 11 { 12 //取中间的元素作为比较基准,小于他的往左边移,大于他的往右边移 13 int middle = arry[(left + right) / 2]; 14 int i = left - 1; 15 int j = right + 1; 16 while (true) 17 { 18 //移动下标,左边的往右移动,右边的向左移动 19 while (arry[++i] < middle && i < right); 20 while (arry[--j] > middle && j > 0); 21 if (i >= j) 22 break; 23 //交换位置 24 int number = arry[i]; 25 arry[i] = arry[j]; 26 arry[j] = number; 27 28 } 29 QuickSort(arry, left, i - 1); 30 QuickSort(arry, j + 1, right); 31 } 32 }
测试代码:
1 static void Main(string[] args) 2 { 3 int[] arry = new int[] { 34,1,221,50,44,58,12,1,1}; 4 //arry.BubbleSort(); 5 arry.QuickSort(0, arry.Length-1 ); 6 for (int i = 0; i < arry.Length; i++) 7 { 8 Console.Write(" " + arry[i]); 9 } 10 Console.Read(); 11 }
输出:1 1 1 12 34 44 50 58 221
方案二:
实现代码:
1 using System; 2 3 namespace Guying.Demo.ConsoleApp 4 { 5 class QuickSort 6 { 7 static void Main(string[] args) 8 { 9 int[] data = new int[] { 1, 1, 4, 3, 6, 7, 4, 5, 0, 0 }; // 建立测试数组 10 Run(data, 0, data.Length - 1); // 进行快速排序 11 for (int i = 0; i < data.Length; i++) 12 Console.Write("{0}, ", data[i]); 13 Console.Read(); 14 } 15 16 /// <summary> 17 /// 快速排序算法 18 /// </summary> 19 /// <param name="data">排序数组</param> 20 /// <param name="low">排序下限</param> 21 /// <param name="high">排序上限</param> 22 static void Run(int[] data, int low, int high) 23 { 24 /** 25 * 简单设定中间值,并以此为一趟快速排序的分割点 26 * 注意这里是一个简单的算法 27 * 如果想对这个算法进行优化的话,可以采取随机的方法来获取分割点 28 * */ 29 int middle = data[(low + high) / 2]; 30 31 int i = low, j = high; // 设定移动上下标 32 33 // 直至分割出两个序列 34 do 35 { 36 // 扫描中值左边元素 37 while (data[i] < middle && i < high) i++; 38 // 扫描中值右边元素 39 while (data[j] > middle && j > low) j--; 40 // 找到了一对可交换的值 41 if (i <= j) 42 { 43 // 交换 44 int temp = data[i]; 45 data[i] = data[j]; 46 data[j] = temp; 47 i++; 48 j--; 49 } 50 } while (i <= j); 51 // 递归对比分割点元素都小的那个序列进行快速排序 52 if (j > low) Run(data, low, j); 53 // 递归对比分割点元素都大的那个序列进行快速排序 54 if (i < high) Run(data, i, high); 55 } 56 } 57 }
输出:0 0 1 1 3 4 4 5 6 7
小结:
快速排序算法在最坏的情况下运行时间为O(n2),平均运行时间为O(nlgn)。快速排序的对象都读入内存中,所以输入内部输入内部排序。
快速排序基于比较关键字来确定元素的位置,所以它属于比较排序。同时,快速排序具有原地排序特性和不稳定特性,这意味着快速排序不需要额外的排序空间,但是不能确保相等的元素位置不被交换。
快速排序是最为高效的内部排序算法之一,其基本思想在于把排序对象分割为两列子序列,而其中一个子序列的值都大于另一个子序列的值,并且进一步递归排序所有子序列。
二、插入排序
2.1、直接插入排序
每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。
第一趟比较前两个数,然后把第二个数按大小插入到有序表中; 第二趟把第三个数据与前两个数从前向后扫描,把第三个数按大小插入到有序表中;依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。
直接插入排序属于稳定的排序,最坏时间复杂性为O(n^2),空间复杂度为O(1)。
直接插入排序是由两层嵌套循环组成的。外层循环标识并决定待比较的数值。内层循环为待比较数值确定其最终位置。直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的。当前一数值比待比较数值大的情况下继续循环比较,直到找到比待比较数值小的并将待比较数值置入其后一位置,结束该次循环。
值得注意的是,我们必需用一个存储空间来保存当前待比较的数值,因为当一趟比较完成时,我们要将待比较数值置入比它小的数值的后一位 插入排序类似玩牌时整理手中纸牌的过程。插入排序的基本方法是:每步将一个待排序的记录按其关键字的大小插到前面已经排序的序列中的适当位置,直到全部记录插入完毕为止。
实现代码:
1 /// <summary> 2 /// 直接插入排序 3 /// </summary> 4 /// <param name="arry">要排序的数组</param> 5 public static void InsertSort(this int[] arry) 6 { 7 //直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的 8 for (int i = 1; i < arry.Length; i++) 9 { 10 //如果当前元素小于其前面的元素 11 if (arry[i] < arry[i - 1]) 12 { 13 //用一个变量来保存当前待比较的数值,因为当一趟比较完成时,我们要将待比较数值置入比它小的数值的后一位 14 int temp = arry[i]; 15 int j = 0; 16 for (j = i - 1; j >= 0 && temp < arry[j]; j--) 17 { 18 arry[j + 1] = arry[j]; 19 } 20 arry[j + 1] = temp; 21 } 22 } 23 }
测试代码:
1 static void Main(string[] args) 2 { 3 int[] arry = new int[] { 34,1,221,50,44,58,12}; 4 //arry.BubbleSort(); 5 //arry.QuickSort(0, arry.Length-1 ); 6 arry.InsertSort(); 7 for (int i = 0; i < arry.Length; i++) 8 { 9 Console.Write(" " + arry[i]); 10 } 11 Console.Read(); 12 }
输出:1 12 34 44 50 58 221
2.2、希尔排序
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。
实现代码:
1 /// <summary> 2 /// 希尔排序 3 /// </summary> 4 /// <param name="arry">待排序的数组</param> 5 public static void ShellSort(this int[] arry) 6 { 7 int length = arry.Length; 8 for (int h = length / 2; h > 0; h = h / 2) 9 { 10 //here is insert sort 11 for (int i = h; i < length; i++) 12 { 13 int temp = arry[i]; 14 if (temp < arry[i - h]) 15 { 16 for (int j = 0; j < i; j += h) 17 { 18 if (temp<arry[j]) 19 { 20 temp = arry[j]; 21 arry[j] = arry[i]; 22 arry[i] = temp; 23 } 24 } 25 } 26 } 27 } 28 }
测试代码:
1 static void Main(string[] args) 2 { 3 int[] arry = new int[] { 34,1,221,50,44,58,12}; 4 //arry.BubbleSort(); 5 //arry.QuickSort(0, arry.Length-1 ); 6 //arry.InsertSort(); 7 arry.ShellSort(); 8 for (int i = 0; i < arry.Length; i++) 9 { 10 Console.Write(" " + arry[i]); 11 } 12 Console.Read(); 13 }
输出:1 12 34 44 50 58 221
图例:
三、选择排序
3.1、简单选择排序
设所排序序列的记录个数为n。i取1,2,…,n-1,从所有n-i+1个记录(Ri,Ri+1,…,Rn)中找出排序码最小的记录,与第i个记录交换。执行n-1趟 后就完成了记录序列的排序。
在简单选择排序过程中,所需移动记录的次数比较少。最好情况下,即待排序记录初始状态就已经是正序排列了,则不需要移动记录。
最坏情况下,即待排序记录初始状态是按逆序排列的,则需要移动记录的次数最多为3(n-1)。简单选择排序过程中需要进行的比较次数与初始状态下待排序的记录序列的排列情况无关。当i=1时,需进行n-1次比较;当i=2时,需进行n-2次比较;依次类推,共需要进行的比较次数是(n-1)+(n-2)+…+2+1=n(n-1)/2,即进行比较操作的时间复杂度为O(n^2),进行移动操作的时间复杂度为O(n)。
实现代码:
1 /// <summary> 2 /// 简单选择排序 3 /// </summary> 4 /// <param name="arry">待排序的数组</param> 5 public static void SimpleSelectSort(this int[] arry) 6 { 7 int tmp = 0; 8 int t = 0;//最小数标记 9 for (int i = 0; i < arry.Length; i++) 10 { 11 t = i; 12 for (int j = i + 1; j < arry.Length; j++) 13 { 14 if (arry[t] > arry[j]) 15 { 16 t = j; 17 } 18 } 19 tmp = arry[i]; 20 arry[i] = arry[t]; 21 arry[t] = tmp; 22 } 23 }
测试代码:
1 static void Main(string[] args) 2 { 3 int[] arry = new int[] { 34,1,221,50,44,58,12}; 4 //arry.BubbleSort(); 5 //arry.QuickSort(0, arry.Length-1 ); 6 //arry.InsertSort(); 7 //arry.ShellSort(); 8 arry.SimpleSelectSort(); 9 for (int i = 0; i < arry.Length; i++) 10 { 11 Console.Write(" " + arry[i]); 12 } 13 Console.Read(); 14 }
输出:1 12 34 44 50 58 221
3.2、堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。
注意:如果想升序排序就使用大顶堆,反之使用小顶堆。原因是堆顶元素需要交换到序列尾部。
实现代码:
1 /// <summary> 2 /// 堆排序 3 /// </summary> 4 /// <param name="arry"></param> 5 public static void HeapSort(this int[] arry, int top) 6 { 7 List<int> topNode = new List<int>(); 8 9 for (int i = arry.Length / 2 - 1; i >= 0; i--) 10 { 11 HeapAdjust(arry, i, arry.Length); 12 } 13 14 for (int i = arry.Length - 1; i >= arry.Length - top; i--) 15 { 16 int temp = arry[0]; 17 arry[0] = arry[i]; 18 arry[i] = temp; 19 HeapAdjust(arry, 0, i); 20 } 21 } 22 /// <summary> 23 /// 构建堆 24 /// </summary> 25 /// <param name="arry"></param> 26 /// <param name="parent"></param> 27 /// <param name="length"></param> 28 private static void HeapAdjust(int[] arry, int parent, int length) 29 { 30 int temp = arry[parent]; 31 32 int child = 2 * parent + 1; 33 34 while (child < length) 35 { 36 if (child + 1 < length && arry[child] < arry[child + 1]) child++; 37 38 if (temp >= arry[child]) 39 break; 40 41 arry[parent] = arry[child]; 42 43 parent = child; 44 45 child = 2 * parent + 1; 46 } 47 48 arry[parent] = temp; 49 }
测试代码:
1 static void Main(string[] args) 2 { 3 int[] arry = new int[] { 34,1,221,50,44,58,12}; 4 //arry.BubbleSort(); 5 //arry.QuickSort(0, arry.Length-1 ); 6 //arry.InsertSort(); 7 //arry.ShellSort(); 8 //arry.SimpleSelectSort(); 9 arry.HeapSort(arry.Length); 10 for (int i = 0; i < arry.Length; i++) 11 { 12 Console.Write(" " + arry[i]); 13 } 14 Console.Read(); 15 }
输出:1 12 34 44 50 58 221
例图:
49,38,65,97,76,13,27,49序列的堆排序建初始堆和调整的过程如下:
3.3、归并排序
归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。
归并操作(merge),也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。
如 设有数列{6,202,100,301,38,8,1}
初始状态:6,202,100,301,38,8,1
第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;
第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;
第三次归并后:{1,6,8,38,100,202,301},比较次数:4;
总的比较次数为:3+4+4=11,;
逆序数为14;
归并操作的工作原理如下:
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
实现代码:
1 /// <summary> 2 /// 归并排序 3 /// </summary> 4 /// <param name="arry"></param> 5 /// <param name="first"></param> 6 /// <param name="last"></param> 7 public static void MergeSort(this int[] arry, int first, int last) 8 { 9 if (first + 1 < last) 10 { 11 int mid = (first + last) / 2; 12 MergeSort(arry, first, mid); 13 MergeSort(arry, mid, last); 14 Merger(arry, first, mid, last); 15 } 16 } 17 /// <summary> 18 /// 归并 19 /// </summary> 20 /// <param name="arry"></param> 21 /// <param name="first"></param> 22 /// <param name="mid"></param> 23 /// <param name="last"></param> 24 private static void Merger(int[] arry, int first, int mid, int last) 25 { 26 Queue<int> tempV = new Queue<int>(); 27 int indexA, indexB; 28 //设置indexA,并扫描subArray1 [first,mid] 29 //设置indexB,并扫描subArray2 [mid,last] 30 indexA = first; 31 indexB = mid; 32 //在没有比较完两个子标的情况下,比较 v[indexA]和v[indexB] 33 //将其中小的放到临时变量tempV中 34 while (indexA < mid && indexB < last) 35 { 36 if (arry[indexA] < arry[indexB]) 37 { 38 tempV.Enqueue(arry[indexA]); 39 indexA++; 40 } 41 else 42 { 43 tempV.Enqueue(arry[indexB]); 44 indexB++; 45 } 46 } 47 //复制没有比较完子表中的元素 48 while (indexA < mid) 49 { 50 tempV.Enqueue(arry[indexA]); 51 indexA++; 52 } 53 while (indexB < last) 54 { 55 tempV.Enqueue(arry[indexB]); 56 indexB++; 57 } 58 int index = 0; 59 while (tempV.Count > 0) 60 { 61 arry[first + index] = tempV.Dequeue(); 62 index++; 63 } 64 }
测试代码:
1 static void Main(string[] args) 2 { 3 int[] arry = new int[] { 34,1,221,50,44,58,12}; 4 //arry.BubbleSort(); 5 //arry.QuickSort(0, arry.Length-1 ); 6 //arry.InsertSort(); 7 //arry.ShellSort(); 8 //arry.SimpleSelectSort(); 9 //arry.HeapSort(arry.Length); 10 arry.MergeSort(0, arry.Length); 11 for (int i = 0; i < arry.Length; i++) 12 { 13 Console.Write(" " + arry[i]); 14 } 15 Console.Read(); 16 }
输出:1 12 34 44 50 58 221
图例:
3.4、基数排序
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
实现代码:
1 /// <summary> 2 /// 基数排序 3 /// 约定:待排数字中没有0,如果某桶内数字为0则表示该桶未被使用,输出时跳过即可 4 /// </summary> 5 /// <param name="arry">待排数组</param> 6 /// <param name="array_x">桶数组第一维长度</param> 7 /// <param name="array_y">桶数组第二维长度</param> 8 public static void RadixSort(this int[] arry, int array_x = 10, int array_y = 100) 9 { 10 /* 最大数字不超过999999999...(array_x个9) */ 11 for (int i = 0; i < array_x; i++) 12 { 13 int[,] bucket = new int[array_x, array_y]; 14 foreach (var item in arry) 15 { 16 int temp = (item / (int)Math.Pow(10, i)) % 10; 17 for (int l = 0; l < array_y; l++) 18 { 19 if (bucket[temp, l] == 0) 20 { 21 bucket[temp, l] = item; 22 break; 23 } 24 } 25 } 26 for (int o = 0, x = 0; x < array_x; x++) 27 { 28 for (int y = 0; y < array_y; y++) 29 { 30 if (bucket[x, y] == 0) continue; 31 arry[o++] = bucket[x, y]; 32 } 33 } 34 } 35 36 }
测试代码:
1 static void Main(string[] args) 2 { 3 int[] arry = new int[] { 34,1,221,50,44,58,12}; 4 //arry.BubbleSort(); 5 //arry.QuickSort(0, arry.Length-1 ); 6 //arry.InsertSort(); 7 //arry.ShellSort(); 8 //arry.SimpleSelectSort(); 9 //arry.HeapSort(arry.Length); 10 //arry.MergeSort(0, arry.Length); 11 arry.RadixSort(); 12 for (int i = 0; i < arry.Length; i++) 13 { 14 Console.Write(" " + arry[i]); 15 } 16 Console.Read(); 17 }
输出:1 12 34 44 50 58 221
图例:
各排序算法时间复杂度与空间复杂度
补充:
1、整数数组各种排序算法扩展类
1 /// <summary> 2 /// 排序辅助类 3 /// </summary> 4 public static class SortExtention 5 { 6 /// <summary> 7 /// 冒泡排序 8 /// </summary> 9 /// <param name="arry">要排序的整数数组</param> 10 public static void BubbleSort(this int[] arry) 11 { 12 for (int i = 0; i < arry.Length; i++) 13 { 14 for (int j = 0; j < arry.Length - 1 - i; j++) 15 { 16 //比较相邻的两个元素,如果前面的比后面的大,则交换位置 17 if (arry[j] > arry[j + 1]) 18 { 19 int temp = arry[j + 1]; 20 arry[j + 1] = arry[j]; 21 arry[j] = temp; 22 } 23 } 24 } 25 } 26 /// <summary> 27 /// 快速排序 28 /// </summary> 29 /// <param name="arry">要排序的数组</param> 30 /// <param name="left">低位</param> 31 /// <param name="right">高位</param> 32 public static void QuickSort(this int[] arry, int left, int right) 33 { 34 //左边索引小于右边,则还未排序完成 35 if (left < right) 36 { 37 //取中间的元素作为比较基准,小于他的往左边移,大于他的往右边移 38 int middle = arry[(left + right) / 2]; 39 int i = left - 1; 40 int j = right + 1; 41 while (true) 42 { 43 //移动下标,左边的往右移动,右边的向左移动 44 while (arry[++i] < middle && i < right) ; 45 while (arry[--j] > middle && j > 0) ; 46 if (i >= j) 47 break; 48 //交换位置 49 int number = arry[i]; 50 arry[i] = arry[j]; 51 arry[j] = number; 52 53 } 54 QuickSort(arry, left, i - 1); 55 QuickSort(arry, j + 1, right); 56 } 57 } 58 /// <summary> 59 /// 直接插入排序 60 /// </summary> 61 /// <param name="arry">要排序的数组</param> 62 public static void InsertSort(this int[] arry) 63 { 64 //直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的 65 for (int i = 1; i < arry.Length; i++) 66 { 67 //如果当前元素小于其前面的元素 68 if (arry[i] < arry[i - 1]) 69 { 70 //用一个变量来保存当前待比较的数值,因为当一趟比较完成时,我们要将待比较数值置入比它小的数值的后一位 71 int temp = arry[i]; 72 int j = 0; 73 for (j = i - 1; j >= 0 && temp < arry[j]; j--) 74 { 75 arry[j + 1] = arry[j]; 76 } 77 arry[j + 1] = temp; 78 } 79 } 80 } 81 /// <summary> 82 /// 希尔排序 83 /// </summary> 84 /// <param name="arry">待排序的数组</param> 85 public static void ShellSort(this int[] arry) 86 { 87 int length = arry.Length; 88 for (int h = length / 2; h > 0; h = h / 2) 89 { 90 //here is insert sort 91 for (int i = h; i < length; i++) 92 { 93 int temp = arry[i]; 94 if (temp < arry[i - h]) 95 { 96 for (int j = 0; j < i; j += h) 97 { 98 if (temp < arry[j]) 99 { 100 temp = arry[j]; 101 arry[j] = arry[i]; 102 arry[i] = temp; 103 } 104 } 105 } 106 } 107 } 108 } 109 /// <summary> 110 /// 简单选择排序 111 /// </summary> 112 /// <param name="arry">待排序的数组</param> 113 public static void SimpleSelectSort(this int[] arry) 114 { 115 int tmp = 0; 116 int t = 0;//最小数标记 117 for (int i = 0; i < arry.Length; i++) 118 { 119 t = i; 120 for (int j = i + 1; j < arry.Length; j++) 121 { 122 if (arry[t] > arry[j]) 123 { 124 t = j; 125 } 126 } 127 tmp = arry[i]; 128 arry[i] = arry[t]; 129 arry[t] = tmp; 130 } 131 } 132 /// <summary> 133 /// 堆排序 134 /// </summary> 135 /// <param name="arry"></param> 136 public static void HeapSort(this int[] arry, int top) 137 { 138 List<int> topNode = new List<int>(); 139 140 for (int i = arry.Length / 2 - 1; i >= 0; i--) 141 { 142 HeapAdjust(arry, i, arry.Length); 143 } 144 145 for (int i = arry.Length - 1; i >= arry.Length - top; i--) 146 { 147 int temp = arry[0]; 148 arry[0] = arry[i]; 149 arry[i] = temp; 150 HeapAdjust(arry, 0, i); 151 } 152 } 153 /// <summary> 154 /// 构建堆 155 /// </summary> 156 /// <param name="arry"></param> 157 /// <param name="parent"></param> 158 /// <param name="length"></param> 159 private static void HeapAdjust(int[] arry, int parent, int length) 160 { 161 int temp = arry[parent]; 162 163 int child = 2 * parent + 1; 164 165 while (child < length) 166 { 167 if (child + 1 < length && arry[child] < arry[child + 1]) child++; 168 169 if (temp >= arry[child]) 170 break; 171 172 arry[parent] = arry[child]; 173 174 parent = child; 175 176 child = 2 * parent + 1; 177 } 178 179 arry[parent] = temp; 180 } 181 /// <summary> 182 /// 归并排序 183 /// </summary> 184 /// <param name="arry"></param> 185 /// <param name="first"></param> 186 /// <param name="last"></param> 187 public static void MergeSort(this int[] arry, int first, int last) 188 { 189 if (first + 1 < last) 190 { 191 int mid = (first + last) / 2; 192 MergeSort(arry, first, mid); 193 MergeSort(arry, mid, last); 194 Merger(arry, first, mid, last); 195 } 196 } 197 /// <summary> 198 /// 归并 199 /// </summary> 200 /// <param name="arry"></param> 201 /// <param name="first"></param> 202 /// <param name="mid"></param> 203 /// <param name="last"></param> 204 private static void Merger(int[] arry, int first, int mid, int last) 205 { 206 Queue<int> tempV = new Queue<int>(); 207 int indexA, indexB; 208 //设置indexA,并扫描subArray1 [first,mid] 209 //设置indexB,并扫描subArray2 [mid,last] 210 indexA = first; 211 indexB = mid; 212 //在没有比较完两个子标的情况下,比较 v[indexA]和v[indexB] 213 //将其中小的放到临时变量tempV中 214 while (indexA < mid && indexB < last) 215 { 216 if (arry[indexA] < arry[indexB]) 217 { 218 tempV.Enqueue(arry[indexA]); 219 indexA++; 220 } 221 else 222 { 223 tempV.Enqueue(arry[indexB]); 224 indexB++; 225 } 226 } 227 //复制没有比较完子表中的元素 228 while (indexA < mid) 229 { 230 tempV.Enqueue(arry[indexA]); 231 indexA++; 232 } 233 while (indexB < last) 234 { 235 tempV.Enqueue(arry[indexB]); 236 indexB++; 237 } 238 int index = 0; 239 while (tempV.Count > 0) 240 { 241 arry[first + index] = tempV.Dequeue(); 242 index++; 243 } 244 } 245 246 /// <summary> 247 /// 基数排序 248 /// 约定:待排数字中没有0,如果某桶内数字为0则表示该桶未被使用,输出时跳过即可 249 /// </summary> 250 /// <param name="arry">待排数组</param> 251 /// <param name="array_x">桶数组第一维长度</param> 252 /// <param name="array_y">桶数组第二维长度</param> 253 public static void RadixSort(this int[] arry, int array_x = 10, int array_y = 100) 254 { 255 /* 最大数字不超过999999999...(array_x个9) */ 256 for (int i = 0; i < array_x; i++) 257 { 258 int[,] bucket = new int[array_x, array_y]; 259 foreach (var item in arry) 260 { 261 int temp = (item / (int)Math.Pow(10, i)) % 10; 262 for (int l = 0; l < array_y; l++) 263 { 264 if (bucket[temp, l] == 0) 265 { 266 bucket[temp, l] = item; 267 break; 268 } 269 } 270 } 271 for (int o = 0, x = 0; x < array_x; x++) 272 { 273 for (int y = 0; y < array_y; y++) 274 { 275 if (bucket[x, y] == 0) continue; 276 arry[o++] = bucket[x, y]; 277 } 278 } 279 } 280 281 } 282 283 }
2、计数排序
虽然前面基于比较的排序的下限是O(nlogn)。但是确实也有线性时间复杂度的排序(写一个O(n)时间复杂度的排序算法),只不过有前提条件,就是待排序的数要满足一定的范围的整数,而且计数排序需要比较多的辅助空间。其基本思想是,用待排序的数作为计数数组的下标,统计每个数字的个数。然后依次输出即可得到有序序列。
实现代码:
1 public class CountSort { 2 3 public static void countSort(int[] arr) { 4 if(arr == null || arr.length == 0) 5 return ; 6 7 int max = max(arr); 8 9 int[] count = new int[max+1]; 10 Arrays.fill(count, 0); 11 12 for(int i=0; i<arr.length; i++) { 13 count[arr[i]] ++; 14 } 15 16 int k = 0; 17 for(int i=0; i<=max; i++) { 18 for(int j=0; j<count[i]; j++) { 19 arr[k++] = i; 20 } 21 } 22 23 } 24 25 public static int max(int[] arr) { 26 int max = Integer.MIN_VALUE; 27 for(int ele : arr) { 28 if(ele > max) 29 max = ele; 30 } 31 32 return max; 33 } 34 35 }
3、桶排序
桶排序算是计数排序的一种改进和推广,但桶排序要比计数排序复杂许多。
1 public class BucketSort { 2 3 public static void bucketSort(int[] arr) { 4 if(arr == null && arr.length == 0) 5 return ; 6 7 int bucketNums = 10; //这里默认为10,规定待排数[0,100) 8 List<List<Integer>> buckets = new ArrayList<List<Integer>>(); //桶的索引 9 10 for(int i=0; i<10; i++) { 11 buckets.add(new LinkedList<Integer>()); //用链表比较合适 12 } 13 14 //划分桶 15 for(int i=0; i<arr.length; i++) { 16 buckets.get(f(arr[i])).add(arr[i]); 17 } 18 19 //对每个桶进行排序 20 for(int i=0; i<buckets.size(); i++) { 21 if(!buckets.get(i).isEmpty()) { 22 Collections.sort(buckets.get(i)); //对每个桶进行快排 23 } 24 } 25 26 //还原排好序的数组 27 int k = 0; 28 for(List<Integer> bucket : buckets) { 29 for(int ele : bucket) { 30 arr[k++] = ele; 31 } 32 } 33 } 34 35 /** 36 * 映射函数 37 * @param x 38 * @return 39 */ 40 public static int f(int x) { 41 return x / 10; 42 } 43 44 }