插入、冒泡、歸併、堆排序、快排總結
1、插入排序
public static void insertionSort(int[] array) {
int l = array.length;
int j;
for (int i = 1; i < l; i++) {
int temp = array[i];
//temp <= array[j - 1] 會因為等於多一次比較
for (j = i; j > 0 && temp < array[j - 1]; j--)
array[j] = array[j - 1];
array[j] = temp;
}
}
空間消耗 O(1) (臨時儲存array[i])
平均時間複雜度 O(n^2)
最好情況 O(n) (已經排序,內層for迴圈的檢測總是立即判斷不成立而終止)
最壞情況 O(n^2) (需排序的為逆序)
如果目標是把n個元素的序列升序排列,那麼採用插入排序存在最好情況和最壞情況。最好情況就是,序列已經是升序排列了,在這種情況下,需要進行的比較操作需(n-1)次即可。最壞情況就是,序列是降序排列,那麼此時需要進行的比較共有n(n-1)/2次。插入排序的賦值操作是比較操作的次數加上 (n-1)次。平均來說插入排序演算法的時間複雜度為O(n^2)。
(可以使用二分查詢法進行優化)
2、氣泡排序
氣泡排序(Bubble Sort,臺灣譯為:泡沫排序或氣泡排序)是一種簡單的排序演算法。它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個演算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。
可以是將最小的冒上去,或者最大的沉下去。
//改進後的,增加了一個判斷標誌
public static void bubbleSort(int[] array) {
boolean flag = true;
//若flag 為false表明剩下的序列是有序的了
for (int i = 0; i < array.length && flag; i++) {
flag = false;
for (int j = array.length - 1; j > i; j--) {
if (array[j] < array[j - 1]) {
int temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
flag = true;//表明有資料交換
}
}
}
空間消耗 O(1) (用於交換相鄰資料)
平均時間複雜度 O(n^2)
最好情況 O(n) (改進後的,避免對已經有序的序列重複進行迴圈比較,未改進的為O(n^2) )
最壞情況 O(n^2) (需排序的為逆序)
當最好情況下,即需排序的陣列本身是有序的,根據改進的程式碼。可以推斷出有n-1次比較,沒有資料交換。當最壞的時候,即需排序的陣列本身是逆序的,此時需要比較n(n-1)/2次,並做等量數量級的記錄移動。
3、歸併排序
(1)遞迴實現
public static void mergeSort(int[] array) {
int[] tempArr = new int[array.length];
mergeSort(array, tempArr, 0, array.length - 1);
}
private static void mergeSort(int[] array, int[] tempArr, int left, int right) {
if (left < right) {
int center = (left + right) / 2;
//遞迴將左邊的歸併為有序
mergeSort(array, tempArr, left, center);
//遞迴將右邊的歸併為有序
mergeSort(array, tempArr, center + 1, right);
//將左右兩個子序列歸併到一起
merge(array, tempArr, left, center + 1, right);
}
}
private static void merge(int[] array, int[] tempArr, int leftPos, int rightPos, int rightEnd) {
int leftEnd = rightPos - 1, tmpPos = leftPos, num = rightEnd - leftPos + 1;
while (leftPos <= leftEnd && rightPos <= rightEnd) {
if (array[leftPos] < array[rightPos]) tempArr[tmpPos++] = array[leftPos++];
else tempArr[tmpPos++] = array[rightPos++];
// tempArr[tmpPos++] = array[array[leftPos] < array[rightPos] ? leftPos++ : rightPos++];
}
while (leftPos <= leftEnd)
tempArr[tmpPos++] = array[leftPos++];
while (rightPos <= rightEnd)
tempArr[tmpPos++] = array[rightPos++];
for (int i = 0; i < num; i++, rightEnd--)
array[rightEnd] = tempArr[rightEnd];
}
空間消耗 O(n+log n)
平均時間複雜度 O(n log n)
最好情況 O(n log n)
最壞情況 O(n log n)
(2)非遞迴實現
public static void mergeSort(int[] arr) {
int len = arr.length;
int k = 1;
while(k < len)
{
mergePass(arr, k, len);
k *= 2;
}
}
//mergePass方法負責將陣列中的相鄰的有k個元素的序列進行歸併
private static void mergePass(int[] arr, int k, int n) {
int i = 0;
//從前往後,將2個長度為k的子序列合併為1個
//n - 2*k + 1中加 1 的原因是陣列的下表是從 0 開始的
//且需要保證兩兩合併的序列(非落單的序列)長度為k
while(i < n - 2*k + 1)
{
merge(arr, i, i + k-1, i + 2*k - 1);
i += 2*k;
}
//這段程式碼保證了,將那些“落單的”長度不足兩兩merge的部分和前面merge起來。
if(i < n - k )
{
merge(arr, i, i+k-1, n-1);
}
}
//merge函式實際上是將兩個有序陣列合併成一個有序陣列
private static void merge(int[] arr, int low, int mid, int high) {
//temp陣列用於暫存合併的結果
int[] temp = new int[high - low + 1];
int i = low;
int j = mid+1;
int k = 0;
//將記錄由小到大地放進temp陣列
for(; i <= mid && j <= high; k++)
{
if(arr[i] < arr[j])
temp[k] = arr[i++];
else
temp[k] = arr[j++];
}
//接下來兩個while迴圈是為了將剩餘的(比另一邊多出來的個數)放到temp陣列中
while(i <= mid)
temp[k++] = arr[i++];
while(j <= high)
temp[k++] = arr[j++];
//將temp陣列中的元素寫入到待排陣列中
for(int l = 0; l < temp.length; l++)
arr[low + l] = temp[l];
}
空間消耗 O(n)
避免了遞迴時需要的深度為 log n 的棧空間
4、堆排序
堆排序(Heapsort)是指利用堆這種資料結構所設計的一種排序演算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
在下面的篇部落格中說得挺好的,具體的實現細節可以參考,但是是用JS實現程式碼的:
http://bubkoo.com/2014/01/14/sort-algorithm/heap-sort/
public static void heapSort(int[] arr) {
//構建初始最大堆
for (int i = arr.length/2-1; i >=0; i--) {
buildMaxHeap(arr,i,arr.length);
}
for (int i = arr.length-1; i > 0 ; i--) {
//交換堆頂最大值和最後一個葉子結點
int tmp = arr[0];
arr[0] = arr[i];
arr[i] = tmp;
//重新構建剩下的序列的最大堆
buildMaxHeap(arr,0,i);
}
}
//構建最大堆
public static void buildMaxHeap(int[] array,int index,int heapSize) {
int iMax, iLeft, iRight;
while (true) {
iMax = index;
iLeft = 2 * index + 1;
iRight = 2 * (index + 1);
if (iLeft < heapSize && array[index] < array[iLeft]) {
iMax = iLeft;
}
if (iRight < heapSize && array[iMax] < array[iRight]) {
iMax = iRight;
}
if (iMax != index) {
int tmp = array[iMax];
array[iMax] = array[index];
array[index] = tmp;
index = iMax;
} else {
break;
}
}
}
空間消耗 O(1)
平均時間複雜度 O(n log n)
最好情況 O(n log n)
最壞情況 O(n log n)
堆排序複雜度分析:
它的執行時間主要是消耗在初始構建堆和在重建堆時的反覆篩選上。
在構建堆的過程中,因為我們是完全二叉樹從最下層最右邊的非終端結點開始構建,將它與其孩子進行比較和若有必要的互換,對於每個非終端結點來說,其實最多進行兩次比較和互換操作,因此整個構建堆的時間複雜度為O(n)。
在正式排序時,第i次取堆頂記錄重建堆需要用O(logi)的時間(完全二叉樹的某個結點到根結點的距離為⌊log2i⌋+1),並且需要取n-1次堆頂記錄,因此,重建堆的時間複雜度為O(nlogn)。
所以總體來說,堆排序的時間複雜度為O(nlogn)。由於堆排序對原始記錄的排序狀態並不敏感,因此它無論是最好、最壞和平均時間複雜度均為O(nlogn)。這在效能上顯然要遠遠好過於冒泡、簡單選擇、直接插入的O(n2)的時間複雜度了。
空間複雜度上,它只有一個用來交換的暫存單元,也算是非常的不錯。不過由於記錄的比較與交換是跳躍式進行,因此堆排序也是一種不穩定的排序方法。
另外,由於初始構建堆所需的比較次數較多,因此,它並不適合待排序序列個數較少的情況。
5、快速排序
快速排序(Quicksort)是對氣泡排序的一種改進。它的基本思想是:通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。
參考部落格:http://bubkoo.com/2014/01/12/sort-algorithm/quick-sort/
public static void quickSort(int[] arr) {
sort(arr,0,arr.length-1);
}
public static void sort(int[] array,int left,int right) {
if (left > right) {
return;
}
int storeIndex = partition(array, left, right);
sort(array, left, storeIndex - 1);
sort(array, storeIndex + 1, right);
}
private static int partition(int[] array, int left, int right) {
int storeIndex = left;
int pivot = array[right]; // 直接選最右邊的元素為基準元素
for (int i = left; i < right; i++) {
if (array[i] < pivot) {
swap(array, storeIndex, i);
storeIndex++; // 交換位置後,storeIndex 自增 1,代表下一個可能要交換的位置
}
}
swap(array, right, storeIndex); // 將基準元素放置到最後的正確位置上
//之後,以基準元素為分界點,左邊是小於它的,右邊是大於等於它的
return storeIndex;
}
private static void swap(int[] array, int i, int k) {
int temp = array[i];
array[i] = array[k];
array[k] = temp;
}
空間消耗 O(log n)
平均時間複雜度 O(n log n)
最好情況 O(n log n)
最壞情況 O(n^2)
最壞情況發生在每次劃分過程產生的兩個區間分別包含n-1個元素和1個元素的時候(設輸入的表有n個元素)。
最好情況為如果每次劃分過程產生的區間大小都為n/2。
相關文章
- 利用java實現插入排序、歸併排序、快排和堆排序Java排序
- 單連結串列的冒泡,快排,選擇,插入,歸併等多圖詳解
- 常見的排序演算法:冒泡、快排、歸併排序演算法
- 排序法:選擇、冒泡、插入和快排排序
- 前端也能學演算法:JS版常見排序演算法-冒泡,插入,快排,歸併前端演算法JS排序
- (建議收藏)2020最新排序演算法總結:冒泡、選擇、插入、希爾、快速、歸併、堆排序、基數排序排序演算法
- php插入排序,快速排序,歸併排序,堆排序PHP排序
- 幾大排序總結(上)!圖解解析+程式碼例項(冒泡、選擇、插入、希爾、快排)排序圖解
- 十種排序演算法總結(冒泡、插入、選擇、希爾、歸併、堆、快速,計數,桶,基數)排序演算法
- 排序演算法Python(冒泡、選擇、快速、插入、希爾、歸併排序)排序演算法Python
- 演算法(氣泡排序,快排,歸併排序)演算法排序
- 冒泡、選擇、快排、插入排序—效能簡單測試/rand()邊界值——c++資料結構排序C++資料結構
- js冒泡、快排的簡單寫法JS
- C語言排序 冒泡 選擇 快排C語言排序
- ACW--基礎語法1(快排,歸併,二分)
- 氣泡排序 插入排序 快排排序
- 《演算法筆記》3. 歸併排序、隨機快排整理演算法筆記排序隨機
- 單連結串列的排序(插入,選擇,冒泡)排序
- 單連結串列增刪改查和歸併插入排序排序
- 排序演算法總結之歸併排序排序演算法
- 七、排序,選擇、冒泡、希爾、歸併、快速排序實現排序
- O(lgn)的三種排序,快速排序、歸併排序、堆排序排序
- 【資料結構】快排!!!資料結構
- 排序演算法總結之堆排序排序演算法
- 連結串列歸併排序排序
- 【演算法】排序02——歸併排序介紹及其在分治演算法思想上與快排的區別(含歸併程式碼)演算法排序
- 淺談歸併排序:合併 K 個升序連結串列的歸併解法排序
- CPU快取學習及C6678快取使用總結(知識歸納)快取
- 【資料結構】歸併排序!!!資料結構排序
- 【資料結構】歸併排序資料結構排序
- Java Day_6(陣列,JVM記憶體,遞迴演算法(冒泡,快排))Java陣列JVM記憶體遞迴演算法
- 排程管理大總結
- 線性迴歸總結
- 迴歸測試總結
- 單連結串列的歸併(資料結構)資料結構
- 藍橋杯 小朋友排隊 (歸併排序 逆序數 好題)排序
- [C++]歸併排序(連結串列描述)C++排序
- 資料結構 歸併排序 C++資料結構排序C++