排序算法 動圖講解

本文由 簡悅 SimpRead 轉碼, 原文地址 https://www.toutiao.com/i6732720817001464327/html

做者:郭耀華
來源:https://www.cnblogs.com/guoyaohua/p/8600214.html

最近幾天在研究排序算法,看了不少博客,發現網上有的文章中對排序算法解釋的並非很透徹,並且有不少代碼都是錯誤的,例若有的文章中在 「桶排序」 算法中對每一個桶進行排序直接使用了 Collection.sort()函數,這樣雖然能達到效果,但對於算法研究來說是不能夠的。算法

因此我根據這幾天看的文章,整理了一個較爲完整的排序算法總結,本文中的全部算法均有 JAVA 實現,經本人調試無誤後才發出,若有錯誤,請各位前輩指出。數組

0、排序算法說明數據結構

0.1 排序的定義ide

對一序列對象根據某個關鍵字進行排序。函數

0.2 術語說明性能

  • 穩定:若是 a 本來在 b 前面,而 a=b,排序以後 a 仍然在 b 的前面;
  • 不穩定:若是 a 本來在 b 的前面,而 a=b,排序以後 a 可能會出如今 b 的後面;
  • 內排序:全部排序操做都在內存中完成;
  • 外排序:因爲數據太大,所以把數據放在磁盤中,而排序經過磁盤和內存的數據傳輸才能進行;
  • 時間複雜度:一個算法執行所耗費的時間。
  • 空間複雜度:運行完一個程序所需內存的大小。

0.3 算法總結ui

圖片名詞解釋:設計

  • n: 數據規模
  • k: 「桶」 的個數
  • In-place: 佔用常數內存,不佔用額外內存
  • Out-place: 佔用額外內存

0.4 算法分類3d

0.5 比較和非比較的區別

常見的快速排序、歸併排序、堆排序、冒泡排序等屬於比較排序。在排序的最終結果裏,元素之間的次序依賴於它們之間的比較。每一個數都必須和其餘數進行比較,才能肯定本身的位置。

在冒泡排序之類的排序中,問題規模爲 n,又由於須要比較 n 次,因此平均時間複雜度爲 O(n²)。在歸併排序、快速排序之類的排序中,問題規模經過分治法消減爲 logN 次,因此時間複雜度平均 O(nlogn)。

比較排序的優點是,適用於各類規模的數據,也不在意數據的分佈,都能進行排序。能夠說,比較排序適用於一切須要排序的狀況。

計數排序、基數排序、桶排序則屬於非比較排序。非比較排序是經過肯定每一個元素以前,應該有多少個元素來排序。針對數組 arr,計算 arr[i] 以前有多少個元素,則惟一肯定了 arr[i] 在排序後數組中的位置。

非比較排序只要肯定每一個元素以前的已有的元素個數便可,全部一次遍歷便可解決。算法時間複雜度 O(n)。

非比較排序時間複雜度底,但因爲非比較排序須要佔用空間來肯定惟一位置。因此對數據規模和數據分佈有必定的要求。

一、冒泡排序(Bubble Sort)

冒泡排序是一種簡單的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,若是它們的順序錯誤就把它們交換過來。走訪數列的工做是重複地進行直到沒有再須要交換,也就是說該數列已經排序完成。

這個算法的名字由來是由於越小的元素會經由交換慢慢 「浮」 到數列的頂端。冒泡排序介紹:冒泡排序

1.1 算法描述

  • 比較相鄰的元素。若是第一個比第二個大,就交換它們兩個;
  • 對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對,這樣在最後的元素應該會是最大的數;
  • 針對全部的元素重複以上的步驟,除了最後一個;
  • 重複步驟 1~3,直到排序完成。

1.2 動圖演示

1.3 代碼實現

1.4 算法分析

最佳狀況:T(n) = O(n) 最差狀況:T(n) = O(n2) 平均狀況:T(n) = O(n2)

二、選擇排序(Selection Sort)

表現最穩定的排序算法之一,由於不管什麼數據進去都是 O(n2) 的時間複雜度,因此用到它的時候,數據規模越小越好。惟一的好處可能就是不佔用額外的內存空間了吧。理論上講,選擇排序可能也是平時排序通常人想到的最多的排序方法了吧。

選擇排序 (Selection-sort) 是一種簡單直觀的排序算法。它的工做原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,而後,再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。以此類推,直到全部元素均排序完畢。

2.1 算法描述

n 個記錄的直接選擇排序可通過 n-1 趟直接選擇排序獲得有序結果。具體算法描述以下:

  • 初始狀態:無序區爲 R[1..n],有序區爲空;
  • 第 i 趟排序 (i=1,2,3…n-1) 開始時,當前有序區和無序區分別爲 R[1..i-1]和 R(i..n)。該趟排序從當前無序區中 - 選出關鍵字最小的記錄 R[k],將它與無序區的第 1 個記錄 R 交換,使 R[1..i]和 R[i+1..n)分別變爲記錄個數增長 1 個的新有序區和記錄個數減小 1 個的新無序區;
  • n-1 趟結束,數組有序化了。

2.2 動圖演示

2.3 代碼實現

2.4 算法分析

最佳狀況:T(n) = O(n2) 最差狀況:T(n) = O(n2) 平均狀況:T(n) = O(n2)

三、插入排序(Insertion Sort)

插入排序(Insertion-Sort)的算法描述是一種簡單直觀的排序算法。它的工做原理是經過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。插入排序在實現上,一般採用 in-place 排序(即只需用到 O(1) 的額外空間的排序),於是在從後向前掃描過程當中,須要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。

3.1 算法描述

通常來講,插入排序都採用 in-place 在數組上實現。具體算法描述以下:

  • 從第一個元素開始,該元素能夠認爲已經被排序;
  • 取出下一個元素,在已經排序的元素序列中從後向前掃描;
  • 若是該元素(已排序)大於新元素,將該元素移到下一位置;
  • 重複步驟 3,直到找到已排序的元素小於或者等於新元素的位置;
  • 將新元素插入到該位置後;
  • 重複步驟 2~5。

3.2 動圖演示

3.2 代碼實現

3.4 算法分析

最佳狀況:T(n) = O(n) 最壞狀況:T(n) = O(n2) 平均狀況:T(n) = O(n2)

四、希爾排序(Shell Sort)

希爾排序是希爾(Donald Shell)於 1959 年提出的一種排序算法。希爾排序也是一種插入排序,它是簡單插入排序通過改進以後的一個更高效的版本,也稱爲縮小增量排序,同時該算法是衝破 O(n2)的第一批算法之一。它與插入排序的不一樣之處在於,它會優先比較距離較遠的元素。希爾排序又叫縮小增量排序。

希爾排序是把記錄按下表的必定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減小,每組包含的關鍵詞愈來愈多,當增量減至 1 時,整個文件恰被分紅一組,算法便終止。

4.1 算法描述

咱們來看下希爾排序的基本步驟,在此咱們選擇增量 gap=length/2,縮小增量繼續以 gap = gap/2 的方式,這種增量選擇咱們能夠用一個序列來表示,{n/2,(n/2)/2…1},稱爲增量序列。希爾排序的增量序列的選擇與證實是個數學難題,咱們選擇的這個增量序列是比較經常使用的,也是希爾建議的增量,稱爲希爾增量,但其實這個增量序列不是最優的。此處咱們作示例使用希爾增量。

先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,具體算法描述:

  • 選擇一個增量序列 t1,t2,…,tk,其中 ti>tj,tk=1;
  • 按增量序列個數 k,對序列進行 k 趟排序;
  • 每趟排序,根據對應的增量 ti,將待排序列分割成若干長度爲 m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲 1 時,整個序列做爲一個表來處理,表長度即爲整個序列的長度。

4.2 過程演示

4.3 代碼實現

4.4 算法分析

最佳狀況:T(n) = O(nlog2 n) 最壞狀況:T(n) = O(nlog2 n) 平均狀況:T(n) =O(nlog2n) 

五、歸併排序(Merge Sort)

和選擇排序同樣,歸併排序的性能不受輸入數據的影響,但表現比選擇排序好的多,由於始終都是 O(n log n)的時間複雜度。代價是須要額外的內存空間。

歸併排序是創建在歸併操做上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。歸併排序是一種穩定的排序方法。將已有序的子序列合併,獲得徹底有序的序列;即先使每一個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲 2 - 路歸併。

5.1 算法描述

  • 把長度爲 n 的輸入序列分紅兩個長度爲 n/2 的子序列;
  • 對這兩個子序列分別採用歸併排序;
  • 將兩個排序好的子序列合併成一個最終的排序序列。

5.2 動圖演示

5.3 代碼實現

5.4 算法分析

最佳狀況:T(n) = O(n) 最差狀況:T(n) = O(nlogn) 平均狀況:T(n) = O(nlogn)

六、快速排序(Quick Sort)

快速排序的基本思想:經過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另外一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。

6.1 算法描述

快速排序使用分治法來把一個串(list)分爲兩個子串(sub-lists)。具體算法描述以下:

  • 從數列中挑出一個元素,稱爲 「基準」(pivot);
  • 從新排序數列,全部元素比基準值小的擺放在基準前面,全部元素比基準值大的擺在基準的後面(相同的數能夠到任一邊)。在這個分區退出以後,該基準就處於數列的中間位置。這個稱爲分區(partition)操做;
  • 遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。

6.2 動圖演示

6.3 代碼實現

6.4 算法分析

最佳狀況:T(n) = O(nlogn) 最差狀況:T(n) = O(n2) 平均狀況:T(n) = O(nlogn) 

七、堆排序(Heap Sort)

堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。

7.1 算法描述

  • 將初始待排序關鍵字序列 (R1,R2….Rn) 構建成大頂堆,此堆爲初始的無序區;
  • 將堆頂元素 R[1]與最後一個元素 R[n]交換,此時獲得新的無序區 (R1,R2,……Rn-1) 和新的有序區(Rn), 且知足 R[1,2…n-1]<=R[n];
  • 因爲交換後新的堆頂 R[1]可能違反堆的性質,所以須要對當前無序區 (R1,R2,……Rn-1) 調整爲新堆,而後再次將 R[1]與無序區最後一個元素交換,獲得新的無序區 (R1,R2….Rn-2) 和新的有序區(Rn-1,Rn)。不斷重複此過程直到有序區的元素個數爲 n-1,則整個排序過程完成。

7.2 動圖演示

7.3 代碼實現

注意:這裏用到了徹底二叉樹的部分性質:

http://www.cnblogs.com/guoyaohua/p/8595289.html

7.4 算法分析

最佳狀況:T(n) = O(nlogn) 最差狀況:T(n) = O(nlogn) 平均狀況:T(n) = O(nlogn)

八、計數排序(Counting Sort)

計數排序的核心在於將輸入的數據值轉化爲鍵存儲在額外開闢的數組空間中。做爲一種線性時間複雜度的排序,計數排序要求輸入的數據必須是有肯定範圍的整數。

計數排序 (Counting sort) 是一種穩定的排序算法。計數排序使用一個額外的數組 C,其中第 i 個元素是待排序數組 A 中值等於 i 的元素的個數。而後根據數組 C 來將 A 中的元素排到正確的位置。它只能對整數進行排序。

8.1 算法描述

  • 找出待排序的數組中最大和最小的元素;
  • 統計數組中每一個值爲 i 的元素出現的次數,存入數組 C 的第 i 項;
  • 對全部的計數累加(從 C 中的第一個元素開始,每一項和前一項相加);
  • 反向填充目標數組:將每一個元素 i 放在新數組的第 C(i) 項,每放一個元素就將 C(i) 減去 1。

8.2 動圖演示

8.3 代碼實現

8.4 算法分析

當輸入的元素是 n 個 0 到 k 之間的整數時,它的運行時間是 O(n + k)。計數排序不是比較排序,排序的速度快於任何比較排序算法。因爲用來計數的數組 C 的長度取決於待排序數組中數據的範圍(等於待排序數組的最大值與最小值的差加上 1),這使得計數排序對於數據範圍很大的數組,須要大量時間和內存。

最佳狀況:T(n) = O(n+k) 最差狀況:T(n) = O(n+k) 平均狀況:T(n) = O(n+k)

九、桶排序(Bucket Sort)

桶排序是計數排序的升級版。它利用了函數的映射關係,高效與否的關鍵就在於這個映射函數的肯定。

桶排序 (Bucket sort) 的工做的原理:假設輸入數據服從均勻分佈,將數據分到有限數量的桶裏,每一個桶再分別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排

9.1 算法描述

  • 人爲設置一個 BucketSize,做爲每一個桶所能放置多少個不一樣數值(例如當 BucketSize==5 時,該桶能夠存放{1,2,3,4,5}這幾種數字,可是容量不限,便可以存放 100 個 3);
  • 遍歷輸入數據,而且把數據一個一個放到對應的桶裏去;
  • 對每一個不是空的桶進行排序,可使用其它排序方法,也能夠遞歸使用桶排序;
  • 從不是空的桶裏把排好序的數據拼接起來。

注意,若是遞歸使用桶排序爲各個桶排序,則當桶數量爲 1 時要手動減少 BucketSize 增長下一循環桶的數量,不然會陷入死循環,致使內存溢出。

9.2 圖片演示

9.3 代碼實現

9.4 算法分析

桶排序最好狀況下使用線性時間 O(n),桶排序的時間複雜度,取決與對各個桶之間數據進行排序的時間複雜度,由於其它部分的時間複雜度都爲 O(n)。很顯然,桶劃分的越小,各個桶之間的數據越少,排序所用的時間也會越少。但相應的空間消耗就會增大。

最佳狀況:T(n) = O(n+k) 最差狀況:T(n) = O(n+k) 平均狀況:T(n) = O(n2)

十、基數排序(Radix Sort)

基數排序也是非比較的排序算法,對每一位進行排序,從最低位開始排序,複雜度爲 O(kn), 爲數組長度,k 爲數組中的數的最大的位數;

基數排序是按照低位先排序,而後收集;再按照高位排序,而後再收集;依次類推,直到最高位。有時候有些屬性是有優先級順序的,先按低優先級排序,再按高優先級排序。最後的次序就是高優先級高的在前,高優先級相同的低優先級高的在前。基數排序基於分別排序,分別收集,因此是穩定的。基數排序:基數排序

10.1 算法描述

  • 取得數組中的最大數,並取得位數;
  • arr 爲原始數組,從最低位開始取每一個位組成 radix 數組;
  • 對 radix 進行計數排序(利用計數排序適用於小範圍數的特色);

10.2 動圖演示

10.3 代碼實現

10.4 算法分析

最佳狀況:T(n) = O(n * k) 最差狀況:T(n) = O(n * k) 平均狀況:T(n) = O(n * k)

基數排序有兩種方法:

  • MSD 從高位開始進行排序
  • LSD 從低位開始進行排序

基數排序 vs 計數排序 vs 桶排序

這三種排序算法都利用了桶的概念,但對桶的使用方法上有明顯差別:

  • 基數排序:根據鍵值的每位數字來分配桶
  • 計數排序:每一個桶只存儲單一鍵值
  • 桶排序:每一個桶存儲必定範圍的數值
相關文章
相關標籤/搜索