排序演算法總結

2021-10-12 16:29:17 字數 4049 閱讀 4351

arrays.sort用到的排序演算法

排序演算法有非常多種。有些排序演算法的時間複雜度是o(n^2),有一些是o(nlogn),有些是o(n);有些排序演算法基於資料之間的比較而有些不基於。有些排序演算法的空間複雜度是o(1),有些是o(n),有些排序演算法是穩定的,而有些不是穩定的排序演算法。本文將歸納一些經典的排序演算法和其各自的特點。

對於排序演算法,我們可以從以下角度來分析其執行效率

time complexity

對於要排序的資料,有些資料本身已經接近有序,有些則是完全無序,對於有序程度不同的資料,對齊排序的時間執行效率是不同的,所以分析排序演算法的時間複雜度我們需要分析最好情況、最壞情況、平均情況的時間複雜度。

而對於某些資料量不大的資料,n本身的值不大,所以有的時候我們還需要考慮到時間複雜度的係數、常數、低階項。對於某些資料,比如n = 1000,可能會出現o(n^2)的排序演算法比o(nlogn)的排序演算法執行時間更短的情況。

space complexity

針對排序演算法,還另外引入了乙個叫**原地排序(sorted in place)**的概念,原地排序就是指空間複雜度為o(1)的排序演算法。要實現sorted in place的演算法,一般要依賴兩個指標,然後對資料進行交換,這樣可以不借助其他資料結構。

排序演算法的穩定性

針對排序演算法,除了分析它的時間複雜度和空間複雜度,我們還需要考慮這個演算法的穩定性。排序演算法的穩定性是指如果待排序的序列中存在值相等的元素,經過排序之後,相等元素之間的原有的先後順序不變。

為什麼要考慮排序演算法的穩定性?

如果要排序的內容是乙個複雜物件的多個數字屬性,且其原本的初始順序存在意義,我們需要使用到穩定性的演算法。例如:要排序的內容是一組原本按照**高低排序的物件,如今需要按照銷量高低排序,使用穩定性演算法,可以使得想同銷量的物件依舊保持著**高低的排序展現,只有銷量不同的才會重新排序。

氣泡排序的思想很簡單,它基於相鄰元素之間的兩兩比較。第一趟比較過程,將所有相鄰元素比較一遍,把最小(大)的數放在最前面(最後面一樣)。第二趟比較過程,把第二小的數放在第一小的數後面…依次類推。假設有k個物件要比較,就要比較k趟。

直接上**:

public

void

bubblesort

(int

nums)if(

!flag)

break;}

}}

這個實現是每次把最小的數、第二小的數…一趟趟地交換到前面。寫的時候加了乙個優化的步驟:當一趟下來,沒有任何資料交換的時候說明已經排好序,可以直接退出迴圈。

下面分析氣泡排序的執行效率:

插入演算法的核心思想是取未排序區間中的元素,在已排序區間中找到合適的插入位置將其插入,並保證已排序區間資料一直有序。

時間複雜度 o(n^2),空間複雜度是o(1),是穩定的演算法。

選擇排序演算法的實現思路有點類似插入排序,也分已排序區間和未排序區間。但是選擇排序每次會從未排序區間中找到最小的元素,將其放到已排序區間的末尾。

時間複雜度是 o(n^2) ,空間複雜度是 o(1) ,不穩定。

快排的思想:在乙個陣列中隨便選定乙個數target(比如固定選取第乙個數,最後乙個數,或者中間的數),將比target小的數分為一部分,比target大的數分為一部分。(注意:這個過程中target的位置並不確定會在**。)

下面是乙個快速排序的實現:

public

class

solution

public

void

quicksort

(int

nums,

int start,

int end)

while

(left <= right && nums[right]

> pivot)

if(left <= right)

}quicksort

(nums, start, right)

;quicksort

(nums, left, end);}

}

說明:

看到這個實現的時候,我第一反應是, 為什麼nums[left] < pivotnums[right] > pivot這兩個判斷不加等號?

其實,這個是為了處理一種極端情況,也就是nums中每個數都相同的情況。

舉例說明,當nums = [1,1,1,1,1,1,1], 把這個情況帶入上面的**,此時有pivot = 1,如果有等號,比如nums[left] <= pivot,此時在迴圈內left每次加1,看上去沒啥問題,但是如果nums很大的時候,這樣做就會比較慢,而如果採用nums[left] < pivot,碰到這種情況,每次迴圈是left++right--,在這種情況下,不加等號,迴圈可以快點結束。

歸併排序的思想:如果要排序乙個陣列,我們先把陣列從中間分成前後兩部分,然後對前後兩部分分別排序,再將排好序的兩部分合併在一起,這樣整個陣列就都有序了。

歸併的空間複雜度是o(n),但歸併排序的時間複雜度不論什麼情況都是o(nlogn),而快排時間複雜度在極端情況下時間複雜度可能是o(n^2)

歸併排序和快排思想上的差別:

乙個歸併排序的實現:

public

class

solution

public

static

void

sort

(int

a,int low,

int high)

int mid =

(low + high)/2

;sort

(a, low, mid)

;sort

(a, mid +

1, high)

;merge

(a, low, mid, high);}

public

static

void

merge

(int

a,int low,

int mid,

int high)

for(

int k = low; k <= high; k++

)else

if(j > high)

else

if(aux[j]

< aux[i]

)else}}

}

堆排序是原地排序演算法,時間複雜度為o(nlogn),但在實際的軟體開發中,不如快速排序的效能,原因是堆本身是一種樹狀結構,維持這個結構也會額外消耗一些效能。

堆排序的過程大致分解成兩個大的步驟,建堆和排序。

建堆時間複雜度:o(n),排序時間複雜度:o(nlogn)。

從後往前的堆化:

private

static

void

buildheap

(int

a,int n)

}private

static

void

heapify

(int

a,int n,

int i)

}

排序過程時間複雜度是o(nlogn)。

桶排序(bucket sort)或所謂的箱排序,是乙個排序演算法,工作的原理是將陣列分到有限數量的桶裡。每個桶再分別排序。當要被排序的陣列內的數值是均勻分配的時候,桶排序使用線性時間o(n)。但是如果所有的數都被分到了乙個桶中,相當於對該桶的所有數又要進行排序。又回到了前面幾種排序演算法。而且桶排序耗空間。原因在於如果我們知道資料的範圍,我們準備桶的數量要根據(max-min)/ 單個桶包含資料範圍 來判斷桶的數量。

jdk1.8中的arrays.sort排序,在元素小於47的時候用插入排序,大於47小於286用雙軸快排,大於286用timsort歸併排序,並在timesort中記錄資料的連續的有序段的的位置,若有序段太多,也就是說資料近乎亂序,則用雙軸快排,當然快排的遞迴呼叫的過程中,若排序的子陣列資料數量小,用插入排序。

排序演算法總結

1 直接插入排序 1 穩定性 穩定 2 適用情況 待排記錄規模較小,或者記錄已經基本有序 2 希爾排序 1 穩定性 不穩定 2 特點 希爾排序的執行時間依賴於增量序列,它的效率比直接插入排序有較大的改進。3 氣泡排序 1 穩定性 穩定 2 特點 當待排記錄基本有序是,氣泡排序是不錯的選擇 但由於氣泡...

排序演算法總結

1 選擇排序 選擇排序的思想是依次從待排序數列中選擇最大 小 的 第二大 小 的等等,然後依次重新排列為有序數列。void selectionsort int a,int n if min i 時間複雜度o n 2 2 歸併排序 void merge int a,int left,int mid,i...

排序演算法總結

學習了這麼多的排序演算法,還沒有做個總結,呵呵 氣泡排序 氣泡排序是最慢的排序演算法。在實際運用中它是效率最低的演算法。它通過一趟又一趟地比較陣列中的每乙個元素,使較大的資料下沉,較小的資料上公升。它是 o n 2 的演算法。快速排序 快速排序是乙個就地排序,分而治之,大規模遞迴的演算法。從本質上來...