1. 程式人生 > >排序演算法總結(含動圖演示和Java程式碼實現)

排序演算法總結(含動圖演示和Java程式碼實現)

本文將圍繞氣泡排序、桶排序、計數排序、堆排序、插入排序、並歸排序、快速排序和選擇排序,按照描述、時間複雜度(最壞情況)、動態圖展示和程式碼實現來講解。本文預設排序為從小到大。

本文相關程式碼已上傳至github,歡迎關注https://github.com/zhuzhenke/common-algorithms

公用方法

SortUtils

public class SortUtils {

    public static void check(int[] sortingData) {
        if (sortingData == null || sortingData.length == 0) {
            throw new IllegalArgumentException("sortingData can not be empty!");
        }
    }

    public static void swap(int[] swapArray, int i, int j) {
        check(swapArray);
        if (i < 0 || i > swapArray.length - 1) {
            throw new ArrayIndexOutOfBoundsException("illegal index i:" + i);
        }
        if (j < 0 || j > swapArray.length - 1) {
            throw new ArrayIndexOutOfBoundsException("illegal index j:" + j);
        }
        int temp = swapArray[i];
        swapArray[i] = swapArray[j];
        swapArray[j] = temp;
    }

    public static int getMaxValue(int[] sortingData) {
        check(sortingData);

        int max = sortingData[0];
        for (int value : sortingData) {
            if (value < 0) {
                throw new IllegalArgumentException("value could not be negative:" + value);
            }
            if (value > max) {
                max = value;
            }
        }
        return max;
    }
}

氣泡排序

描述

  • 比較前後兩個數的大小,如果前者大於後者,則交換兩個數的位置
  • 最大的數字在進行一輪比較和交換後,會出現在陣列的最後一個位置
  • 每一輪之後,可減少最大數字的比較。重複上述操作直到沒有需要比較的元素為止

時間複雜度

O(n^2)

動態圖展示

氣泡排序

程式碼實現

github程式碼

public int[] sort(int[] sortingData) {
        SortUtils.check(sortingData);
        for (int j = sortingData.length - 1; j > 0; j--) {
            for (int i = 0; i < j; i++) {
                if (sortingData[i] > sortingData[i + 1]) {
                    SortUtils.swap(sortingData, i, i + 1);
                }
            }
        }
        return sortingData;
    }

桶排序

描述

  • 挑選陣列中最大的數字,設定預設分配的桶數,得到每個桶容納的數字範圍。如最大是10,桶是4個,則每個桶最大容納3個數字。第0個桶放0、1、2、3,第1個桶方4、5、6,第2桶方7,8,9,以此類推
  • 對每個桶內進行氣泡排序或選擇排序
  • 遍歷所有桶,依次取出每個桶中的元素,得到的就是一個排好序的陣列

時間複雜度

O(n^2)

動態圖展示


程式碼實現

github程式碼

@Override
    public int[] sort(int[] sortingData) {
        SortUtils.check(sortingData);

        int bucketSize = (int) Math.round(Math.sqrt(sortingData.length)) + 1;
        int[][] buckets = new int[bucketSize][];
        int max = SortUtils.getMaxValue(sortingData);
        double avgContain = Math.ceil((double) max / (double) bucketSize);

        for (int value : sortingData) {
            int bucketIndex = (int) Math.ceil(value / avgContain) - 1;
            if (bucketIndex < 0) {
                bucketIndex = 0;
            }
            int[] bucketIndexs = buckets[bucketIndex];
            if (bucketIndexs == null || bucketIndexs.length == 0) {
                bucketIndexs = new int[1];
                bucketIndexs[0] = value;
                buckets[bucketIndex] = bucketIndexs;
            } else {
                int[] newBucketIndexs = new int[bucketIndexs.length + 1];
                System.arraycopy(bucketIndexs, 0, newBucketIndexs, 0, bucketIndexs.length);
                newBucketIndexs[bucketIndexs.length] = value;
                buckets[bucketIndex] = newBucketIndexs;
            }
        }

        Sort sort = new InsertionSort();
        for (int a = 0; a < buckets.length; a++) {
            int[] bucket = buckets[a];
            if (bucket == null || bucket.length == 0) {
                continue;
            }
            bucket = sort.sort(bucket);
            buckets[a] = bucket;
        }

        int[] result = new int[sortingData.length];
        int resultIndex = 0;
        for (int[] bucket : buckets) {
            if (bucket == null || bucket.length == 0) {
                continue;
            }
            for (int bucketValue : bucket) {
                result[resultIndex++] = bucketValue;
            }
        }

        return result;
    }

計數排序

描述

  • 獲取陣列中最大的值(這個值需要在可控範圍,最好是在10000以內)
  • 建立一個長度為最大值加1的計數陣列,
  • 遍歷待排序陣列,將元素的值落入到計數陣列的下標元素,將下標元素的值加1
  • 遍歷下標陣列,將後一個元素的值標為當前元素值加前一個元素的值(用於排序後的陣列下標)。
  • 建立一個長度跟待排序陣列大小相同的結果陣列
  • 遍歷待排序陣列,取出待排序元素,對應計數陣列的下標元素,並放在計數元素值的前一個位置,並將計數元素值減1

時間複雜度

O(n)

動態圖展示

程式碼實現

github程式碼

private int[] sort2(int[] sortingData) {
        int maxValue = SortUtils.getMaxValue(sortingData);
        //get max,check all numbers must be bigger or equal 0
        int[] count = new int[maxValue + 1];
        //count every number
        for (int value : sortingData) {
            count[value] = count[value] + 1;
        }

        for (int i = 1; i < count.length; i++) {
            count[i] = count[i] + count[i - 1];
        }
        //output
        int[] result = new int[sortingData.length];
        for (int value : sortingData) {
            result[count[value] - 1] = value;
            count[value] = count[value] - 1;
        }
        return result;
    }

堆排序

描述

  • 為了方便理解,將陣列元素對映成二叉樹,array[0]對一個根節點,array[1]為根的左節點,array[2]為根的右節點,一次類推
  • 從最後一個元素開始遍歷到第一個,讓其與父節點比較大小,如果子節點大,則交換位置。(另外一種方式是從中間元素開始比較,得到當前元素和子元素的最大值,這樣則遍歷深度為logn,這種方式屬於推薦方式)
  • 遍歷一輪結束後,則最大值已經位於index為0的位置,這時交換index為0和最後一位的位置,則最大值確定。下一輪比較從最大值的前一個index下標元素開始遍歷,依次進行遍歷
  • 最後全部遍歷完成,則得到一個拍好序的陣列

時間複雜度

O(nlogn)

動態圖展示

程式碼實現

github程式碼

public int[] sort(int[] sortingData) {
        int highIndex = sortingData.length - 1;
        while (highIndex > 0) {
            for (int i = 1; i <= highIndex; i++) {
                sortBiggestToIndex0(sortingData, i);
            }
            SortUtils.swap(sortingData, 0, highIndex);
            highIndex--;
        }
        return sortingData;
    }

    public static void sortBiggestToIndex0(int[] sortingData, int sortIndex) {
        while (sortIndex > 0 && sortingData[sortIndex] > sortingData[(sortIndex - 1) / 2]) {
            SortUtils.swap(sortingData, sortIndex, (sortIndex - 1) / 2);
            sortIndex = (sortIndex - 1) / 2;
        }
    }

插入排序

描述

  • 插入排序類似於撲克摸牌排序
  • 第一次只有一種牌,牌是有序的。當摸到第二張牌時,則插入到已有的排好序的牌中,此時前兩張牌有序
  • 依次進行同樣的操作,摸到第n張牌時,前n-1張牌已經有序,進行插入到合適位置即可

時間複雜度

O(n^2)

動態圖展示

程式碼實現

github程式碼

public int[] sort(int[] sortingData) {
        SortUtils.check(sortingData);

        for (int i = 1; i < sortingData.length; i++) {
            int currentValue = sortingData[i];
            int j = i;
            while (j - 1 >= 0 && currentValue < sortingData[j - 1]) {
                sortingData[j] = sortingData[j - 1];
                j--;
            }
            sortingData[j] = currentValue;
        }
        return sortingData;
    }

並歸排序

描述

  • 並歸排序利用遞迴思想,遞迴思想的核心在於找到一個模式,分解為子模式,子模式又可以分解子模式,則對於最小子模式,可以直接求解。
  • 首先會將待排序陣列分為兩份,兩份分別排好序後進行合併
  • 兩份中的每一份,又可以查分為更小的一份,直到每份只有一個元素,則此份為已排好序的子陣列。對兩個子陣列進行合併的排序

時間複雜度

O(nlogn)

動態圖展示

程式碼實現

github程式碼

@Override
    public int[] sort(int[] sortingData) {
        SortUtils.check(sortingData);
        splitDate(sortingData, 0, sortingData.length - 1);
        return sortingData;
    }

    private void splitDate(int[] sortingData, int start, int end) {
        if (end - start < 1) {
            return;
        }
        int middle = (start + end) / 2;
        splitDate(sortingData, start, middle);
        splitDate(sortingData, middle + 1, end);
        mergeData(sortingData, start, middle, end);
    }

    private void mergeData(int[] sortingData, int start, int middle, int end) {
        int[] left = Arrays.copyOfRange(sortingData, start, middle + 1);
        int[] right = Arrays.copyOfRange(sortingData, middle + 1, end + 1);

        int i = 0;
        int j = 0;

        for (int k = start; k <= end; k++) {
            if (i < left.length && j < right.length) {
                if (left[i] <= right[j]) {
                    sortingData[k] = left[i];
                    i++;
                } else {
                    sortingData[k] = right[j];
                    j++;
                }
            } else {
                if (i >= left.length) {
                    sortingData[k] = right[j];
                    j++;
                } else if (j >= right.length) {
                    sortingData[k] = left[i];
                    i++;
                }
            }
        }
    }

快速排序

描述

  • 選擇待排序陣列的中元元素,一般選擇第一個或最後一個元素,將陣列拆分為兩部分,左邊部分元素小於等於中元元素,右邊部分元素大於中元元素
  • 繼續將子陣列按中元元素進行拆分,直到全部排好序位置

時間複雜度

O(n^2)

動態圖展示

程式碼實現

github程式碼

@Override
    public int[] sort(int[] sortingData) {
        SortUtils.check(sortingData);

        quickSort(sortingData, 0, sortingData.length - 1);

        return sortingData;
    }

    private void quickSort(int[] sortingData, int start, int end) {
        if (start > end) {
            return;
        }
        int middle = getQuickSortMiddle(sortingData, start, end);
        quickSort(sortingData, start, middle - 1);
        quickSort(sortingData, middle + 1, end);
    }


    /**
     * one side
     */
    public int getQuickSortMiddle(int[] sortingData, int start, int end) {
        int i = start;
        int pivot = end;
        for (int j = start; j < end; j++) {
            if (sortingData[j] < sortingData[pivot]) {
                SortUtils.swap(sortingData, i, j);
                i++;
            }
        }
        SortUtils.swap(sortingData, i, pivot);
        return i;
    }

選擇排序

描述

  • 從第一個元素開始遍歷,記錄最小值和對應元素下標,遍歷一輪則可以得到最小值,將這個值與下標為0的元素交換,則完成第一輪最小值輸出
  • 從第2個進行同樣的遍歷操作,遍歷取剩餘元素的最小值,將這個值與下標為1的元素交換
  • 從第index個開始進行遍歷操作,遍歷取剩餘元素的最小值,將這個值與下標為index的元素交換
  • 遍歷直到最後一個元素位置,得到一個排好序的陣列

時間複雜度

O(n^2)

動態圖展示

程式碼實現

github程式碼

public int[] sort(int[] sortingData) {
        SortUtils.check(sortingData);

        for (int index = 0; index < sortingData.length; index++) {
            int smallestIndex = index;
            int smallestValue = sortingData[index];
            for (int j = index; j < sortingData.length; j++) {
                if (sortingData[j] < smallestValue) {
                    smallestValue = sortingData[j];
                    smallestIndex = j;
                }
            }
            sortingData[smallestIndex] = sortingData[index];
            sortingData[index] = smallestValue;
        }
        return sortingData;
    }