数据结构八大排序——数组和链表实现交换排序(冒泡和选择)与插入排序

冒泡排序(优化版)(交换排序):

  数组实现:

  顾名思义,冒泡排序,首先将一组乱序数字中的最大数移到最后,随后除去每次移动到最后的数字,再将前面的所有数字中最大数移到最后...以此类推。(也就是先排序后面的),这里做的优化就是基于后面已经排好序的情况下进行的,假如一组数: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
由此可见,希尔排序很大程度上提高了插入排序的性能!

猜你喜欢

转载自www.cnblogs.com/taichiman/p/12871128.html