常用的排序方法整理
常用的排序方法
排序方法 |
平均時間複雜度 | 時間複雜度(最好) | 時間複雜度(最壞) | 空間複雜度 | 穩定性 |
氣泡排序(BubbleSort) | O(n²) | O(n) | O(n²) | O(1) | 穩定 |
直接選擇排序(SectionSort) | O(n²) | O(n²) | O(n²) | O(1) | 不穩定 |
直接插入排序(InsertSort) | O(n²) | O(n) | O(n²) | O(1) | 穩定 |
希爾排序(ShellSort) | O(nlog₂n) | O(n) | O(n²) | O(1) | 不穩定 |
快速排序(QuickSort) | O(nlog₂n) | O(nlog₂n) | O(n²) | O(nlog₂n) | 不穩定 |
歸併排序(MergeSort) | O(nlog₂n) | O(nlog₂n) | O(nlog₂n) | O(n) | 穩定 |
堆排序(HeapSort) | O(nlog₂n) | O(nlog₂n) | O(nlog₂n) | O(1) | 不穩定 |
桶排序(BucketSort) /基數排序(RadixSort)/ 計數排序(CountSort) |
O(d(n+m)) |
O(d(n+m)) | O(d(n+m)) | O(n+m) | 穩定 |
Tips: d(n+m)=n+n*(logN-logM);
其中,演算法的穩定性是指,當陣列中存在兩個相同的資料,經過排序之後兩個資料的位置是否發生相對改變。
1、氣泡排序(BubbleSort):
1:將每個數與下一個數進行比較,如果大於,則兩者交換位置;(經過1次迴圈之後,最大數位於陣列末端)
2:對剩餘的 n - 1個數,重複第一步操作,經過n次迴圈之後,陣列達到有序。
Tips:1. 可以將整個過程理解成泡泡的上升,最大的泡泡浮得最快,位於陣列末端,
2.最好的情況是陣列基本有序,最壞的情況是陣列基本逆序。
public class BubbleSort {
public static void bubbleSort(int[] arr){
if(arr == null || arr.length <2){
return;
}
for(int i = arr.length - 1; i > 0; i--){//迴圈N次
for(int j = 0; j < i; j++){
if(arr[j] > arr[j+1]){
SwapArr.swap(arr, j, j+1);
}
}
}
}
}
2、直接選擇排序(SectionSort)
1:遍歷整個陣列,找到其中最小的數,並與陣列最前端的數交換位置(初始為0,經過一次遍歷,最小數位於最前端);
2:從1位置繼續遍歷,重複第1步操作,迴圈n次,最終陣列達到有序。
Tips: 每次都在數組裡找最小的數放到前面(已經排好序的陣列後面)
public class SectionSort {
public static void sectionSort(int[] arr){
if(arr == null || arr.length <2){
return;
}
for(int i = 0; i < arr.length - 1; i++){
int minIndex = i;
for(int j = i + 1; j < arr.length; j++){
if(arr[minIndex] > arr[j]){
minIndex = j;
}
}
SwapArr.swap(arr, i, minIndex);
}
}
}
3、直接插入排序(InsertSort)
1:從1位置開始,與前面的數進行比較,如果小於前面的數則交換位置,直到不再小於前面的數;
2:從2位置開始,重複第1步,直到達到最後一個位置。
Tips:1.從第二個數開始,做插入操作(插入的位置在小於自己的數之後,大於自己的數之前),最終達到有序,
2.最好的情況是陣列基本有序,最壞的情況是陣列基本逆序。
public class InsertionSort {
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) {
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
SwapArr.swap(arr, j, j+1);
}
}
}
}
4、希爾排序(ShellSort)
1:首先確定一個步長 k,根據步長把陣列分為(n/k)個部分進行排序;
2:縮短步長,繼續細分待排序的陣列;
3:直到步長縮短為1,此時陣列達到有序。
Tips:1.過程類似於插入排序,但是通過將待排陣列分為若干個子序列,減少移動次數,是插入排序的一種優化,
2.常用的初始步長是 (n / 2)。
3.最好的情況是資料基本有序。
public class ShellSort {
public static void shellSort(int[] arr) {
if (arr == null || arr.length < 2)
return;
int gap = arr.length / 2;
for (; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
for (int j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j -= gap) {
SwapArr.swap(arr, j, j+gap);
}
}
}
}
public static void shellSort(int[] arr, int n) {
if (arr == null || arr.length < 2)
return;
int gap = n / 2;
for (; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
for (int j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j -= gap) {
SwapArr.swap(arr, j, j+gap);
}
}
}
}
}
5、快速排序(QuickSort)
1:隨機選出一個哨兵partition(樞軸 pivot),把大於partition的值放在右邊,小於partition的值放在左邊;
(partition一般取最左邊的數,也可隨機生成)
2:從partition左右兩邊分割,遞迴呼叫自身;
3:當達到遞迴結束條件是,陣列達到有序狀態。
Tips:1.為防止出現最壞情況(即所取的哨兵(樞軸)為最大數或最小數),可以隨機生成哨兵(樞軸),但會增加開銷
2.當陣列基本有序時,快排將退化為氣泡排序。
public class QuickSort {
public static void quickSort(int[] arr){
if(arr.length>0)
quickSort(arr, 0, arr.length -1);
}
public static void quickSort(int[] arr, int left,int right){
if(left > right){
return;
}
int i = left;
int j = right;
int partition = arr[left];
while(i < j){
while(j > i && arr[j] > partition){
j--;
}
while(j > i && arr[i] <= partition){
i++;
}
if(i<j){
SwapArr.swap(arr, i, j);
}
}
SwapArr.swap(arr, i, left);
quickSort(arr, left, i-1);
quickSort(arr,i+1, right);
}
}
6、歸併排序(MergeSort)
1:把資料分為兩個部分,分別進行排序,最後合併;
2:遞迴呼叫自身,以實現排序。
Tips:1.將待排陣列分為若干個有序的子序列,再進行合併操作,使其達到有序狀態。
public class MergeSort {
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
mergeSort(arr, 0, arr.length - 1);
}
public static void mergeSort(int[] arr, int left, int right) {
if (left == right) {
return;
}
int mid = left + ((right - left) / 2);
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
private static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int [right - left + 1];
int l = left;
int r = mid + 1;
int index = 0;
while(l <= mid && r <= right){
temp[index++] = arr[l] < arr[r] ? arr[l++] :arr[r++];
}
while(l <= mid){
temp[index++] = arr[l++];
}
while(r <= right){
temp[index++] = arr[r++];
}
for(int i = 0; i < temp.length; i++){
arr[left+i] = temp[i];
}
}
}
7、堆排序(HeapSort)
1:將陣列構造成一個大頂堆;
2:將堆頂的資料與最後一個數進行交換,然後調整剩下的(n - 1)個數據,使其仍為大頂堆;
3:最後實現排序。
Tips:1.大頂堆指根結點大於葉子結點的完全二叉樹,整個演算法的實現在於如何構建大頂堆和如何調整,
2.完全二叉樹中,{(n - 1)/ 2 } 可以找到葉子結點的根結點在陣列中的索引,(n * 2 + 1)為根結點的左子樹結點在陣列中的結點位置,再加 1 則為右結點。
public class HeapSort {
private static void insertHeap(int[] arr, int i) {//構建大頂堆
while (arr[i] > arr[(i - 1) / 2]) {
SwapArr.swap(arr, i, (i - 1) / 2);
i = (i - 1) / 2;
}
}
private static void heapAdjust(int[] arr,int index, int size){
int left = index * 2 + 1;
while(left < size){
int largest = (left+1) < size && arr[left] < arr[left+1] ? left+1: left;
if(arr[index] > arr[largest]){
return;
}
SwapArr.swap(arr, index, largest);
index = largest;
left = index * 2 +1;
}
}
public static void heapSort(int[] arr){
if(arr == null || arr.length < 2){
return ;
}
for(int i = 1; i < arr.length; i++){ //將陣列整理為大頂堆
insertHeap(arr, i);
}
int size = arr.length - 1;
while (size > 0){
SwapArr.swap(arr, 0, size);//交換堆頂和最後一位
heapAdjust(arr, 0, size--);//重新整理陣列形成大頂堆
}
}
}
8、桶排序(BucketSort)/基數排序(RadixSort)/計數排序(CountSort)
1:桶排序的實現方法為:找到陣列中的最大數max,建立(max + 1) 個“桶”(陣列),遍歷一遍待排序的陣列,將資料放入對應的“桶”,最後按順序遍歷一遍所有“桶”,將有資料的桶的編號按順序放入陣列中,最後陣列達到有序。
2:基數排序的實現方法有兩種:
MSD(最高位優先 Most Significant Digit first),找到陣列中資料最高位,對最高位進行排序,依次進行到個位數。
LSD(最低位優先 Least Significant Digit first),從個位數開始排序,直到最高位結束。
桶排序:
public class BucketSort {
public static void bucketSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int max = Integer.MIN_VALUE;
for (int i : arr) {
max = Math.max(max, i);
}
int[] bucket = new int[max + 1];
for(int i : arr){
bucket[i] ++;
}
int i = 0;
for(int j = 0; j < bucket.length;j++){
while(bucket[j]-- >0){
arr[i++] = j;
}
}
}
}
基數排序:
public class RadixSort {
public static void radixSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
radixSort(arr, 0, arr.length - 1, maxbit(arr));
}
private static void radixSort(int[] arr, int begin, int end, int digit) {
int[] bucket = new int[end - begin + 1];
final int radix = 10;
int i, j = 0;
int[] count = new int[radix];
for (int d = 1; d <= digit; d++) {
for(i = 0; i < radix ; i++){
count[i] = 0;
}
for (i = begin; i <= end; i++) {
j = getDigit(arr[i], d);
count[j]++;
}
for (i = 1; i < radix; i++) {//count用來歸位陣列,此處加上前一個是往後增加
count[i] += count[i - 1];
}
for (i = end; i >= begin; i--) {
j = getDigit(arr[i], d);
bucket[count[j] - 1] = arr[i];
count[j] -- ;
}
for(i = begin ,j = 0; i<= end;i++,j++){
arr[i] = bucket[j];
}
}
}
private static int maxbit(int[] arr) {
int max = Integer.MIN_VALUE;
int res = 0;
for (int i : arr) {
max = Math.max(max, i);
}
while (max != 0) {
res++;
max /= 10;
}
return res;
}
private static int getDigit(int num, int d) {
return (num / (int) Math.pow(10, d - 1) % 10);
}
}