冒泡排序(优化版)(交换排序):
数组实现:
顾名思义,冒泡排序,首先将一组乱序数字中的最大数移到最后,随后除去每次移动到最后的数字,再将前面的所有数字中最大数移到最后...以此类推。(也就是先排序后面的),这里做的优化就是基于后面已经排好序的情况下进行的,假如一组数:4、2、8、3、4、1,第一轮排好后:2、4、3、4、1、8,此时8已经排好,所以以后无需比较此数,设一个标志位记录本轮有没有进行交换动作,如若没有,说明已经排好序,不需要再进行后面的比较。
如果忘记了,可以先看下列分析图:
具体代码:
1 public static void bubbleSort(int[] nums) { 2 int temp = 0; 3 boolean flag = false; 4 for (int j = 0; j < nums.length - 1; j++) { 5 for (int i = 0; i < nums.length - 1 - j; i++) {// 此处-i是将后面的值确定化,并不再比较它们,减少内循环 6 7 if (nums[i] > nums[i + 1]) {//交换,并记录本轮是否有过交换 8 count++;//记录一共比较多少次 9 flag = true; 10 temp = nums[i]; 11 nums[i] = nums[i + 1]; 12 nums[i + 1] = temp; 13 } 14 } 15 if (!flag)// 优化: 说明本轮没有进行比较,避免第j次已经排好序的状态下,又继续排序. 16 return;17 else 18 flag = false; 19 } 20 }
注意:测试时的计算机配置不同,结果可能不同,但是排序之间的相对结果应该是合理的。
冒泡排序的最坏情况下:时间复杂度为O(n2)
测试80000随机数,范围在1000w内的随机数。结果如下:
前后差约为:16秒(效率很差,即使再怎么优化也不能提升,如果是很多数排序,千万不要用冒泡排序)
before:2020/05/10 23:07:17:982
after:2020/05/10 23:07:33:307比较了交换次数:1603714390
链表实现:
1 public void bubbleSort(Node head) { 2 if (null == head.getNext()) {//验证链表是否为空 3 throw new RuntimeException("head getnext is null"); 4 } 5 boolean flag = false;//优化标志位 6 for (int i = 0; i < size; i++) { 7 Node temp = head;//代替辅助头结点 8 while (temp.getNext() != head) {//由于采用循环双链表,所以判断当前结点下一个是否为头结点,如果是则一轮比较结束 9 Node n1 = temp;//保存当前结点 10 Node n2 = temp.getNext();//保存下一结点 11 if (n1.getValue() > n2.getValue()) {//开始比较进行交换 12 swap(n1, n2);//交换方法在下方 13 flag = true; 14 continue;//优化点(其实大数量下没什么卵用) 15 } 16 temp = temp.getNext(); 17 } 18 if (!flag) { 19 return; 20 } else { 21 flag = false; 22 } 23 } 24 } 25 //这里的交换方法,需要你仔细思量,最好在纸上画一下 26 public void swap(Node node1, Node node2) { 27 node1.getPre().setNext(node2); 28 node2.setPre(node1.getPre()); 29 node1.setNext(node2.getNext()); 30 node2.getNext().setPre(node1); 31 node1.setPre(node2); 32 node2.setNext(node1); 33 }
测试:
冒泡排序的优化在上万数量级下,没有什么卵用,冒泡退出舞台,没有可以挽留的余地了。链表形式的冒泡看看就行了。。。由于是环形双链表,其交换效率比移动数组还差!不要测试了,80000数据跑了几分钟。。。不如数组。
选择排序(交换排序):
选择最小的,从前往后排,每一轮找出(除前面排好的)数字中最小的数与当前值进行交换。比较简单,看了前面的冒泡排序,链表也没必要了,直接上代码:
1 public static void selectSort(int[] nums) { 2 for (int i = 0; i < nums.length - 1; i++) { 3 int min = nums[i]; 4 int index = i; 5 for (int j = i + 1; j < nums.length; j++) { 6 if (nums[j] < min) { 7 min = nums[j]; 8 index = j; 9 } 10 } 11 if (index != i) { 12 nums[index] = nums[i]; 13 nums[i] = min; 14 } 15 } 16 }
插入排序:
数组实现:
思路:先将当前值保存到value中,遍历之前得所有元素并且与其比较,如果大于value,则往后移动一位并继续比较前一个,直到value之前得某个值小于等于value时,结束遍历,并将value值赋给该值得后一个位置。估计不好理解,看图说话:
具体代码:
1 public static void insertSort(int[] nums) { 2 int insertValue = 0; 3 int insertIndex = 0; 4 boolean flag=false; 5 for (int i = 1; i < nums.length; i++) { 6 insertValue = nums[i];// 保存当前需要插入到有序表的数值 7 insertIndex = i - 1;// 表示要与当前数值进行比较的值的下标 8 for (int j = insertIndex; j >= 0; j--) {// 在当前值的前面的有序表中遍历 9 if (nums[insertIndex] > insertValue) {// 如果待插入的值小于有序表中的某值(从后往前) 10 nums[insertIndex + 1] = nums[insertIndex];// 那么就将某值后移 11 insertIndex--;// 接着比较有序表中某值的前一个,直到有序表中的第一个 12 } 13 } 14 15 nums[insertIndex + 1] = insertValue;// 因为真正的待插入的位置在遍历完是在待插入位置的前一个,所以要将待插入位置+1 16 } 17 }
测试:
插入排序的最坏情况下:时间复杂度为O(n2)
测试80000随机数,范围在1000w内的随机数。结果如下:
前后差约为:5秒(同条件下相比冒泡提升了3倍以上的效率)
before:2020/05/10 23:18:04:379
after:2020/05/10 23:18:09:187
链表实现:
1 public static void insertSort(Node head) { 2 if (null == head.getNext()) { 3 return; 4 } 5 Node temp = head.getNext(); 6 while (temp != head) { 7 temp = temp.getNext(); 8 if (temp == head) 9 break; 10 int insertValue = temp.getValue(); 11 Node insertPointer = temp.getPre(); 12 while (head != insertPointer) { 13 if (insertPointer.getValue() > insertValue && insertPointer.getPre().getValue() <= insertValue) { 14 insertPointer = insertPointer.getPre(); 15 16 temp.getPre().setNext(temp.getNext()); 17 temp.getNext().setPre(temp.getPre()); 18 19 Node insertNode = new Node(insertValue); 20 insertNode.setNext(insertPointer.getNext()); 21 insertPointer.getNext().setPre(insertNode); 22 insertPointer.setNext(insertNode); 23 insertNode.setPre(insertPointer); 24 25 continue; 26 } 27 insertPointer = insertPointer.getPre(); 28 } 29 } 30 }
不推荐链表排序,同样80000个数,居然要1分钟才排好。。。看看就行了。学习链表其中的技巧。
希尔排序(插入排序的一种):
希尔排序是插入排序的升级版,其升级之处在于分组排序,分组移动插入。插入排序每个元素都需要与之前所有的元素进行比较等操作,但希尔排序却是跳跃式的操作,只有最后一次遍历才是对之前所有元素进行操作。
具体看图:
以下示例为一组无需数组,假设这里的分组间隔为(length/2)即:gap=4
分组间隙为4,即从gap开始往后每一元素下标减去gap对应的元素,这些为一组进行比较,每一次比较前保存当前值和索引,与当前索引和gap的差对应的元素进行比较。
分组间隔为4,索引0、4、8为一组进行元素比较,第一轮结束。
gap=gap/2=2,即分组间隔为2
后面的操作与第一轮一样。图示过程如下:
最后一轮,也就是gap=gap/2=1。这里只列出了变化了的操作。
具体代码描述:
1 public static void shellSort(int[] nums) { 2 for (int gap = nums.length / 2; gap > 0; gap /= 2) {// 数组长度减半 3 for (int i = gap; i < nums.length; i++) {// 从gap开始遍历到数组尾部 4 int j = i;// 当前索引复制给j为从当前i到之前所有的j-gap 5 int temp = nums[j];// 保存当前待插入的值 6 while (j - gap >= 0 && temp < nums[j - gap]) {// 同一间隔组从后往前进行比较,并且当索引值j-gap<0时就表示这一组已排序完成 7 nums[j] = nums[j - gap];// 元素后移 8 j -= gap;// 进行下一个组内比较,j等于组内前一个元素的索引 9 } 10 nums[j] = temp;// 将待插入的值放到 11 //System.out.println(Arrays.toString(nums)); 12 } 13 } 14 }
测试:
// before:2020/02/13 20:36:38:324
// after:2020/02/13 20:36:41:82
// 800w数据(数据小于1000w)排序:花了大概3s
由此可见,希尔排序很大程度上提高了插入排序的性能!