排序(二)交换排序:冒泡排序与快速排序

冒泡排序

  冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。 

  算法描述

  • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • 针对所有的元素重复以上的步骤,除了最后一个;
  • 重复步骤1~3,直到排序完成。

  动图演示

   实例验证

  10000个数字组成的数组排序,耗时大概率在460ms-480ms。

public class TestBubbleSort {

    public static void main(String[] args) {
        System.out.println("old:" + Arrays.asList(Data.array).toString().substring(0, 100) + "...");
        Long start = System.currentTimeMillis();
        sort(Data.array);
        Long end = System.currentTimeMillis();
        System.out.println("new:" + Arrays.asList(Data.array).toString().substring(0, 100) + "...");
        System.out.println("耗时:" + (end - start));
    }

    /**
     * 1.比较相邻的元素。如果第一个比第二个大,就交换它们两个;<br>
     * 2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数; <br>
     * 3.针对所有的元素重复以上的步骤,除了最后一个;<br>
     * 4.重复步骤1~3,直到排序完成。<br>
     */
    static void sort(Integer[] array) {
        // 外层循环控制比较次数
        for (Integer i = 0; i < array.length - 1; i++) {
            // 内层循环控制到达位置
            for (Integer j = 0; j < array.length - 1 - i; j++) {
                if (array[j] > array[j + 1]) {
                    Integer data = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = data;
                }
            }
        }
    }
}

class Data {
    public static Integer[] array;
    static {
        array = new Integer[10000];
        for (Integer i = 0; i < 10000; i++) {
            Random r = new Random();
            array[i] = r.nextInt(100000);
        }
    }
}
View Code

  结果展示:

old:[49773, 80876, 42906, 19675, 75742, 87701, 14611, 18207, 58390, 1921, 45655, 80129, 3414, 71415, 874...
new:[11, 37, 44, 62, 63, 68, 73, 95, 99, 103, 111, 120, 129, 138, 143, 160, 179, 180, 196, 205, 206, 209...
耗时:464

   复杂度分析

平均时间复杂度最好情况最坏情况空间复杂度
O(n²) O(n) O(n²) O(1)

  冒泡排序是最容易实现的排序, 最坏的情况是每次都需要交换, 共需遍历并交换将近n²/2次, 时间复杂度为O(n²). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有缓存的temp变量需要内存空间, 因此空间复杂度为常量O(1).

 

快速排序

  快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

  算法描述

  • 从数列中挑出一个元素,称为 “基准”(pivot) 默认为第一个数;
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

  动图演示

  实例验证

  10000个数字组成的数组排序,耗时在10ms以内。

 1 public class TestQuickSort {
 2     public static void main(String[] args) {
 3         System.out.println("old:" + Arrays.asList(QuickData.array).toString() + "...");
 4         Long start = System.currentTimeMillis();
 5         sort(QuickData.array, 0, QuickData.array.length - 1, "全排");
 6         Long end = System.currentTimeMillis();
 7         System.out.println("new:" + Arrays.asList(QuickData.array).toString() + "...");
 8         System.out.println("耗时:" + (end - start));
 9     }
10 
11     /**
12      * 1.先从数列中取出一个数作为key值; <br>
13      * 2.将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边; <br>
14      * 3.对左右两个小数列重复第二步,直至各区间只有1个数。
15      */
16     static void sort(Integer[] array, int low, int high, String desc) {
17         if (low < high) {
18             int i = getMiddle(array, low, high, desc);
19             sort(array, low, i - 1, "左排");
20             sort(array, i + 1, high, "右排");
21         }
22     }
23 
24     private static int getMiddle(Integer[] array, int low, int high, String desc) {
25         System.out.println("----------------------");
26         System.out.println(desc + "-getMiddle before:" + Arrays.asList(QuickData.array).toString() + "...");
27         int left = low;
28         int right = high;
29         // 保存基准的值
30         int pivot = array[low];
31         while (left < right) {
32             // 从后向前找到比基准小的元素位置right
33             while (array[right] >= pivot && left < right)
34                 right--;
35             // 从前往后找到比基准大的元素位置left
36             while (array[left] <= pivot && left < right)
37                 left++;
38             // 此时array[left]>pivot>array[right],交换left与right元素的位置
39             if (left < right) {
40                 int temp = array[left];
41                 array[left] = array[right];
42                 array[right] = temp;
43             }
44 
45         }
46         // 交换基准元素与中界元素的值
47         array[low] = array[left];
48         array[left] = pivot;
49         System.out.println(desc + "-getMiddle end:" + Arrays.asList(QuickData.array).toString() + "...");
50         System.out.println(desc + "-middle:" + array[left] + " index:" + left);
51         return left;
52     }
53 }
54 
55 class QuickData {
56     public static Integer[] array;
57     static {
58         array = new Integer[10];
59         for (Integer i = 0; i < 10; i++) {
60             Random r = new Random();
61             array[i] = r.nextInt(100);
62         }
63     }
64 }
View Code

  结果展示:

old:[84, 74, 45, 26, 23, 9, 91, 50, 12, 14]...
----------------------
全排-getMiddle before:[84, 74, 45, 26, 23, 9, 91, 50, 12, 14]...
全排-getMiddle end:[12, 74, 45, 26, 23, 9, 14, 50, 84, 91]...
全排-middle:84 index:8
----------------------
左排-getMiddle before:[12, 74, 45, 26, 23, 9, 14, 50, 84, 91]...
左排-getMiddle end:[9, 12, 45, 26, 23, 74, 14, 50, 84, 91]...
左排-middle:12 index:1
----------------------
右排-getMiddle before:[9, 12, 45, 26, 23, 74, 14, 50, 84, 91]...
右排-getMiddle end:[9, 12, 14, 26, 23, 45, 74, 50, 84, 91]...
右排-middle:45 index:5
----------------------
左排-getMiddle before:[9, 12, 14, 26, 23, 45, 74, 50, 84, 91]...
左排-getMiddle end:[9, 12, 14, 26, 23, 45, 74, 50, 84, 91]...
左排-middle:14 index:2
----------------------
右排-getMiddle before:[9, 12, 14, 26, 23, 45, 74, 50, 84, 91]...
右排-getMiddle end:[9, 12, 14, 23, 26, 45, 74, 50, 84, 91]...
右排-middle:26 index:4
----------------------
右排-getMiddle before:[9, 12, 14, 23, 26, 45, 74, 50, 84, 91]...
右排-getMiddle end:[9, 12, 14, 23, 26, 45, 50, 74, 84, 91]...
右排-middle:74 index:7
new:[9, 12, 14, 23, 26, 45, 50, 74, 84, 91]...
耗时:1

  复杂度分析

平均时间复杂度最好情况最坏情况空间复杂度
O(nlog₂n) O(nlog₂n) O(n²) O(1)
posted @ 2021-02-18 23:17  鄙人取个名字好难  阅读(149)  评论(0编辑  收藏  举报