數(shù)據(jù)結(jié)構(gòu)與算法之美-排序(一)

前言:本篇文章只是記錄王爭的數(shù)據(jù)結(jié)構(gòu)與算法之美的學(xué)習(xí)筆記,寫下來能強(qiáng)迫自己系統(tǒng)的再過一遍,加深理解。這門課以實(shí)際開發(fā)中遇到的問題為例,引入解決問題涉及到的的數(shù)據(jù)結(jié)構(gòu)和算法,但不會(huì)講的太細(xì),最好結(jié)合一本實(shí)體書進(jìn)行學(xué)習(xí)。

1. 排序算法

1.1 介紹

下面是我們常用的 8 種排序算法,按照時(shí)間復(fù)雜度分成了三類,如下圖


image.png
1.2 排序算法的執(zhí)行效率
  • 最好情況、最壞情況、平均情況時(shí)間復(fù)雜度
    以及對應(yīng)的要排序的原始數(shù)據(jù),有序度不同的數(shù)據(jù),對于排序的執(zhí)行時(shí)間是有影響的

  • 時(shí)間復(fù)雜度的系數(shù)、常數(shù)、低階
    時(shí)間復(fù)雜度反映的是數(shù)據(jù)規(guī)模 n 很大時(shí)的一個(gè)增長趨勢,會(huì)忽略系數(shù)、常數(shù)、低階,但實(shí)際開發(fā)中,排序的可能是 10 個(gè)、100 個(gè)、1000 個(gè)數(shù)據(jù),在對同一階時(shí)間復(fù)雜度的排序算法性能對比時(shí),需要把系數(shù)、常數(shù)、低階考慮進(jìn)來。

  • 比較次數(shù)和交換(移動(dòng))次數(shù)
    基于比較的排序算法的執(zhí)行過程,會(huì)涉及兩種操作:比較大小和元素交換或移動(dòng),也需要考慮進(jìn)去

1.3 排序算法的內(nèi)存消耗

算法的內(nèi)存消耗可以通過空間復(fù)雜度來衡量,針對排序算法的空間復(fù)雜度,還引入了一個(gè)新的概念:原地排序。原地排序算法,就是特指空間復(fù)雜度是 O(1)的排序算法。

1.4 排序算法的穩(wěn)定性

什么是穩(wěn)定的排序算法?就是如果待排序序列中存在值相等的元素,經(jīng)過排序后,相等元素之間原有的先后順序不變,否則就是不穩(wěn)定的排序算法。

舉個(gè)例子,比如有一組數(shù)據(jù) 2 9 3 4 8 3,按照大小排序之后就是 2 3 3 4 8 9,經(jīng)過某種排序算法排序之后,如果兩個(gè) 3 的前后順序沒有改變,那這種排序算就是穩(wěn)定的排序算法,否則就是不穩(wěn)定的排序算法。

2. 有序度&逆序度

  • 有序度是數(shù)組中具有有序關(guān)系的元素對的個(gè)數(shù)


    image.png

對于一個(gè)倒序排列的數(shù)組,比如 6 5 4 3 2 1,有序度為 0;
對于一個(gè)有序排列的數(shù)組,比如 1 2 3 4 5 6,有序度就是n*(n-1)/2,就是 15,完全有序,為滿有序度。

逆序度的定義和有序度相反,并且逆序度 = 滿有序度 - 有序度。我們排序的過程就是一種增加有序度,減少逆序度的過程,最后達(dá)到滿有序度,說明排序完成。

3. 冒泡排序

冒泡排序只會(huì)操作相鄰的兩個(gè)數(shù)據(jù),每次冒泡操作都會(huì)對相鄰的兩個(gè)元素進(jìn)行比較,看是否滿足大小關(guān)系需求。如果不滿足就讓它倆互換,一次冒泡會(huì)讓至少一個(gè)元素移動(dòng)到它應(yīng)該在的位置,重復(fù) n 次,就完成了 n 個(gè)數(shù)據(jù)的排序工作。

對一組數(shù)據(jù) 4,5,6,3,2,1,從小到大進(jìn)行排序,第一次冒泡操作示例如下:


image.png

經(jīng)過一次冒泡操作之后,6 這個(gè)元素已經(jīng)存儲(chǔ)在正確的位置上了,要想完成所有數(shù)據(jù)的排序,我們只要進(jìn)行 6 次冒泡操作就行了:


image.png

我們可以對相面的冒泡過程進(jìn)行優(yōu)化,當(dāng)某次冒泡操作沒有數(shù)據(jù)交換時(shí),說明已經(jīng)達(dá)到完全有序,不需要在進(jìn)行后續(xù)的冒泡操作,如下圖:


image.png

代碼如下:


// 冒泡排序,a表示數(shù)組,n表示數(shù)組大小
public void bubbleSort(int[] a, int n) {
  if (n <= 1) return;
 
 for (int i = 0; i < n; ++i) {
    // 提前退出冒泡循環(huán)的標(biāo)志位
    boolean flag = false;
    for (int j = 0; j < n - i - 1; ++j) {
      if (a[j] > a[j+1]) { // 交換
        int tmp = a[j];
        a[j] = a[j+1];
        a[j+1] = tmp;
        flag = true;  // 表示有數(shù)據(jù)交換      
      }
    }
    if (!flag) break;  // 沒有數(shù)據(jù)交換,提前退出
  }
}
  • 冒泡排序是原地排序算法,只涉及相鄰數(shù)據(jù)的交換操作,空間復(fù)雜度為 O(1)
  • 冒泡排序是穩(wěn)定的排序算法,當(dāng)有兩個(gè)相鄰元素大小相等的時(shí)候,可以不做交換
  • 最壞情況時(shí)間復(fù)雜度為 O(n^2)


    image.png

4. 插入排序

主要思想就是在有序的數(shù)組中,通過遍歷數(shù)組,將新的數(shù)據(jù)插入到合適的位置,繼續(xù)保持?jǐn)?shù)組有序:

image.png

就是將數(shù)組中的數(shù)據(jù)分為已排序區(qū)間未排序區(qū)間,初始時(shí)已排序區(qū)間只有數(shù)組的第一個(gè)元素。插入算法的核心思想就是取未排序區(qū)間中的元素,在已排序區(qū)間中找到合適的插入位置將其插入,并保證已排序區(qū)間數(shù)據(jù)一直有序。重復(fù)這個(gè)過程,直到未排序區(qū)間中元素為空,算法結(jié)束。

image.png

代碼如下:


// 插入排序,a表示數(shù)組,n表示數(shù)組大小
public void insertionSort(int[] a, int n) {
  if (n <= 1) return;

  for (int i = 1; i < n; ++i) {
    int value = a[I];
    int j = i - 1;
    // 查找插入的位置
    for (; j >= 0; --j) {
      if (a[j] > value) {
        a[j+1] = a[j];  // 數(shù)據(jù)移動(dòng)
      } else {
        break;
      }
    }
    a[j+1] = value; // 插入數(shù)據(jù)
  }
}
  • 插入排序并不需要額外的空間,空間復(fù)雜度為 O(1),為原地排序算法
  • 是穩(wěn)定的排序算法,對于值相同的元素,我們可以選擇將后面出現(xiàn)的元素,插入到前面元素的后面,保持原有的前后順序不變
  • 平均時(shí)間復(fù)雜度為 O(n^2)

5. 選擇排序

選擇排序?qū)崿F(xiàn)思路類似于插入排序,也分為已排序區(qū)間未排序區(qū)間。但是選擇排序每次會(huì)從未排序區(qū)間中找到最小的元素,將其放到已排序區(qū)間的末尾(交換):

image.png

  • 選擇排序空間復(fù)雜度為 O(1),也是一種原地排序算法
  • 時(shí)間復(fù)雜度為 O(n^2)
  • 選擇排序是一種不穩(wěn)定的排序算法,相對于冒泡排序和插入排序,選擇排序稍微遜色

6. 總結(jié)

冒泡排序不管怎么優(yōu)化,元素交換的次數(shù)總是原始數(shù)據(jù)的逆序度;
插入排序不管怎么優(yōu)化,元素移動(dòng)的次數(shù)也是原始數(shù)據(jù)的逆序度。

從代碼實(shí)現(xiàn)上看,冒泡排序的數(shù)據(jù)交換要比插入排序的數(shù)據(jù)移動(dòng)復(fù)雜,冒泡排序需要 3 個(gè)賦值操作,插入排序只需要 1 個(gè),所以插入排序要優(yōu)于冒泡排序:


冒泡排序中數(shù)據(jù)的交換操作:
if (a[j] > a[j+1]) { // 交換
   int tmp = a[j];
   a[j] = a[j+1];
   a[j+1] = tmp;
   flag = true;
}

插入排序中數(shù)據(jù)的移動(dòng)操作:
if (a[j] > value) {
  a[j+1] = a[j];  // 數(shù)據(jù)移動(dòng)
} else {
  break;
}
  • 分析&評價(jià)一個(gè)排序算法,要從執(zhí)行效率、內(nèi)存消耗和穩(wěn)定性三個(gè)方面來看
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。