十大排序算法總結

0. 前言

排序算法中涉及到了兩個概念:git

原地排序:根據算法對內存的消耗狀況,能夠將算法分爲原地排序和非原地排序,原地排序特指空間複雜度爲 O(1) 的排序。github

排序算法的穩定性:例如排序一個數組 [1, 5, 3, 7, 4, 9, 5],數組中有兩個 5,排序以後是 [1, 3, 4, 5, 5, 7, 9],若是排序以後的兩個 5 的先後順序沒有發生變化,那麼稱這個排序是穩定的,反之則是不穩定的。算法

1. 冒泡排序

冒泡排序是很經典的排序算法了,相鄰的兩個數據依次進行比較並交換位置。遍歷一遍數組後,則有一個數據排序完成,而後再遍歷 n 次,排序完成。示意圖以下:shell

在這裏插入圖片描述

代碼實現:api

public class BubbleSort {

    private static void bubbleSort(int[] data){
        int length = data.length;
        for (int i = length - 1; i > 0; i --) {
            //判斷是否有數據交換,若是沒有則提早退出
            boolean flag = false;
            for (int j = 0; j < i; j ++) {
                if (data[j] > data[j + 1]){
                    int temp = data[j];
                    data[j] = data[j + 1];
                    data[j + 1] = temp;
                    flag = true;
                }
            }
            if (!flag){
                break;
            }
        }
    }
}

2. 選擇排序

將要排序的數據分爲了已排序區間和未排序區間,每次從未排序區間找到最小值,而後將其放到已排序區間的末尾,循環遍歷未排序區間則排序完成。數組

示意圖以下:數據結構

在這裏插入圖片描述

代碼實現:dom

public class SelectionSort {

    public static void selectionSort(int[] data){
        int length = data.length;
        for (int i = 0; i < length - 1; i++) {
            int min = i;
            for (int j = i + 1; j < length; j++) {
                if (data[min] > data[j]){
                    min = j;
                }
            }
            int temp = data[min];
            data[min] = data[i];
            data[i] = temp;
        }
    }
}

3. 插入排序

和選擇排序相似,插入排序也將數據分爲了已排序區間和未排序區間,遍歷未排序區間,每次取一個數據,將其插入到已排序區間的合適位置,讓已排序區間一直保持有序,直到未排序區間遍歷完排序則完成。函數

示意圖以下:性能

在這裏插入圖片描述

代碼實現:

public class InsertionSort {

    public static void insertionSort(int[] data){
        int length = data.length;
        for (int i = 0; i < length - 1; i++) {
            int val = data[i + 1];
            int j = i + 1;
            while (j > 0 && data[j - 1] > val){
                data[j] = data[j - 1];
                j --;
            }
            data[j] = val;
        }
    }
}

插入排序爲何比冒泡排序更經常使用?

這兩種排序的時間複雜度都是同樣的,最好狀況是 O(n),最壞狀況是 O(n2),可是在實際的生產中,插入排序使用更多,緣由在於二者數據交換的次數不一樣。冒泡排序須要進行三次交換,插入排序只要一次:

//冒泡排序數據交換
if (data[j] > data[j + 1]){
    int temp = data[j];
    data[j] = data[j + 1];
    data[j + 1] = temp;
    flag = true;
}

//插入排序數據交換
while (j > 0 && data[j - 1] > val){
    data[j] = data[j - 1];
    j --;
}

在數據量較大的時候,二者性能差距就體現出來了。

4. 希爾排序

希爾排序實際上是插入排序的一種優化,其思路是將排序的數組按照必定的增量將數據分組,每一個分組用插入排序進行排序,而後增量逐步減少,當增量減少爲1的時候,算法便終止,因此希爾排序又叫作「縮小增量排序」。

示意圖以下:

在這裏插入圖片描述

圖中的示例,每次依次將數組分爲若干組,每組分別進行插入排序。代碼實現以下:

public class ShellSort {

    public static void shellSort(int[] data) {

        int length = data.length;
        int step = length / 2;
        while (step >= 1){
            for (int i = step; i < length; i++) {
                int val = data[i];
                int j = i - step;
                for (; j >= 0; j -= step){
                    if (data[j] > val){
                        data[j + step] = data[j];
                    }
                    else {
                        break;
                    }
                }
                data[j + step] = val;
            }
            step = step / 2;
        }
    }
}

5. 歸併排序

歸併排序使用到了分治思想,分治思想即將大的問題分解成小的問題,小的問題解決了,大的問題也就解決了。蘊含分治思想的問題,通常可使用遞歸技巧來實現。

歸併排序的思路是:首先將數組分解,局部進行排序,而後將排序的結果進行合併,這樣整個數組就有序了,你能夠結合下圖理解:

在這裏插入圖片描述

代碼實現:

public class MergeSort {

    public static void mergeSort(int[] data){
        mergeInternally(data, 0, data.length - 1);
    }

    private static void mergeInternally(int[] data, int p, int r){
        if (p >= r){
            return;
        }
        int q = (p + r) / 2;
        //分治遞歸
        mergeInternally(data, p, q);
        mergeInternally(data, q + 1, r);
        //結果合併
        merge(data, p, q, r);
    }

    private static void merge(int[] data, int p, int q, int r){
        int[] temp = new int[r - p + 1];
        int k = 0;
        int i = p;
        int j = q + 1;
        //比較併合並
        while (i <= q && j <= r){
            if (data[i] < data[j]){
                temp[k ++] = data[i ++];
            }
            else {
                temp[k ++] = data[j ++];
            }
        }
        //合併可能出現的剩餘元素
        int start = i;
        int end = q;
        if (j <= r){
            start = j;
            end = r;
        }
        while (start <= end){
            temp[k ++] = data[start ++];
        }
        //拷貝回原數組
        if (r - p + 1 >= 0) {
            System.arraycopy(temp, 0, data, p, r - p + 1);
        }
    }
}

6. 快速排序

快速排序也用到了分治的思想,只不過它和歸併排序的思路恰好是相反的,快速排序使用數組中一個數據做爲分區點(通常能夠選取數組第一個或最後一個元素),比分區點小的,放在左側,比分區點大的,放在右側。而後左右兩側的數據再次選擇分區點,循環進行這個操做,直到排序完成。

示意圖以下(圖中是以第一個元素做爲分區點):

在這裏插入圖片描述

代碼實現:

public class QuickSort {
    public static void quickSort(int[] data){
        quickSortInternally(data, 0, data.length - 1);
    }

    private static void quickSortInternally(int[] data, int p, int r){
        if (p >= r){
            return;
        }
        int q = partition(data, p, r);
        quickSortInternally(data, p, q - 1);
        quickSortInternally(data, q + 1, r);
    }

    /**
     * 獲取分區點函數
     */
    private static int partition(int [] data, int p, int q){
        int pivot = data[q];
        int i = 0;
        int j = 0;
        while (j < q){
            if (data[j] <= pivot){
                swap(data, i, j);
                i ++;
            }
            j ++;
        }
        swap(data, i, q);
        return i;
    }
    /**
     * 交換數組兩個元素
     */
    private static void swap(int[] data, int i, int j){
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }
}

7. 堆排序

基於堆的排序比較經常使用,時間複雜度爲 O(nlogn),而且是原地排序,主要的步驟分爲建堆和排序。

建堆

思路是從堆中第一個非葉子節點,依次從上往下進行堆化,以下圖:

在這裏插入圖片描述

排序

建堆完成以後,假設堆中元素個數爲 n,堆頂元素便是最大的元素,這時候直接將堆頂元素和堆中最後一個元素進行交換,而後將剩餘的 n - 1 個元素構建成新的堆,依次類推,直到堆中元素減小至 1,則排序完成。示意圖以下:

在這裏插入圖片描述

代碼實現:

public class HeapSort {

    /**
     * 排序
     */
    public void heapSort(int[] data){
        int length = data.length;
        if (length <= 1){
            return;
        }
        buildHeap(data);
        while (length > 0){
            swap(data, 0, -- length);
            heapify(data, length, 0);
        }
    }

    /**
     * 建堆
     */
    private void buildHeap(int[] data){
        int length = data.length;
        for (int i = (length - 2) / 2; i >= 0; i --) {
            heapify(data, length, i);
        }
    }

    /**
     * 堆化函數
     */
    private void heapify(int[] data, int size, int i){
        while (true){
            int max = i;
            if ((2 * i + 1) < size && data[i] < data[2 * i + 1]) {
                max = 2 * i + 1;
            }
            if ((2 * i + 2) < size && data[max] < data[2 * i + 2]) {
                max = 2 * i + 2;
            }
            if (max == i){
                break;
            }
            swap(data, i, max);
            i = max;
        }
    }

    /**
     * 交換數組中兩個元素
     */
    private void swap(int[] data, int i, int j){
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }
}

8. 桶排序

桶排序並非基於數據比較的,所以比較的高效,時間複雜度接近 O(n),可是相應地,應用的條件十分苛刻。其思路很是的簡單:將要排序的數據分到各個有序的桶內,數據在桶內進行排序,而後按序取出,整個數據就是有序的了。

最好狀況下,數據被均勻的分到各個桶中,最壞狀況是數據全都被分到一個桶中。

下面是一個桶排序的示例:

public class BucketSort {

    /**
     * 測試場景:數組中有10000個數據,範圍在(0-100000)
     * 使用100個桶,每一個桶存放的數據範圍爲:0-999, 1000-1999, 2000-2999,依次類推
     */
    public static void bucketSort(int[] data){
        //新建100個桶
        int bucketSize = 100;
        ArrayList<ArrayList<Integer>> buckets = new ArrayList<>(bucketSize);
        for (int i = 0; i < bucketSize; i++) {
            buckets.add(new ArrayList<>());
        }
        //遍歷數據,將數據放到桶中
        for (int i : data) {
            buckets.get(i / 1000).add(i);
        }
        //在桶內部進行排序
        int k = 0;
        for (int i = 0; i < bucketSize; i++) {
            ArrayList<Integer> list = buckets.get(i);
            Integer[] num = list.toArray(new Integer[1]);
            Arrays.sort(num);
            //拷貝到data中
            for (int n : num) {
                data[k++] = n;
            }
        }
    }
    //測試
    public static void main(String[] args) {
        Random random = new Random();
        int[] data = new int[10000];
        for (int i = 0; i < data.length; i++) {
            data[i] = random.nextInt(100000);
        }
        BucketSort.bucketSort(data);
        System.out.println(Arrays.toString(data));
    }
}

9. 計數排序

計數排序實際上是一種特殊的桶排序,適用於數據的區間不是很大的狀況。

例如給 10 萬人按照年齡進行排序,咱們知道年齡的區間並非很大,最小的 0 歲,最大的能夠假設爲 120 歲,那麼咱們能夠新建 121 個桶,掃描一遍數據,將年齡相同的放到一個桶中,而後按序從桶中將數據取出,這樣數據就有序了。

計數排序的基本思路以下:

在這裏插入圖片描述

代碼實現:

public class CountingSort {

    private static void countingSort(int[] data){
        int length = data.length;
        //找到數組的最大值
        int max = data[0];
        for (int i : data){
            if (max < i){
                max = i;
            }
        }
        //新建一個計數數組,大小爲max+1
        //count數組的下標對應data的值,存儲的值爲對應data值的個數
        int[] count = new int[max + 1];
        for (int i : data){
            count[i] ++;
        }
        //根據count數組取出數據
        int k = 0;
        for (int i = 0; i < count.length; i++) {
            while (count[i] != 0){
                data[k ++] = i;
                count[i] --;
            }
        }
    }
}

10. 基數排序

基數排序適用於位數較多的數字或者字符串,思路是將排序的數據按位拆分,每一位單獨按照穩定的排序算法進行比較,以下圖:

在這裏插入圖片描述

圖中的示例,以每一個數字爲下標,建了 10 個 「桶」,每一個桶是一個隊列(也能夠是數組),而後將要排序的數據按位加入到隊列中,而後出隊,比較完每一位,則排序完成。

代碼實現:

public class RadixSort {

    private static void radixSort(int[] data) {
        int maxDigit = maxDigit(data);

        //新建並初始化10個桶
        Queue<Integer>[] queues = new LinkedList[10];
        for (int i = 0; i < queues.length; i++) {
            queues[i] = new LinkedList<>();
        }

        for (int i = 0, mod = 1; i < maxDigit; i ++, mod *= 10) {
            for (int n : data){
                int m = (n / mod) % 10;
                queues[m].add(n);
            }

            int count = 0;
            for (Queue<Integer> queue : queues) {
                while (queue.size() > 0) {
                    data[count++] = queue.poll();
                }
            }
        }
    }

    /**
     * 獲取數組最大位數
     */
    private static int maxDigit(int[] data){
        int maxDigit = data[0];
        for (int i : data){
            if (maxDigit < i){
                maxDigit = i;
            }
        }
        return String.valueOf(maxDigit).length();
    }
}
在個人 Github 上面有更加詳細的數據結構與算法代碼
相關文章
相關標籤/搜索