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] < pivot
和nums[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 的演算法。快速排序 快速排序是乙個就地排序,分而治之,大規模遞迴的演算法。從本質上來...