Java中常见的排序方法
本博主要介绍Java中几种常见的排序算法;
/*
排序方法的演示
1)插入排序(直接插入排序、希尔排序)
2)交换排序(冒泡排序、快速排序)
3)选择排序(直接选择排序、堆排序)
4)归并排序
5)分配排序(基数排序)
所需辅助空间最多:归并排序
所需辅助空间最少:堆排序
平均速度最快:快速排序
不稳定:快速排序,希尔排序,堆排序。
*/
其中,文字部分来自于网上整理,代码部分属于自己实现的(堆排序,归并排序,基数排序代码来自网上),主要用于自己学习,有空的时候翻翻老笔记看看
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1.插入排序
1.1.基本思想
直接插入排序的基本操作是将一个记录插入到已经排好的有序表中,从而得到一个新的、记录数增1的有序表。对于给定的一组记录,初始时假定第一个记录自成一个有序序列,其余记录为无序序列。接着从第二个记录开始,按照记录的大小依次将当前处理的记录插入到其之前的有序序列中,直到最后一个记录插到有序序列中为止。
1.2.复杂度分析
当最好的情况,也就是要排序的表本身就是有序的,此时只有数据比较,没有数据移动,时间复杂度为O(n)。
当最坏的情况,即待排序的表是逆序的情况,此时需要比较次数为:2+3+…+n=(n+2)(n-1)/2 次,而记录移动的最大值也达到了 (n+4)(n-1)/2 次.
如果排序记录是随机的,那么根据概率相同的原则,平均比较和移动次数约为次n2/4,因此,得出直接插入排序发的时间复杂度为。从这里可以看出,同样的是时间复杂度,直接插入排序法比冒泡和简单选择排序的性能要好一些。
1.3.java实现
1 package MySort; 2 3 import java.util.Arrays; 4 5 public class MySortTest2 { 6 7 public static void main(String[] args) { 8 // 原始数据 9 int[] data1 = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 10 // 1.插入排序:直接插入排序 11 System.out.println("插入排序:\t" + Arrays.toString(insertSort(data1))); 12 } 13 14 // 1.插入排序:直接插入排序 15 private static int[] insertSort(int[] data) { 16 for (int i = 1; i < data.length; i++) { 17 int insertData = data[i]; // 要插入到数组中的数据 18 int j = i - 1; // 临时脚标 19 while (j >= 0 && insertData < data[j]) { 20 data[j + 1] = data[j];// 将原始数据后移 21 j--; 22 } 23 data[j + 1] = insertData; // 将如要插入的数据插入到此处 24 } 25 return data; 26 } 27 28 }
2.希尔排序
2.1.基本思想
希尔排序也成为“缩小增量排序”,其基本原理是,现将待排序的数组元素分成多个子序列,使得每个子序列的元素个数相对较少,然后对各个子序列分别进行直接插入排序,待整个待排序列“基本有序”后,最后在对所有元素进行一次直接插入排序。因此,我们要采用跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。希尔排序是对直接插入排序算法的优化和升级。
所谓的基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间,例如{2,1,3,6,4,7,5,8,9,}就可以称为基本有序了。但像{1,5,9,3,7,8,2,4,6}这样,9在第三位,2在倒数第三位就谈不上基本有序。
2.2.复杂度分析
希尔排序的关键并不是随便分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式移动,使得排序的效率提高。需要注意的是,增量序列的最后一个增量值必须等于1才行。另外,由于记录是跳跃式的移动,希尔排序并不是一种稳定的排序算法。
希尔排序最好时间复杂度和平均时间复杂度都是,最坏时间复杂度为。
2.3.java实现
1 package MySort; 2 3 import java.util.Arrays; 4 5 public class MySortTest3 { 6 7 public static void main(String[] args) { 8 // 原始数据 9 int[] data = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 10 // 1.插入排序:希尔排序 11 System.out.println("希尔排序:\t" + Arrays.toString(shellSort(data))); 12 13 } 14 15 // 1.插入排序:希尔排序 16 private static int[] shellSort(int[] data) { 17 // 划分组 18 for (int r = data.length / 2; r >= 1; r /= 2) { 19 // 对每一组进行插入排序 20 for (int i = r; i < data.length; i++) { 21 int insertData = data[i];// 插入的数据 22 int j = i - r;// 临时序号 23 while (j >= 0 && data[j] < insertData) { 24 data[j + r] = data[j]; 25 j -= r; 26 } 27 data[j + r] = insertData; 28 } 29 } 30 return data; 31 } 32 33 }
3.冒泡排序
3.1.基本思想
依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。至此第一趟结束,将最大的数放到了最后。在第二趟:仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到倒数第二个数(倒数第一的位置上已经是最大的),第二趟结束,在倒数第二的位置上得到一个新的最大数(其实在整个数列中是第二大的数)。如此下去,重复以上过程,直至最终完成排序。
用二重循环实现,外循环变量设为i,内循环变量设为j。假如有n个数需要进行排序,则外循环重复n-1次,内循环依次重复n-1,n-2,...,1次。每次进行比较的两个元素都是与内循环j有关的,它们可以分别用a[j]和a[j+1]标识,i的值依次为1,2,...,n-1,对于每一个i,j的值依次为0,1,2,...n-i 。
设数组长度为N:
1.比较相邻的前后二个数据,如果前面数据大于后面的数据,就将二个数据交换。
2.这样对数组的第0个数据到N-1个数据进行一次遍历后,最大的一个数据就“沉”到数组第N-1个位置。
3.N=N-1,如果N不为0就重复前面二步,否则排序完成。
3.2.复杂度分析
时间复杂度分析。其外层循环执行 N - 1次。内层循环最多的时候执行N次,最少的时候执行1次,平均执行 (N+1)/2
次。
所以循环体内的比较交换约执行 (N - 1)(N + 1) / 2 = (N^2 - 1)/2
(其中N^2
是仿照Latex中的记法,表示N的平方)。按照计算复杂度的原则,去掉常数,去掉最高项系数,其复杂度为O(N^2)
。
3.3.java实现
1 package MySort; 2 3 import java.util.Arrays; 4 5 public class MySortTest4 { 6 7 public static void main(String[] args) { 8 // 原始数据 9 int[] data = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 10 11 // 2.交换排序:冒泡排序 12 System.out.println("冒泡排序:\t" + Arrays.toString(bubbleSort(data))); 13 14 } 15 16 // 2.交换排序:冒泡排序 17 private static int[] bubbleSort(int[] data) { 18 // 冒泡次数 19 for (int i = 0; i < data.length; i++) { 20 // 冒泡 21 for (int j = 0; j < data.length - i - 1; j++) { 22 if (data[j] > data[j + 1]) { 23 int temp = data[j]; 24 data[j] = data[j + 1]; 25 data[j + 1] = temp; 26 } 27 } 28 } 29 return data; 30 } 31 32 }
4.快速排序
4.1.基本思想
快速排序是我们之前学习的冒泡排序的升级,他们都属于交换类排序,都是采用不断的比较和移动来实现排序的。快速排序是一种非常高效的排序算法,它的实现,增大了记录的比较和移动的距离,将关键字较大的记录从前面直接移动到后面,关键字较小的记录从后面直接移动到前面,从而减少了总的比较次数和移动次数。同时采用“分而治之”的思想,把大的拆分为小的,小的拆分为更小的,其原理如下:对于给定的一组记录,选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分,直到序列中的所有记录均有序为止。
4.2.复杂度分析
(1)最坏时间复杂度
最坏情况是指每次区间划分的结果都是基准关键字的左边(或右边)序列为空,而另一边区间中的记录仅比排序前少了一项,即选择的关键字是待排序记录的最小值或最大值。最坏情况下快速排序的时间复杂度为。
(2)最好时间复杂度
最好情况是指每次区间划分的结果都是基准关键字的左右两边长度相等或者相差为1,即选择的基准关键字为待排序的记录的中间值。此时进行比较次数总共为 nlogn,所以最好情况下快速排序的时间复杂度为。
(3)平均时间复杂度
快速排序的平均时间复杂度为。在所有平均时间复杂度为O(nlogn)的算法中,快速排序的平均性能是最好的。
(4)空间复杂度
快速排序的过程中需要一个栈空间来实现递归。最好情况,递归树的深度为,其空间复杂度也就是O(nlogn);最坏情况下,需要进行 n-1次递归,其空间复杂度为O(n);平均情况,空间复杂度为O(nlogn).
(5)基准关键字的选取,基准关键字的选取是决定快速排序算法的关键,常用的基准关键字的选取方式如下:
第一种:三者取中。将序列首、尾和中间位置上的记录进行比较,选择三者中值作为基准关键字。
第二种:取left和right之间的一个随机数,用n[m]作为基准关键字。采用这种方法得到的快速排序一般称为随机的快速排序。
4.3.java实现
1 package MySort; 2 3 import java.util.Arrays; 4 5 public class MySortTest5 { 6 7 public static void main(String[] args) { 8 // 原始数据 9 int[] data = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 10 11 // 2.交换排序:快速排序 12 System.out.println("快速排序:\t" + Arrays.toString(quickSort(data, 0, data.length - 1))); 13 14 } 15 16 // 2.交换排序:快速排序 17 private static int[] quickSort(int[] data, int low, int high) { 18 if (low < high) { 19 int middle = getMiddle(data, low, high);// 获取中间值 20 // 对两端的再次进行排序 21 quickSort(data, low, middle - 1); 22 quickSort(data, middle + 1, high); 23 24 } 25 return data; 26 } 27 28 // 快速排序:获取中间值 29 private static int getMiddle(int[] data, int low, int high) { 30 // 以第一个元素为基准 31 int temp = data[low]; 32 while (low < high) { 33 while (low < high && data[high] > temp) { 34 high--; 35 } 36 data[low] = data[high]; 37 while (low < high && data[low] < temp) { 38 low++; 39 } 40 data[high] = data[low]; 41 } 42 data[low] = temp; 43 return low; 44 } 45 46 }
5.选择排序
5.1.基本思想
选择排序是一种简单直观的排序算法,其基本原理如下:对于给定的一组记录,经过第一轮比较后得到最小的记录,然后将该记录的位置与第一个记录的位置交换;接着对不包括第一个记录以外的其他记录进行第二次比较,得到最小记录并与第二个位置记录交换;重复该过程,知道进行比较的记录只剩下一个为止。
5.2.复杂度分析
从简单选择排序的过程来看,它最大的特点是交换移动数据次数相当少,这样就节约了相应的时间。分析它的时间复杂度发现,无论是最好最差情况,其比较次数都是一样多,第 i 趟排序需要进行 n-i 次关键字比较,此时需要比较次,对于交换次数而言,当最好的时候,交换0次,最差的时候,也就是初始降时,交换次数为 n-1 次,基于最终的时间排序与交换次数总和,因此,总的时间复杂度依然为。
尽管与冒泡排序同为,但简单选择排序的性能要优于冒泡排序。
5.3.java实现
1 package MySort; 2 3 import java.util.Arrays; 4 5 public class MySortTest6 { 6 7 public static void main(String[] args) { 8 // 原始数据 9 int[] data = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 10 11 // 3.选择排序:直接选择排序 12 System.out.println("直接排序:\t" + Arrays.toString(chooseSort(data))); 13 14 } 15 16 // 3.选择排序:直接选择排序 17 private static int[] chooseSort(int[] data) { 18 // 循环次数 19 for (int i = 0; i < data.length; i++) { 20 // 逐个比较 21 for (int j = i + 1; j < data.length; j++) { 22 if (data[i] > data[j]) { 23 int temp = data[i]; 24 data[i] = data[j]; 25 data[j] = temp; 26 } 27 } 28 } 29 30 return data; 31 32 } 33 34 }
6.堆排序
6.1.基本思想
堆排序就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的 n-1 个序列重新构造成一个堆,这样就会得到 n 个元素中次大的值。如此反复执行,便能得到一个有序序列了。
堆排序的实现需要解决的两个关键问题:
(1)将一个无序序列构成一个堆。
(2)输出堆顶元素后,调整剩余元素成为一个新堆。
堆排序的实现:
① 初始化操作:将R[1..n]构造为初始堆;
②每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。
注意:
①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。
②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。
6.2.复杂度分析
堆排序的运行时间主要耗费在初始构建堆和在重建堆时反复筛选上。在构建对的过程中,因为我们是完全二叉树从最下层最右边的非终端节点开始构建,将它与其孩子进行比较和若有必要的互换,对每个非终端节点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。
在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个节点到根节点的距离为),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)。
所以总体来说,堆排序的时间复杂度为O(nlogn),由于堆排序对原始记录的状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的时间复杂度了。
空间复杂度上,它只有一个用来交换的暂存单元,也非常的不错。不过由于记录的比较与交换是跳跃式进行的,因此堆排序也是一种不稳定的排序方法。
另外,由于出事构建堆所需要的比较次数比较多,因此,他并不适合待排序序列个数较少的情况。
6.3.java实现
1 package MySort; 2 3 import java.util.Arrays; 4 5 public class MySortTest7 { 6 7 public static void main(String[] args) { 8 9 int[] data = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 10 11 // 3.选择排序:堆排序 12 System.out.println("堆排序:\t" + Arrays.toString(heapSort(data))); 13 14 } 15 16 private static int[] heapSort(int[] data) { 17 data = buildMaxHeap(data); // 初始建堆,array[0]为第一趟值最大的元素 18 for (int i = data.length - 1; i > 1; i--) { 19 int temp = data[0]; // 将堆顶元素和堆低元素交换,即得到当前最大元素正确的排序位置 20 data[0] = data[i]; 21 data[i] = temp; 22 adjustDownToUp(data, 0, i); // 整理,将剩余的元素整理成堆 23 } 24 return data; 25 } 26 27 // 构建大根堆:将array看成完全二叉树的顺序存储结构 28 private static int[] buildMaxHeap(int[] array) { 29 // 从最后一个节点array.length-1的父节点(array.length-1-1)/2开始,直到根节点0,反复调整堆 30 for (int i = (array.length - 2) / 2; i >= 0; i--) { 31 adjustDownToUp(array, i, array.length); 32 } 33 return array; 34 } 35 36 // 将元素array[k]自下往上逐步调整树形结构 37 private static void adjustDownToUp(int[] array, int k, int length) { 38 int temp = array[k]; 39 for (int i = 2 * k + 1; i < length - 1; i = 2 * i + 1) { // i为初始化为节点k的左孩子,沿节点较大的子节点向下调整 40 if (i < length && array[i] < array[i + 1]) { // 取节点较大的子节点的下标 41 i++; // 如果节点的右孩子>左孩子,则取右孩子节点的下标 42 } 43 if (temp >= array[i]) { // 根节点 >=左右子女中关键字较大者,调整结束 44 break; 45 } else { // 根节点 <左右子女中关键字较大者 46 array[k] = array[i]; // 将左右子结点中较大值array[i]调整到双亲节点上 47 k = i; // 【关键】修改k值,以便继续向下调整 48 } 49 } 50 array[k] = temp; // 被调整的结点的值放人最终位置 51 } 52 53 }
7.归并排序
7.1.基本思想
归并排序就是利用归并的思想实现的排序方法。而且充分利用了完全二叉树的深度是的特性,因此效率比较高。其基本原理如下:对于给定的一组记录,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序,最后再用递归方法将排好序的半子表合并成为越来越大的有序序列。
经过第一轮比较后得到最小的记录,然后将该记录的位置与第一个记录的位置交换;接着对不包括第一个记录以外的其他记录进行第二次比较,得到最小记录并与第二个位置记录交换;重复该过程,知道进行比较的记录只剩下一个为止。
7.2.复杂度分析
一趟归并需要将数组 a[]中相邻的长度为h的有序序列进行两两归并.并将结果放到temp[]中,这需要将待排序列中的所有记录扫描一遍,因此耗费O(n),而又完全二叉树的深度可知,整个归并排序需要进行()次,因此总的时间复杂度为O(nlogn),而且这是归并排序算法中最好、最坏、平均的时间性能。
由于归并排序在归并过程中需要与原始序列同样数量的存储空间存放归并结果以及递归时深度为的栈空间,因此空间复杂度为O(n+logn).
另外,对代码进行仔细研究,发现merge函数中有if (a[i] < a[j]) 的语句,说明它需要两两比较,不存在跳跃,因此归并排序是一种稳定的排序算法。
也就是说,归并排序是一种比较占内存,但却效率高且稳定的算法。
7.3.工作原理
(1)申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
(2)设定两个指针,最初位置分别为两个已经排序序列的起始位置
(3)比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
(4)重复步骤3直到某一指针达到序列尾
(5)将另一序列剩下的所有元素直接复制到合并序列尾
7.4.java实现
1 package MySort; 2 3 import java.util.Arrays; 4 5 public class MySortTest8 { 6 7 public static void main(String[] args) { 8 9 int[] data = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 10 11 // 4.归并排序 12 mergeSort(data, 0, data.length - 1); 13 System.out.println("归并排序:\t" + Arrays.toString(data)); 14 15 } 16 17 private static void mergeSort(int[] data, int low, int high) { 18 int mid = (low + high) / 2; 19 if (low < high) { 20 // 左边 21 mergeSort(data, low, mid); 22 // 右边 23 mergeSort(data, mid + 1, high); 24 // 左右归并 25 merge(data, low, mid, high); 26 27 } 28 29 } 30 31 public static void merge(int[] data, int low, int mid, int high) { 32 int[] temp = new int[high - low + 1]; 33 int i = low;// 左指针 34 int j = mid + 1;// 右指针 35 int k = 0; 36 // 把较小的数先移到新数组中 37 while (i <= mid && j <= high) { 38 if (data[i] < data[j]) { 39 temp[k++] = data[i++]; 40 } else { 41 temp[k++] = data[j++]; 42 } 43 } 44 // 把左边剩余的数移入数组 45 while (i <= mid) { 46 temp[k++] = data[i++]; 47 } 48 // 把右边边剩余的数移入数组 49 while (j <= high) { 50 temp[k++] = data[j++]; 51 } 52 // 把新数组中的数覆盖nums数组 53 for (int k2 = 0; k2 < temp.length; k2++) { 54 data[k2 + low] = temp[k2]; 55 } 56 } 57 58 }
8.基数排序
8.1.基本思想
像选择排序、插入排序、快速排序等都是基于两个元素的比较进行排序的。而基数排序无需进行元素比较,基于队列处理就能够达到排序的目的。
基数排序不是基于排序关键字来比较排序项,而是基于排序关键字的结构。对于排序关键字中的每一个数字或字符的每一种可能取值,都会创建一个单独的队列。队列的数目就称为基数。
例如:要排序全部由小写字母组成的字符串,则基数就是26,就会用到26个单独的队列。如果对十进制数进行排序,则基数应该是10.
8.2.复杂度分析
在基数排序中,没有任何元素的比较和交换,元素只是在每一轮中从一个队列移动到另一个队列。对于给定的基数,遍历数据的轮次是一个常数,它与排序关键字的数目无关,于是,基数排序算法的时间复杂度为O(n)
1.基数排序算法要根据给定问题特别设计;
2.如果排序关键字中的数字数目与列表中元素的数目接近,那么算法的时间复杂度接近O(n平方);
3.基数影响空间复杂度。
8.3.java实现
1 package MySort; 2 3 import java.util.Arrays; 4 5 public class MySortTest9 { 6 7 public static void main(String[] args) { 8 9 int[] data = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 10 11 // 5.分配排序(基数排序) 12 System.out.println("基数排序:\t" + Arrays.toString(radixSort(data, getMaxWeishu(data)))); 13 } 14 15 // 求得最大位数d 16 public static int getMaxWeishu(int[] a) { 17 int max = a[0]; 18 for (int i = 0; i < a.length; i++) { 19 if (a[i] > max) 20 max = a[i]; 21 } 22 int tmp = 1, d = 1; 23 while (true) { 24 tmp *= 10; 25 if (max / tmp != 0) { 26 d++; 27 } else 28 break; 29 } 30 return d; 31 } 32 33 // pos=1表示个位,pos=2表示十位 34 public static int getNumInPos(int num, int pos) { 35 int tmp = 1; 36 for (int i = 0; i < pos - 1; i++) { 37 tmp *= 10; 38 } 39 return (num / tmp) % 10; 40 } 41 42 private static int[] radixSort(int[] data, int d) { 43 int[][] array = new int[10][data.length + 1]; 44 for (int i = 0; i < 10; i++) { 45 array[i][0] = 0; 46 // array[i][0]记录第i行数据的个数 47 } 48 for (int pos = 1; pos <= d; pos++) { 49 for (int i = 0; i < data.length; i++) { 50 // 分配过程 51 int row = getNumInPos(data[i], pos); 52 int col = ++array[row][0]; 53 array[row][col] = data[i]; 54 } 55 for (int row = 0, i = 0; row < 10; row++) { 56 // 收集过程 57 for (int col = 1; col <= array[row][0]; col++) { 58 data[i++] = array[row][col]; 59 } 60 array[row][0] = 0; 61 // 复位,下一个pos时还需使用 62 } 63 } 64 return data; 65 } 66 67 }
上述文字均来自网络的整理,接下来我会慢慢整理,根据个人理解优化文字部分;
以下为上述代码的合集
1 package MySort; 2 3 import java.util.Arrays; 4 5 /* 6 上述文字均来自网络的整理,接下来我会慢慢整理,根据个人理解优化文字部分; 7 以下为上述代码的合集 8 */ 9 public class MySortTest { 10 11 public static void main(String[] args) { 12 // 原始数据 13 int[] data1 = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 14 int[] data2 = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 15 int[] data3 = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 16 int[] data4 = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 17 int[] data5 = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 18 int[] data6 = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 19 int[] data7 = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 20 int[] data8 = { 1, 3, 6, 2, 4, 8, 9, 5, 12 }; 21 // 1.插入排序:直接插入排序 22 System.out.println("插入排序:\t" + Arrays.toString(insertSort(data1))); 23 // 1.插入排序:希尔排序 24 System.out.println("希尔排序:\t" + Arrays.toString(shellSort(data2))); 25 // 2.交换排序:冒泡排序 26 System.out.println("冒泡排序:\t" + Arrays.toString(bubbleSort(data3))); 27 // 2.交换排序:快速排序 28 System.out.println("快速排序:\t" + Arrays.toString(quickSort(data4, 0, data4.length - 1))); 29 // 3.选择排序:直接选择排序 30 System.out.println("直接排序:\t" + Arrays.toString(chooseSort(data5))); 31 // 3.选择排序:堆排序 32 System.out.println("堆排序:\t" + Arrays.toString(heapSort(data6))); 33 // 4.归并排序 34 mergeSort(data7, 0, data7.length - 1); 35 System.out.println("归并排序:\t" + Arrays.toString(data7)); 36 // 5.分配排序(基数排序) 37 System.out.println("基数排序:\t" + Arrays.toString(radixSort(data8, getMaxWeishu(data8)))); 38 } 39 40 // 求得最大位数d 41 public static int getMaxWeishu(int[] a) { 42 int max = a[0]; 43 for (int i = 0; i < a.length; i++) { 44 if (a[i] > max) 45 max = a[i]; 46 } 47 int tmp = 1, d = 1; 48 while (true) { 49 tmp *= 10; 50 if (max / tmp != 0) { 51 d++; 52 } else 53 break; 54 } 55 return d; 56 } 57 58 // pos=1表示个位,pos=2表示十位 59 public static int getNumInPos(int num, int pos) { 60 int tmp = 1; 61 for (int i = 0; i < pos - 1; i++) { 62 tmp *= 10; 63 } 64 return (num / tmp) % 10; 65 } 66 67 private static int[] radixSort(int[] data, int d) { 68 int[][] array = new int[10][data.length + 1]; 69 for (int i = 0; i < 10; i++) { 70 array[i][0] = 0; 71 // array[i][0]记录第i行数据的个数 72 } 73 for (int pos = 1; pos <= d; pos++) { 74 for (int i = 0; i < data.length; i++) { 75 // 分配过程 76 int row = getNumInPos(data[i], pos); 77 int col = ++array[row][0]; 78 array[row][col] = data[i]; 79 } 80 for (int row = 0, i = 0; row < 10; row++) { 81 // 收集过程 82 for (int col = 1; col <= array[row][0]; col++) { 83 data[i++] = array[row][col]; 84 } 85 array[row][0] = 0; 86 // 复位,下一个pos时还需使用 87 } 88 } 89 return data; 90 } 91 92 private static void mergeSort(int[] data, int low, int high) { 93 int mid = (low + high) / 2; 94 if (low < high) { 95 // 左边 96 mergeSort(data, low, mid); 97 // 右边 98 mergeSort(data, mid + 1, high); 99 // 左右归并 100 merge(data, low, mid, high); 101 102 } 103 104 } 105 106 public static void merge(int[] data, int low, int mid, int high) { 107 int[] temp = new int[high - low + 1]; 108 int i = low;// 左指针 109 int j = mid + 1;// 右指针 110 int k = 0; 111 // 把较小的数先移到新数组中 112 while (i <= mid && j <= high) { 113 if (data[i] < data[j]) { 114 temp[k++] = data[i++]; 115 } else { 116 temp[k++] = data[j++]; 117 } 118 } 119 // 把左边剩余的数移入数组 120 while (i <= mid) { 121 temp[k++] = data[i++]; 122 } 123 // 把右边边剩余的数移入数组 124 while (j <= high) { 125 temp[k++] = data[j++]; 126 } 127 // 把新数组中的数覆盖nums数组 128 for (int k2 = 0; k2 < temp.length; k2++) { 129 data[k2 + low] = temp[k2]; 130 } 131 } 132 133 private static int[] heapSort(int[] data) { 134 data = buildMaxHeap(data); // 初始建堆,array[0]为第一趟值最大的元素 135 for (int i = data.length - 1; i > 1; i--) { 136 int temp = data[0]; // 将堆顶元素和堆低元素交换,即得到当前最大元素正确的排序位置 137 data[0] = data[i]; 138 data[i] = temp; 139 adjustDownToUp(data, 0, i); // 整理,将剩余的元素整理成堆 140 } 141 return data; 142 } 143 144 // 构建大根堆:将array看成完全二叉树的顺序存储结构 145 private static int[] buildMaxHeap(int[] array) { 146 // 从最后一个节点array.length-1的父节点(array.length-1-1)/2开始,直到根节点0,反复调整堆 147 for (int i = (array.length - 2) / 2; i >= 0; i--) { 148 adjustDownToUp(array, i, array.length); 149 } 150 return array; 151 } 152 153 // 将元素array[k]自下往上逐步调整树形结构 154 private static void adjustDownToUp(int[] array, int k, int length) { 155 int temp = array[k]; 156 for (int i = 2 * k + 1; i < length - 1; i = 2 * i + 1) { // i为初始化为节点k的左孩子,沿节点较大的子节点向下调整 157 if (i < length && array[i] < array[i + 1]) { // 取节点较大的子节点的下标 158 i++; // 如果节点的右孩子>左孩子,则取右孩子节点的下标 159 } 160 if (temp >= array[i]) { // 根节点 >=左右子女中关键字较大者,调整结束 161 break; 162 } else { // 根节点 <左右子女中关键字较大者 163 array[k] = array[i]; // 将左右子结点中较大值array[i]调整到双亲节点上 164 k = i; // 【关键】修改k值,以便继续向下调整 165 } 166 } 167 array[k] = temp; // 被调整的结点的值放人最终位置 168 } 169 170 // 3.选择排序:直接选择排序 171 private static int[] chooseSort(int[] data) { 172 // 循环次数 173 for (int i = 0; i < data.length; i++) { 174 // 逐个比较 175 for (int j = i + 1; j < data.length; j++) { 176 if (data[i] > data[j]) { 177 int temp = data[i]; 178 data[i] = data[j]; 179 data[j] = temp; 180 } 181 } 182 } 183 184 return data; 185 186 } 187 188 // 2.交换排序:快速排序 189 private static int[] quickSort(int[] data, int low, int high) { 190 if (low < high) { 191 int middle = getMiddle(data, low, high);// 获取中间值 192 // 对两端的再次进行排序 193 quickSort(data, low, middle - 1); 194 quickSort(data, middle + 1, high); 195 196 } 197 return data; 198 } 199 200 // 快速排序:获取中间值 201 private static int getMiddle(int[] data, int low, int high) { 202 // 以第一个元素为基准 203 int temp = data[low]; 204 while (low < high) { 205 while (low < high && data[high] > temp) { 206 high--; 207 } 208 data[low] = data[high]; 209 while (low < high && data[low] < temp) { 210 low++; 211 } 212 data[high] = data[low]; 213 } 214 data[low] = temp; 215 return low; 216 } 217 218 // 2.交换排序:冒泡排序 219 private static int[] bubbleSort(int[] data) { 220 // 冒泡次数 221 for (int i = 0; i < data.length; i++) { 222 // 冒泡 223 for (int j = 0; j < data.length - i - 1; j++) { 224 if (data[j] > data[j + 1]) { 225 int temp = data[j]; 226 data[j] = data[j + 1]; 227 data[j + 1] = temp; 228 } 229 } 230 } 231 return data; 232 } 233 234 // 1.插入排序:希尔排序 235 private static int[] shellSort(int[] data) { 236 // 划分组 237 for (int r = data.length / 2; r >= 1; r /= 2) { 238 // 对每一组进行插入排序 239 for (int i = r; i < data.length; i++) { 240 int insertData = data[i];// 插入的数据 241 int j = i - r;// 临时序号 242 while (j >= 0 && data[j] < insertData) { 243 data[j + r] = data[j]; 244 j -= r; 245 } 246 data[j + r] = insertData; 247 } 248 } 249 return data; 250 } 251 252 // 1.插入排序:直接插入排序 253 private static int[] insertSort(int[] data) { 254 for (int i = 1; i < data.length; i++) { 255 int insertData = data[i]; // 要插入到数组中的数据 256 int j = i - 1; // 临时脚标 257 while (j >= 0 && insertData < data[j]) { 258 data[j + 1] = data[j];// 将原始数据后移 259 j--; 260 } 261 data[j + 1] = insertData; // 将如要插入的数据插入到此处 262 } 263 return data; 264 } 265 266 }