排序算法比較與總結

一、插入排序

1. 核心思想

共進行 N - 1 趟排序,對於 P = 1 到 P = N - 1 趟,保證從位置0到位置P上的元素爲排序狀態。在第P趟,將位置P上的元素移動到前P + 1個元素的正確位置上,比其更大的元素都向右移動一個位置。

2. 分析

如果輸入數據已預先排序,則時間複雜度爲O(N);平均時間複雜度和最壞時間複雜度均爲O(N^2),空間複雜度爲O(1)。插入排序只用在小的或是非常接近排好序的輸入數據上。

3. 代碼實現 

public void InsertSort(int[] arr) {
    int tmp, p, i;
    for(p = 1; p < arr.length; p++) {
        tmp = arr[p];
        for(i = p; i > 0 && arr[i - 1] > tmp; i--) {
            arr[i] = arr[i - 1];
        }
        arr[i] = tmp;
    }
}

二、選擇算法

1. 核心思想

每趟均在數組中找到第k小的元素文章,將其放置k - 1的位置,共進行N - 1趟排序。

2. 分析

無論什麼樣本數據,時間複雜度均爲O(N^2),空間複雜度爲O(1),不適合大量數據的排序。

3. 代碼實現

public 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[min] > arr[j]) {
                min = j;
            }
        }
        swap(arr, min, i);
    }
}

三、冒泡算法

1. 核心思想

每趟排序都對數組中未排序數據進行遍歷,相鄰元素兩兩比較,如果順序與預先規定的順序不一致,則交換,每次遍歷的結果都會使最大元素上浮到頂端。之後再重複操作直至數組有序,共進行N - 1趟排序。

2. 分析

當輸入數據爲正序時,時間複雜度最小,爲O(N),當輸入數據爲反序時,時間複雜度最大,爲O(N^2),平均時間複雜度爲O(N^2)。當數據量很大時,冒泡排序效率不高。

3. 代碼實現

public void bubbleSort(int[] arr) {
    for(int i = 0; i < arr.length - 1; i++) {
        for(int j = 0; j < arr.length - 1 - i; j++) {
            if(arr[j] > arr[j + 1]) {
                swap(arr, j, j + 1);
            }
        }
    }
}

四、希爾排序(縮小增量排序)

1. 核心思想

希爾排序使用一個增量序列h1,h2,...,ht,在使用增量hk的一趟排序後,對於每個i都有arr[i] <= A[i + hk],所有相隔hk的元素都被排序。

 

2. 分析

希爾排序的時間複雜度與增量序列的選取有關。使用希爾增量時希爾排序的時間複雜度的最壞時間複雜度爲O(N^2),最好時間複雜度爲O(NlogN)。

3. 代碼實現

public void shellSort(int[] arr) {
    int n = arr.length;
    for(int increment = n / 2; increment > 0; increment /= 2) {
        for(int i = increment; i < n; i++) {
            int tmp = arr[i], j;
            for(j = i; j >= increment && arr[j] < arr[j - increment]; j -= increment) {
                arr[j] = arr[j - increment];
            }
            arr[j] = tmp;
        }
    }

五、堆排序

1. 核心思想

將無序數組構造成一個大頂堆(排序後可獲得升序數組),完成後將堆頂元素與末尾元素交換,此時末尾爲最大值。然後繼續調整堆,再次將堆頂與末尾元素互換,此時可得到第二大元素,如此反覆進行交換、重建。

2. 分析

堆排序的最好、最壞、平均時間複雜度均爲O(NlogN)。

3. 代碼實現

public void heapSort(int[] arr) {
    int n = arr.length;
    for(int i = n / 2; i >= 0; i--) {
        percDown(arr, i, n); // 初始化堆
    }
    for(int i = n - 1; i > 0; i--) {
        swap(arr, 0, i);
        percDown(arr, 0, i);
    }
}

public void percDown(int[] arr, int i, int n) {
    int tmp = arr[i], child;
    for(; 2 * i + 1 < n; i = child) {
        child = 2 * i + 1;
        if(child != n - 1 && arr[child] < arr[child + 1]) child++;
        if(tmp < arr[child])
            arr[i] = arr[child];
        else
            break;
    }
    arr[i] = tmp;
}

六、歸併排序

1. 核心思想

該算法是經典的分治策略,它將無序數組分成兩個序列,分別對它們進行排序,最後合併。而拆分的子序列繼續拆分成兩個序列再分別進行排序,直至序列只有一個元素。

2. 分析:

歸併算法的最好、最壞、平均時間複雜度均爲O(NlogN),空間複雜度爲O(N),因此很難用於主存排序,因爲合併兩個排序的表時需要線性附加內存。

3. 代碼實現

public void mergeSort(int[] arr) {
    int n = arr.length;
    int[] tmp = new int[n];
    mergeSort(0, n - 1, arr, tmp);
}

public void mergeSort(int left, int right, int[] arr, int[] tmp) {
    if(left < right) {
        int mid = (left + right) / 2;
        mergeSort(left, mid, arr, tmp);
        mergeSort(mid + 1, right, arr, tmp);
        merge(left, mid, right, arr, tmp);
    }
}

public void merge(int left, int mid, int right, int[] arr, int[] tmp) {
    int k = 0, i = left, j = mid + 1;
    while(i <= mid && j <= right) {
        if(arr[i] <= arr[j])
            tmp[k++] = arr[i++];
        else
            tmp[k++] = arr[j++];
    }
    while(i <= mid) {
        tmp[k++] = arr[i++];
    }
    while(j <= right) {
        tmp[k++] = arr[j++];
    }
    for(int t = 0; t < k; t++) {
        arr[left + t] = tmp[t];
    }
}

七、快速排序

1. 核心思想

也是一種分治算法,在無序數組中選取一個元素作爲樞紐元,使得樞紐元左邊的元素均小於它,而右邊的元素均大於它,隨後繼續對兩邊的子序列進行快速排序。

2. 分析

平均時間複雜度爲O(NlogN),最壞時間複雜度爲O(N^2),但這種情況並不多見,空間複雜度爲O(logN)。

3. 代碼實現

public void quickSort(int[] arr) {
    int n = arr.length;
    quickSort(arr, 0, n - 1);
}

public void quickSort(int[] arr, int left, int right) {
    if(left >= right) {
        return;
    } else {
        int pivot = arr[left];
        int partition = partition(arr, left, right, pivot);
        quickSort(arr, left, partition);
        quickSort(arr, partition + 1, right);
    }
}

public int partition(int[] arr, int left, int right, int pivot) {
    while(left < right) {
        while (left < right && arr[right] > pivot) right--;
        swap(arr, left, right);
        while (left < right && arr[left] < pivot) left++;
        swap(arr, left, right);
    }
    return left;
}

八、總結

排序算法 平均時間複雜度 最好時間複雜度 最壞時間複雜度 空間複雜度 穩定性
插入算法 O(N^2) O(N) O(N^2) O(1) 穩定
選擇算法 O(N^2) O(N^2) O(N^2) O(1) 不穩定
冒泡算法 O(N^2) O(N) O(N^2) O(1) 穩定
希爾排序 O(NlogN) O(Nlog^2N) O(Nlog^2N) O(1) 不穩定
堆排序 O(NlogN) O(NlogN) O(NlogN) O(1) 不穩定
歸併排序 O(NlogN) O(NlogN) O(NlogN) O(N) 穩定
快速排序 O(NlogN) O(NlogN) O(N^2) O(logN) 不穩定

1. 穩定性即排序前後兩個相等的數其位置不變。其中插入、冒泡、歸併排序均爲穩定排序,選擇、希爾、堆、快速排序均爲不穩定排序。

2. 除歸併、快速排序外,其他排序空間複雜度均爲O(1),其中歸併排序爲O(N),快速排序爲O(logN)。

3. 選擇、堆、歸併排序的時間複雜度在三種情況下均相同,其中選擇排序的均爲O(N^2),堆、歸併排序的均爲O(NlogN)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章