我的笔记--排序
返回
示例代码
示例代码
示例代码
示例代码
示例代码
示例代码
示例代码
排序
比较排序常见的快速排序、归并排序、堆排序、冒泡排序;在排序结果中,元素之间的次序依赖于它们之间的比较,每个数都必须和其他数进行比较,才能确定自己的位置。
非比较排序常见的计数排序、基数排序、桶排序;通过确定每个元素之前应该有多少个元素来排序,针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置。
对比非比较排序时间复杂度低,但由于非比较排序需要占用空间来确定唯一位置,所以对数据规模和数据分布有一定的要求。
冒泡排序比较相邻的元素,如果前一个比后一个大,就交换他们两个。数值大的元素放到后面。一趟循环能确定未排序的数组中的最大值(大的元素往后缩)
1 public static int[] GetOrderArr(int[] arr) 2 { 3 if (arr.Length == 0) return arr; 4 for(var i = 0; i < arr.Length; i++) 5 { 6 for(var j = 0; j < arr.Length - 1 - i; j++) 7 { 8 var current = arr[j]; 9 var next = arr[j + 1]; 10 if (current > next) 11 { 12 var temp = arr[j]; 13 current = next; 14 next = temp; 15 arr[j] = current; 16 arr[j + 1] = next; 17 } 18 } 19 } 20 return arr; 21 }
选择排序最稳定的排序算法,首先在未排序数组中找到最小(大)的元素下标,存在到排序数组的起始位置,然后在从未排序的数组中继续找最小(大)元素下标,然后存放到已排序数组的末尾,以此类推。。。
1 public static int[] GetOrderArr(int[] arr) 2 { 3 if (arr.Length == 0) return arr; 4 for (var i = 0; i < arr.Length; i++) 5 { 6 var minIndex = i; 7 for (var j = i; j < arr.Length; j++) 8 if (arr[j] < arr[minIndex]) 9 minIndex = j; 10 var temp = arr[i]; 11 arr[i] = arr[minIndex]; 12 arr[minIndex] = temp; 13 } 14 return arr; 15 }
插入排序从第一个元素开始,该元素可以认为是已排序,取未排序数组的第一个元素和已排序的数组元素从后到前逐个比较,如果该元素小于已排序数组元素,则调换位置,直到该元素大于已排序数组元素,以此类推。。。(把未排序数组第一个元素作为已排序数组最后一个元素,用该元素向前逐一比较大小)
1 public static int[] GetOrderArr(int[] arr) 2 { 3 if (arr.Length == 0) return arr; 4 for (var i = 1; i < arr.Length; i++) 5 { 6 //arr[i]是未排序数组的第一个元素,可作为已排序数组的最后一个元素,将该元素向前逐一比较 7 for (var j = i; j > 0; j--) 8 { 9 if (arr[j] < arr[j - 1]) 10 { 11 var temp = arr[j]; 12 arr[j] = arr[j - 1]; 13 arr[j - 1] = temp; 14 } 15 } 16 } 17 return arr; 18 }
希尔排序希尔排序(缩小增量排序)也是一种插入排序,更高效的插入排序算法(未能理解,因为最后一次还要进行一次全新的插入排序,还不如直接进行插入排序呢);对原始数组进行分组(分组数是arr.length/2/2/2...,直到分组为1时,不要求arr.length必须是偶数,因为最后还要进行一次插入排序),对分组内元素进行插入排序。???
1 public static int[] GetOrderArr(int[] arr) 2 { 3 if (arr.Length == 0) return arr; 4 //获取初始增量(分组数) 5 var gap = arr.Length / 2; 6 //分组规则:第gap个元素和第gap-gap个元素比较或反过来比较 7 while (gap > 0) 8 { 9 //循环增量(分组数)次 10 for (var i = gap; i < arr.Length; i++) 11 { 12 //对每组元素进行插入排序 13 for (var j = i; j > 0; j--) 14 { 15 if (arr[j] < arr[j - 1]) 16 { 17 var temp = arr[j]; 18 arr[j] = arr[j - 1]; 19 arr[j - 1] = temp; 20 } 21 } 22 } 23 //增量减半 24 gap /= 2; 25 } 26 return arr; 27 }
归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案“修补”在一起,即分而治之)
1 public static int[] GetOrderArr(int[] arr) 2 { 3 var length = arr.Length; 4 if (length < 2) return arr; 5 var leftLength = length / 2; 6 var rightLength = length - leftLength; 7 int[] left = new int[leftLength]; 8 int[] right = new int[rightLength]; 9 for (var i = 0; i < length; i++) 10 { 11 if (i < leftLength) 12 left[i] = arr[i]; 13 else 14 right[i - leftLength] = arr[i]; 15 } 16 var result = Merge(GetOrderArr(left), GetOrderArr(right)); 17 return result; 18 } 19 20 public static int[] Merge(int[] left, int[] right) 21 { 22 int[] result = new int[left.Length + right.Length]; 23 var i = 0; 24 var j = 0; 25 for (int index = 0; index < result.Length; index++) 26 { 27 if (i >= left.Length) 28 result[index] = right[j++]; 29 else if (j >= right.Length) 30 result[index] = left[i++]; 31 else if (left[i] > right[j]) 32 result[index] = right[j++]; 33 else 34 result[index] = left[i++]; 35 } 36 return result; 37 }
快速排序通过一趟排序将待排记录分隔成独立的两部分,其中一份记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序(从数列中挑出一个元素,称为“基准”、重新排序 数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值到的摆在基准的后面,这个分区退出之后,该基准就处于数列的中间位置,这个称为分区操作;递归的把小于基准值元素的子数列和大于基准值元素的子数列排序)。
1 /// <summary> 2 /// 快速排序 3 /// </summary> 4 /// <param name="array"></param> 5 /// <param name="low">第一次是0</param> 6 /// <param name="high">第一次是array.length-1</param> 7 public static void sort(int[] array, int low, int high) 8 { 9 if (low >= high) 10 return; 11 /*完成一次单元排序*/ 12 int index = sortUnit(array, low, high); 13 /*对左边单元进行排序*/ 14 sort(array, low, index - 1); 15 /*对右边单元进行排序*/ 16 sort(array, index + 1, high); 17 } 18 private static int sortUnit(int[] array, int low, int high) 19 { 20 int key = array[low]; 21 while (low < high) 22 { 23 /*从后向前搜索比key小的值*/ 24 while (array[high] >= key && high > low) 25 --high; 26 /*比key小的放左边*/ 27 array[low] = array[high]; 28 /*从前向后搜索比key大的值,比key大的放右边*/ 29 while (array[low] <= key && high > low) 30 ++low; 31 /*比key大的放右边*/ 32 array[high] = array[low]; 33 } 34 /*左边都比key小,右边都比key大。//将key放在游标当前位置。//此时low等于high */ 35 array[low] = key; 36 return high; 37 }
堆排序堆排序是指利用堆这种数据结构所涉及的一种排序算法,堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或大于)它的父节点。
计数排序计数排序的核心在于将输入的数值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,基数排序要求输入的数据必须是有确定范围的整数。计数排序是一种稳定的排序算法,计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数据C来将A中的元素排到正确的位置。它只能对整数进行排序。
找出待排序的数组中最大和最小是元素;统计数组中每个值为i的元素出现的次数,存入数组C的第i项;对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);方向填充没押镖数组,将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
1 public static int[] CountingSort(int[] array) 2 { 3 if (array.Length == 0) return array; 4 int bias, min = array[0], max = array[0]; 5 //获取数组中最大值和最小值 6 for (var a = 1; a < array.Length; a++) 7 { 8 if (array[a] > max) 9 max = array[a]; 10 if (array[a] < min) 11 min = array[a]; 12 } 13 bias = 0 - min; 14 //创建最大值和最小值区间大小的数组 15 int[] bucket = new int[max - min + 1]; 16 //计算每个元素出现的次数 17 for (int j = 0; j < array.Length; j++) 18 bucket[array[j] + bias]++; 19 int index = 0, i = 0; 20 //回填数据组成有序数组 21 while (index < array.Length) 22 { 23 if (bucket[i] != 0) 24 { 25 array[index] = i - bias; 26 bucket[i]--; 27 index++; 28 } 29 else 30 i++; 31 } 32 return array; 33 }
桶排序桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排。
算法描述:
- 人为设置一个BucketSize,作为每个桶所能放置多少个不同数值(例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3);
- 遍历输入数据,并且把数据一个一个放到对应的桶里去;
- 对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;
- 从不是空的桶里把排好序的数据拼接起来。
注意,如果递归使用桶排序为各个桶排序,则当桶数量为1时要手动减小BucketSize增加下一循环桶的数量,否则会陷入死循环,导致内存溢出。
基数排序基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),为数组长度,k为数组中的数的最大的位数;基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
算法描述:
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点);
分别取出个位数、十位数、百位数进行排序。