八大排序算法總結與java實現
一、直接插入排序(Insertion Sort)
插入排序的設計初衷是往有序的數組中快速插入一個新的元素。它的算法思想是:把要排序的數組分為了兩個部分, 一部分是數組的全部元素(除去待插入的元素), 另一部分是待插入的元素; 先將第一部分排序完成, 然后再插入這個元素. 其中第一部分的排序也是通過再次拆分為兩部分來進行的.
1、 基本思想
直接插入排序的基本思想是:將數組中的所有元素依次跟前面已經排好的元素相比較,如果選擇的元素比已排序的元素小,則交換,直到全部元素都比較過為止。
2、 算法描述
一般來說,插入排序都采用in-place在數組上實現。具體算法描述如下:
① 從第一個元素開始,該元素可以認為已經被排序
② 取出下一個元素,在已經排序的元素序列中從后向前掃描
③ 如果該元素(已排序)大於新元素,將該元素移到下一位置
④ 重復步驟3,直到找到已排序的元素小於或者等於新元素的位置
⑤ 將新元素插入到該位置后
⑥ 重復步驟② ~ ⑤
如果比較操作的代價比交換操作大的話,可以采用二分查找法來減少比較操作的數目。該算法可以認為是插入排序的一個變種,稱為二分查找插入排序。
3、 代碼實現
public static void insertionSort(int[] arr){ for( int i=0; i<arr.length-1; i++ ) { for( int j=i+1; j>0; j-- ) { if( arr[j-1] <= arr[j] ) break; int temp = arr[j]; //交換操作 arr[j] = arr[j-1]; arr[j-1] = temp; System.out.println("Sorting: " + Arrays.toString(arr)); } } }
平均時間復雜度 |
最好情況 |
最壞情況 |
空間復雜度 |
O(n²) |
O(n²) |
O(n²) |
O(1) |
Tips: 由於直接插入排序每次只移動一個元素的位,並不會改變值相同的元素之間的排序,因此它是一種穩定排序。
二、希爾排序(Shell Sort)
希爾排序是先將整個待排序的記錄序列分割成為若干子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行依次直接插入排序。
1、 基本思想
將待排序數組按照步長gap進行分組,然后將每組的元素利用直接插入排序的方法進行排序;每次再將gap折半減小,循環上述操作;當gap=1時,利用直接插入,完成排序。
可以看到步長的選擇是希爾排序的重要部分。只要最終步長為1任何步長序列都可以工作。一般來說最簡單的步長取值是初次取數組長度的一半為增量,之后每次再減半,直到增量為1。
2、 算法描述
① 選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;(一般初次取數組半長,之后每次再減半,直到增量為1)
② 按增量序列個數k,對序列進行k 趟排序;
③ 每趟排序,根據對應的增量ti,將待排序列分割成若干長度為m 的子序列,分別對各子表進行直接插入排序。僅增量因子為1時,整個序列作為一個表來處理,表長度即為整個序列的長度。
3、 代碼實現
public static void shellSort(int[] arr){ int gap = arr.length / 2; for (; gap > 0; gap /= 2) { //不斷縮小gap,直到1為止 for (int j = 0; (j+gap) < arr.length; j++){ //使用當前gap進行組內插入排序 for(int k = 0; (k+gap)< arr.length; k += gap){ if(arr[k] > arr[k+gap]) { int temp = arr[k+gap]; //交換操作 arr[k+gap] = arr[k]; arr[k] = temp; System.out.println("Sorting: " + Arrays.toString(arr)); } } } } }
① 第一層for循環表示一共有多少個增量。增量的序列的個數,就是希爾排序的趟數。上面的增量序列為: arr.length/2, arr.length/2/2, arr.length/2/2/2, .... 2, 1
② 里層的兩個for循環,實際上就是以一個gap拆分為一組的組內插入排序。
平均時間復雜度 |
最好情況 |
最壞情況 |
空間復雜度 |
O(nlog2 n) |
O(nlog2 n) |
O(nlog2 n) |
O(1) |
三、選擇排序(Selection Sort)
從算法邏輯上看,選擇排序是一種簡單直觀的排序算法,在簡單選擇排序過程中,所需移動記錄的次數比較少。
1、 基本思想
選擇排序的基本思想:比較 + 交換。
在未排序序列中找到最小(大)元素,存放到未排序序列的起始位置。在所有的完全依靠交換去移動元素的排序方法中,選擇排序屬於非常好的一種。
2、 算法描述
① 從待排序序列中,找到關鍵字最小的元素;
② 如果最小元素不是待排序序列的第一個元素,將其和第一個元素互換;
③ 從余下的 N - 1 個元素中,找出關鍵字最小的元素,重復① 、② 步,直到排序結束。
3、 代碼實現
public static void selectionSort(int[] arr){ for(int i = 0; i < arr.length-1; i++){ int min = i; for(int j = i+1; j < arr.length; j++){ //選出之后待排序中值最小的位置 if(arr[j] < arr[min]){ min = j; } } if(min != i){ int temp = arr[min]; //交換操作 arr[min] = arr[i]; arr[i] = temp; System.out.println("Sorting: " + Arrays.toString(arr)); } } }
平均時間復雜度 |
最好情況 |
最壞情況 |
空間復雜度 |
O(n²) |
O(n²) |
O(n²) |
O(1) |
選擇排序的簡單和直觀名副其實,這也造就了它”出了名的慢性子”,無論是哪種情況,哪怕原數組已排序完成,它也將花費將近n²/2次遍歷來確認一遍。即便是這樣,它的排序結果也還是不穩定的。 唯一值得高興的是,它並不耗費額外的內存空間。
四、堆排序(Heap Sort)
堆的定義如下:個元素的序列當且僅當滿足下關系時,稱之為堆。
把此序列對應的二維數組看成一個完全二叉樹。那么堆的含義就是:完全二叉樹中任何一個非葉子節點的值均不大於(或不小於)其左,右孩子節點的值。由上述性質可知大頂堆的堆頂的關鍵字肯定是所有關鍵字中最大的,小頂堆的堆頂的關鍵字是所有關鍵字中最小的。因此我們可使用大頂堆進行升序排序, 使用小頂堆進行降序排序。
1、 基本思想
此處以大頂堆為例,堆排序的過程就是將待排序的序列構造成一個堆,選出堆中最大的移走,再把剩余的元素調整成堆,找出最大的再移走,重復直至有序。
2、 算法描述
① 先將初始序列建成一個大頂堆, 那么此時第一個元素最大, 此堆為初始的無序區.
②再將關鍵字最大的記錄 (即堆頂, 第一個元素)和無序區的最后一個記錄交換, 由此得到新的無序區和有序區, 且滿足
③交換和后, 堆頂可能違反堆性質, 因此需將調整為堆. 然后重復步驟②, 直到無序區只有一個元素時停止。
3、 代碼實現
從算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最后一個元素交換位置。所以堆排序有兩個函數組成。一是建堆函數,二是反復調用建堆函數以選擇出剩余未排元素中最大的數來實現排序的函數。
總結起來就是定義了以下幾種操作:
- 最大堆調整(Max_Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點。
- 創建最大堆(Build_Max_Heap):將堆所有數據重新排序。
- 堆排序(HeapSort):移除位在第一個數據的根節點,並做最大堆調整的遞歸運算。
對於堆節點的訪問:
- 父節點i的左子節點在位置:(2*i+1);
- 父節點i的右子節點在位置:(2*i+2);
- 子節點i的父節點在位置:floor((i-1)/2);
public static void heapSort(int[] arr){ for(int i = arr.length; i > 0; i--){ max_heapify(arr, i); int temp = arr[0]; //堆頂元素(第一個元素)與Kn交換 arr[0] = arr[i-1]; arr[i-1] = temp; } } private static void max_heapify(int[] arr, int limit){ if(arr.length <= 0 || arr.length < limit) return; int parentIdx = limit / 2; for(; parentIdx >= 0; parentIdx--){ if(parentIdx * 2 >= limit){ continue; } int left = parentIdx * 2; //左子節點位置 int right = (left + 1) >= limit ? left : (left + 1); //右子節點位置,如果沒有右節點,默認為左節點位置 int maxChildId = arr[left] >= arr[right] ? left : right; if(arr[maxChildId] > arr[parentIdx]){ //交換父節點與左右子節點中的最大值 int temp = arr[parentIdx]; arr[parentIdx] = arr[maxChildId]; arr[maxChildId] = temp; } } System.out.println("Max_Heapify: " + Arrays.toString(arr)); }
①. 建立堆的過程, 從length/2 一直處理到0, 時間復雜度為O(n);
②. 調整堆的過程是沿着堆的父子節點進行調整, 執行次數為堆的深度, 時間復雜度為O(lgn);
③. 堆排序的過程由n次第②步完成, 時間復雜度為O(nlgn).
平均時間復雜度 |
最好情況 |
最壞情況 |
空間復雜度 |
O(nlogn) |
O(nlogn) |
O(nlogn) |
O(1) |
由於堆排序中初始化堆的過程比較次數較多, 因此它不太適用於小序列. 同時由於多次任意下標相互交換位置, 相同元素之間原本相對的順序被破壞了, 因此, 它是不穩定的排序.
五、冒泡排序(Bubble Sort)
1、 基本思想
冒泡排序(Bubble Sort)是一種簡單的排序算法。它重復地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端。
2、 算法描述
冒泡排序算法的運作如下:
① 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
② 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對。這步做完后,最后的元素會是最大的數。
③ 針對所有的元素重復以上的步驟,除了最后一個。
④ 持續每次對越來越少的元素重復上面的步驟① ~ ③ 直到沒有任何一對數字需要比較。
3、 代碼實現
冒泡排序需要兩個嵌套的循環. 其中, 外層循環移動游標; 內層循環遍歷游標及之后(或之前)的元素, 通過兩兩交換的方式, 每次只確保該內循環結束位置排序正確, 然后內層循環周期結束, 交由外層循環往后(或前)移動游標, 隨即開始下一輪內層循環, 以此類推, 直至循環結束.
public static void bubbleSort(int[] arr){ for (int i = arr.length; i > 0; i--) { //外層循環移動游標 for(int j = 0; j < i && (j+1) < i; j++){ //內層循環遍歷游標及之后(或之前)的元素 if(arr[j] > arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; System.out.println("Sorting: " + Arrays.toString(arr)); } } } }
平均時間復雜度 |
最好情況 |
最壞情況 |
空間復雜度 |
O(n²) |
O(n) |
O(n²) |
O(1) |
冒泡排序是最容易實現的排序, 最壞的情況是每次都需要交換, 共需遍歷並交換將近n²/2次, 時間復雜度為O(n²). 最佳的情況是內循環遍歷一次后發現排序是對的, 因此退出循環, 時間復雜度為O(n). 平均來講, 時間復雜度為O(n²). 由於冒泡排序中只有緩存的temp變量需要內存空間, 因此空間復雜度為常量O(1).
由於冒泡排序只在相鄰元素大小不符合要求時才調換他們的位置, 它並不改變相同元素之間的相對順序, 因此它是穩定的排序算法.
六、快速排序(Quick Sort)
1、 基本思想
快速排序的基本思想:挖坑填數+分治法。
首先選一個軸值(pivot,也有叫基准的),通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
2、 算法描述
快速排序使用分治策略來把一個序列(list)分為兩個子序列(sub-lists)。步驟為:
① 從數列中挑出一個元素,稱為”基准”(pivot)。
② 重新排序數列,所有比基准值小的元素擺放在基准前面,所有比基准值大的元素擺在基准后面(相同的數可以到任一邊)。在這個分區結束之后,該基准就處於數列的中間位置。這個稱為分區(partition)操作。
③ 遞歸地(recursively)把小於基准值元素的子數列和大於基准值元素的子數列排序。
遞歸到最底部時,數列的大小是零或一,也就是已經排序好了。這個算法一定會結束,因為在每次的迭代(iteration)中,它至少會把一個元素擺到它最后的位置去。
3、 代碼實現
/** * 快速排序(遞歸) */ public static void quickSort(int[] arr, int low, int high){ if(arr.length <= 0) return; if(low >= high) return; int left = low; int right = high; int temp = arr[left]; //挖坑1:保存基准的值 while (left < right){ while(left < right && arr[right] >= temp){ //坑2:從后向前找到比基准小的元素,插入到基准位置坑1中 right--; } arr[left] = arr[right]; while(left < right && arr[left] <= temp){ //坑3:從前往后找到比基准大的元素,放到剛才挖的坑2中 left++; } arr[right] = arr[left]; } arr[left] = temp; //基准值填補到坑3中,准備分治遞歸快排 System.out.println("Sorting: " + Arrays.toString(arr)); quickSort(arr, low, left-1); quickSort(arr, left+1, high); }
上面是遞歸版的快速排序:通過把基准temp插入到合適的位置來實現分治,並遞歸地對分治后的兩個划分繼續快排。那么非遞歸版的快排如何實現呢?
因為遞歸的本質是棧,所以我們非遞歸實現的過程中,可以借助棧來保存中間變量就可以實現非遞歸了。在這里中間變量也就是通過Pritation函數划分區間之后分成左右兩部分的首尾指針,只需要保存這兩部分的首尾指針即可。
public static void quickSortByStack(int[] arr){ if(arr.length <= 0) return; Stack<Integer> stack = new Stack<Integer>(); //初始狀態的左右指針入棧 stack.push(0); stack.push(arr.length - 1); while(!stack.isEmpty()){ int high = stack.pop(); //出棧進行划分 int low = stack.pop(); int pivotIdx = partition(arr, low, high); //保存中間變量 if(pivotIdx > low) { stack.push(low); stack.push(pivotIdx - 1); } if(pivotIdx < high && pivotIdx >= 0){ stack.push(pivotIdx + 1); stack.push(high); } } } private static int partition(int[] arr, int low, int high){ if(arr.length <= 0) return -1; if(low >= high) return -1; int l = low; int r = high; int pivot = arr[l]; //挖坑1:保存基准的值 while(l < r){ while(l < r && arr[r] >= pivot){ //坑2:從后向前找到比基准小的元素,插入到基准位置坑1中 r--; } arr[l] = arr[r]; while(l < r && arr[l] <= pivot){ //坑3:從前往后找到比基准大的元素,放到剛才挖的坑2中 l++; } arr[r] = arr[l]; } arr[l] = pivot; //基准值填補到坑3中,准備分治遞歸快排 return l; }
快速排序是通常被認為在同數量級(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按關鍵碼有序或基本有序時,快排序反而蛻化為冒泡排序。為改進之,通常以“三者取中法”來選取基准記錄,即將排序區間的兩個端點與中點三個記錄關鍵碼居中的調整為支點記錄。快速排序是一個不穩定的排序方法。
平均時間復雜度 |
最好情況 |
最壞情況 |
空間復雜度 |
O(nlog₂n) |
O(nlog₂n) |
O(n²) |
O(1)(原地分區遞歸版) |
快速排序排序效率非常高。 雖然它運行最糟糕時將達到O(n²)的時間復雜度, 但通常平均來看, 它的時間復雜為O(nlogn), 比同樣為O(nlogn)時間復雜度的歸並排序還要快. 快速排序似乎更偏愛亂序的數列, 越是亂序的數列, 它相比其他排序而言, 相對效率更高.
Tips: 同選擇排序相似, 快速排序每次交換的元素都有可能不是相鄰的, 因此它有可能打破原來值為相同的元素之間的順序. 因此, 快速排序並不穩定.
七、歸並排序(Merging Sort)
1、 基本思想
歸並排序算法是將兩個(或兩個以上)有序表合並成一個新的有序表,即把待排序序列分為若干個子序列,每個子序列是有序的。然后再把有序子序列合並為整體有序序列。
2、 算法描述
歸並排序可通過兩種方式實現:
自上而下的遞歸
自下而上的迭代
一、遞歸法(假設序列共有n個元素):
① 將序列每相鄰兩個數字進行歸並操作,形成 floor(n/2)個序列,排序后每個序列包含兩個元素;
② 將上述序列再次歸並,形成 floor(n/4)個序列,每個序列包含四個元素;
③ 重復步驟②,直到所有元素排序完畢。
二、迭代法
① 申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並后的序列
② 設定兩個指針,最初位置分別為兩個已經排序序列的起始位置
③ 比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置
④ 重復步驟③ 直到某一指針到達序列尾
⑤ 將另一序列剩下的所有元素直接復制到合並序列尾
3、 代碼實現
歸並排序其實要做兩件事:
- 分解:將序列每次折半拆分
- 合並:將划分后的序列段兩兩排序合並
因此,歸並排序實際上就是兩個操作,拆分+合並
這是遞歸的歸並排序:
public static int[] mergingSort(int[] arr){ if(arr.length <= 1) return arr; int num = arr.length >> 1; int[] leftArr = Arrays.copyOfRange(arr, 0, num); int[] rightArr = Arrays.copyOfRange(arr, num, arr.length); System.out.println("split two array: " + Arrays.toString(leftArr) + " And " + Arrays.toString(rightArr)); return mergeTwoArray(mergingSort(leftArr), mergingSort(rightArr)); //不斷拆分為最小單元,再排序合並 } private static int[] mergeTwoArray(int[] arr1, int[] arr2){ int i = 0, j = 0, k = 0; int[] result = new int[arr1.length + arr2.length]; //申請額外的空間存儲合並之后的數組 while(i < arr1.length && j < arr2.length){ //選取兩個序列中的較小值放入新數組 if(arr1[i] <= arr2[j]){ result[k++] = arr1[i++]; }else{ result[k++] = arr2[j++]; } } while(i < arr1.length){ //序列1中多余的元素移入新數組 result[k++] = arr1[i++]; } while(j < arr2.length){ //序列2中多余的元素移入新數組 result[k++] = arr2[j++]; } System.out.println("Merging: " + Arrays.toString(result)); return result; }
由上, 長度為n的數組, 最終會調用mergeSort函數2n-1次。通過自上而下的遞歸實現的歸並排序, 將存在堆棧溢出的風險。
//MergePass方法負責將數組中的相鄰的有k個元素的字序列進行歸並 private static void MergePass(int[] arr, int k, int n) { int i = 0; int j; //從前往后,將2個長度為k的子序列合並為1個 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); } }
平均時間復雜度 |
最好情況 |
最壞情況 |
空間復雜度 |
O(nlog₂n) |
O(nlog₂n) |
O(nlog₂n) |
O(n) |
從效率上看,歸並排序可算是排序算法中的”佼佼者”. 假設數組長度為n,那么拆分數組共需logn,, 又每步都是一個普通的合並子數組的過程, 時間復雜度為O(n), 故其綜合時間復雜度為O(nlogn)。另一方面, 歸並排序多次遞歸過程中拆分的子數組需要保存在內存空間, 其空間復雜度為O(n)。
和選擇排序一樣,歸並排序的性能不受輸入數據的影響,但表現比選擇排序好的多,因為始終都是O(n log n)的時間復雜度。代價是需要額外的內存空間。
八、基數排序(Radix Sort)
基數排序(Radix sort)是一種非比較型整數排序算法,其原理是將整數按位數切割成不同的數字,然后按每個位數分別比較。由於整數也可以表達字符串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是只能使用於整數。
1、 基本思想
它是這樣實現的:將所有待比較數值(正整數)統一為同樣的數位長度,數位較短的數前面補零。然后,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以后,數列就變成一個有序序列。
基數排序按照優先從高位或低位來排序有兩種實現方案:
MSD(Most significant digital)從最左側高位開始進行排序。先按k1排序分組, 同一組中記錄, 關鍵碼k1相等, 再對各組按k2排序分成子組, 之后, 對后面的關鍵碼繼續這樣的排序分組, 直到按最次位關鍵碼kd對各子組排序后. 再將各組連接起來, 便得到一個有序序列。MSD方式適用於位數多的序列。
LSD (Least significant digital)從最右側低位開始進行排序。先從kd開始排序,再對kd-1進行排序,依次重復,直到對k1排序后便得到一個有序序列。LSD方式適用於位數少的序列。
2、 算法描述
我們以LSD為例,從最低位開始,具體算法描述如下:
① 取得數組中的最大數,並取得位數;
② arr為原始數組,從最低位開始取每個位組成radix數組;
③ 對radix進行計數排序(利用計數排序適用於小范圍數的特點);
3、 代碼實現
基數排序:通過序列中各個元素的值,對排序的N個元素進行若干趟的“分配”與“收集”來實現排序。
分配:我們將L[i]中的元素取出,首先確定其個位上的數字,根據該數字分配到與之序號相同的桶中
收集:當序列中所有的元素都分配到對應的桶中,再按照順序依次將桶中的元素收集形成新的一個待排序列L[]。對新形成的序列L[]重復執行分配和收集元素中的十位、百位…直到分配完該序列中的最高位,則排序結束
public static void radixSort(int[] arr){ if(arr.length <= 1) return; //取得數組中的最大數,並取得位數 int max = 0; for(int i = 0; i < arr.length; i++){ if(max < arr[i]){ max = arr[i]; } } int maxDigit = 1; while(max / 10 > 0){ maxDigit++; max = max / 10; } System.out.println("maxDigit: " + maxDigit); //申請一個桶空間 int[][] buckets = new int[10][arr.length]; int base = 10; //從低位到高位,對每一位遍歷,將所有元素分配到桶中 for(int i = 0; i < maxDigit; i++){ int[] bktLen = new int[10]; //存儲各個桶中存儲元素的數量 //分配:將所有元素分配到桶中 for(int j = 0; j < arr.length; j++){ int whichBucket = (arr[j] % base) / (base / 10); buckets[whichBucket][bktLen[whichBucket]] = arr[j]; bktLen[whichBucket]++; } //收集:將不同桶里數據挨個撈出來,為下一輪高位排序做准備,由於靠近桶底的元素排名靠前,因此從桶底先撈 int k = 0; for(int b = 0; b < buckets.length; b++){ for(int p = 0; p < bktLen[b]; p++){ arr[k++] = buckets[b][p]; } } System.out.println("Sorting: " + Arrays.toString(arr)); base *= 10; } }
平均時間復雜度 |
最好情況 |
最壞情況 |
空間復雜度 |
O(d*(n+r)) |
O(d*(n+r)) |
O(d*(n+r)) |
O(n+r) |
其中,d 為位數,r 為基數,n 為原數組個數。在基數排序中,因為沒有比較操作,所以在復雜上,最好的情況與最壞的情況在時間上是一致的,均為 O(d*(n + r))。
基數排序更適合用於對時間, 字符串等這些整體權值未知的數據進行排序。
Tips: 基數排序不改變相同元素之間的相對順序,因此它是穩定的排序算法。
基數排序 vs 計數排序 vs 桶排序
這三種排序算法都利用了桶的概念,但對桶的使用方法上有明顯差異:
- 基數排序:根據鍵值的每位數字來分配桶
- 計數排序:每個桶只存儲單一鍵值
- 桶排序:每個桶存儲一定范圍的數值
總結
從時間復雜度來說:
(1). 平方階O(n²)排序:各類簡單排序:直接插入、直接選擇和冒泡排序;
(2). 線性對數階O(nlog₂n)排序:快速排序、堆排序和歸並排序;
(3). O(n1+§))排序,§是介於0和1之間的常數:希爾排序
(4). 線性階O(n)排序:基數排序
當原表有序或基本有序時,直接插入排序和冒泡排序將大大減少比較次數和移動記錄的次數,時間復雜度可降至O(n);
而快速排序則相反,當原表基本有序時,將蛻化為冒泡排序,時間復雜度提高為O(n2);
原表是否有序,對簡單選擇排序、堆排序、歸並排序和基數排序的時間復雜度影響不大。