基礎排序算法—冒泡,插入,選擇

前言

冒泡,插入,選擇這三種基礎的排序算法,比較簡單效率不高,工做中通常不會使用,可是當作算法來研究仍是能瞭解一些知識的,本文以<數據結構與算法之美>爲基礎,詳細解析一下.java

正文

首先要引入幾個概念git

穩定性

若是待排序數組中有相同的元素,排序事後它們的相對順序不發生變化. 好比 2,9,3,4,8,3 排序事後爲2, 3 , 3, 4, 8, 9 這兩個3的相對順序不變.這樣就是具備穩定性. 穩定性能夠保證複雜數據結構排序時的相對有序性. 好比咱們要對一筆訂單先按金額排列,金額相同的再按時間排序
就能夠先將這筆訂單按照時間排序,再採用具備穩定性的排序算法按金額進行排序. 猜想Java8中的compare().thenCompare()就是採用這種原理實現github

原地排序

排序過程當中只佔用常數級的額外空間或者不佔用額外空間算法

有序度

數組中具備有序關係的元素對的個數,數學表達式爲 有序元素對:a[i] <= a[j], 若是i < j。
2,4,3,1,5,6 的有序度爲11,有序元素對爲(2,4),(2,3),(2,5),(2,6),(4,5),(4,6),(3,5),(3,6),(1,5),(1,6),(5,6)
6,5,4,3,2,1 這個徹底逆序的數組,有序度爲0
1,2,3,4,5,6 這個徹底有序的數組,有序度爲(n-1)+(n-2)+(n-3)+...+0 = n*(n-1)/2 = 15,即滿有序度數組

逆序度

和有序度定義正好相反 逆序度 = 滿有序度 - 有序度數據結構

約定

爲了方便描述,這裏約定數組爲arr[0~n],n≥1,排列方式爲正序(從小到大).
下面的描述不會舉例講解,推薦到 visualgo查看動畫演示性能

冒泡排序

主要思路

首先將數組在邏輯劃分爲左側的未排序部分和右側的已排序部分
冒泡排序中對未排序部分的一次遍歷稱爲一次冒泡,初始時未排序部分爲arr[0~n-1],未排序部分爲空.冒泡一次後,未排序部分爲arr[0~n-2],已排序部分爲arr[n-1~n-1],冒泡x(1≤x<n)次後,未排序部分爲arr[0~n-1-x),已排序部分爲arr[n-x~n-1],最終達成所有排序
每次冒泡未排序部分時,只會操做相鄰的元素,方向爲 arr[0]→ arr[n-1],若是a[i]>a[i+1]就進行交換,這樣一次冒泡操做後,未排序部分中的最大值就會移動到已排序部分且在最左側(即已排序部分的最小值).
同時,若是一次冒泡後沒有進行過任何交換說明整個數組已經有序了,結合這個特性能夠省去沒必要要的冒泡,核心代碼以下學習

public void sort(Comparable[] a) {
        if (a.length <= 1) {
            return;
        }
        for (int i = a.length - 1; i > 0; i--) {
            boolean changeFlag = false;
            for (int j = 0; j < i; j++) {
                if (a[j + 1].compareTo(a[j]) < 0) {
                    swap(a, j + 1, j);
                    changeFlag = true;
                }
            }
            if (!changeFlag) {
                break;
            }
        }
    }

時間複雜度

最壞狀況下,數組徹底倒序, 時間複雜度爲 (n-1)+(n-2)+(n-3)+...+1 = (n^2)/2 ~= O(n^2) 數字表示交換的次數
最好狀況下,數組已經有序,只須要一次冒泡, 時間複雜度爲 O(n) 這裏不會有交換,以比較的次數做爲計量單位
平均狀況下,用機率推算會很困難,這裏採用上面提到的有序度逆序度,並將元素的交換次數當作計量單位, 觀察能夠看到,每交換一次,都會使原來逆序的一組元素變爲有序,逆序度即爲交換次數,那麼在最好狀況下逆序度爲0,最壞狀況下逆序度爲n(n-1)/2,平均時間複雜度爲二者取平均值
(0 + n(n-1)/2)/2 = O(n^2)測試

是原地排序嗎?

冒泡排序涉及到兩個元素的交換,交換時只使用了一個單位臨時空間,是原地排序動畫

穩定嗎?

冒泡排序中元素位置發生變化交換時,而且交換僅在a[i]>a[i+1]時才發生,a[i]==a[i+1]時時不會發生的,所以是穩定的

插入排序

主要思路

將數組分爲左側的已排序部分和右側的未排序部分
初始時已排序部分爲arr[0~0],未排序部分爲arr[1~n-1]一次插入後,已排序部分爲arr[0~1],未排序部分爲arr[2~n-1],x(1≤x<n)次插入後,已排序部分爲arr[0x],未排序部分爲arr(x+1n-1]
從未排序部分的起始位置開始(即arr[1]),每次插入時,方向爲 arr[n-1]→ arr[0],找出第一個小於等於arr[i]的元素,有兩種狀況

  1. 找到了,插入在它以後(具體實現時 假設找到的下標爲j, 將a[j+1~i-1]的元素依次後移,再將a[i]設置到a[j+1]),
  2. 沒找到,說明a[i]就是當前最小的元素,將a[0~i-1]的元素依次後移,將a[i]設置到a[0]
    核心代碼以下
public void sort(Comparable[] arr) {
            if (arr.length <= 1) {
                return;
            }
            for (int i = 1; i < arr.length; i++) {
                Comparable insertValue = arr[i];
                int insertPos = i;
                for (int j = i - 1; j >= 0; j--) {
                    if (insertValue.compareTo(arr[j]) < 0) {
                        arr[j + 1] = arr[j];
                        insertPos = j;
                    } else {
                        insertPos = j + 1;
                        break;
                    }
                }
                arr[insertPos] = insertValue;
            }
        }

時間複雜度

以元素移動的次數做爲計量單位
最壞狀況下,數組徹底倒序,時間複雜度爲 0 + 1 + 2 + 3 +...+ (n-1) = n*(n-1)/2 ~= O(n^2)
最好狀況下,數組徹底順序, 時間複雜度爲 1 + 1 + 1 + ... + 1= n ~=O(n)
平均狀況下, 咱們仍然使用逆序數這個概念,再觀察依舊能夠得知: 逆序數等於移動次數,因此時間複雜度爲 (0 + n(n-1)/2)/2 ~= O(n^2)

是原地排序嗎?

插入排序中,沒有使用額外的空間,所以是原地排序

穩定嗎

插入排序中,只有移動操做會改變元素位置,而斷定條件是小於等於arr[i] ,符合狀況1,a[i]會被設置到a[j+1],從而保證相對順序的穩定.

選擇排序

主要思路

將數組分爲左側的已排序部分和右側的未排序部分
選擇排序中選擇表示對未排序部分的一次遍歷,初始時已排序部分爲空,未排序部分爲arr[0~n-1],
一次選擇後,已排序部分爲arr[0~0],未排序部分爲arr[1~n-1],x(1≤x<n)次選擇後,已排序部分爲arr[0~x-1],未排序部分爲arr(x~n-1]
x(1≤x<n)次選擇時,找出未排序數組中的最小值,放入已排序數組(與arr[x-1]交換),核心代碼以下

public void sort(Comparable[] arr) {
            if (arr.length <= 1) {
                return;
            }
            for (int i = 1; i < arr.length ; i++){
                int minPos=i-1;
                Comparable minValue=arr[i-1];
                for(int j=i;j<arr.length;j++){
                    if (minValue.compareTo(arr[j])>0){
                        minPos=j;
                        minValue=arr[j];
                    }
                }
                swap(arr,minPos,i-1);
            }
        }

時間複雜度

以minValue被替換的次數做爲計量單位.
最壞狀況下爲 (n-1) + (n-2) + ... + 0 =n(n-1)/2 ~= O(n^2)
最好狀況下爲 1+1+1...+1 =n ~= O(n)
平均狀況下,仍然使用逆序數, 能夠發現: 逆序數等於替換的次數,所以時間複雜度爲(0 + n(n-1)/2)/2 ~= O(n^2)

是原地排序嗎?

選擇排序中,minValue使用了一個單位的額外空間,所以是原地排序

穩定嗎

選擇排序中,沒有約束能夠確保穩定, 好比 4, 3, 2, 4, 1 排序事後,兩個4的相對位置會發生變化

冒泡,插入,選擇的比較

三者的相同之處

  • 將數組虛擬爲已排序和未排序部分,在排序過程當中逐步將未排序部分轉化爲已排序部分.
  • 都是原地排序
  • 平均時間複雜度都是O(n^2)

三者的不一樣之處

  • 冒泡排序,插入排序是穩定的,選擇排序時不穩定
  • 冒泡和選擇頻繁使用交換操做,插入排序較多使用賦值操做,而賦值相比交換性能更好
    三者比較,根據我這裏的簡單測試 性能方面 選擇>插入>冒泡, 可是選擇排序不穩定,僅此一點不少場景下就不適用,實用性大大下降. 所以插入排序時三者中較好的選擇.

總結

冒泡,插入,選擇是三種基本的排序算法,研究他們對學習更高效的排序算法有幫助,相關源碼已經上傳到這裏

相關文章
相關標籤/搜索