圖解十大經典排序算法(Java)

在這裏插入圖片描述
比較排序:
冒泡、選擇、插入、希爾、堆、歸併、快排
非比較排序:
基數、計數、桶
穩定排序:
冒泡、插入、歸併、基數、計數、桶
不穩定排序:
選擇、希爾、堆、快排
分治思想:
歸併、快排

冒泡排序

  1. 算法步驟:
    1)比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
    2)對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對。這步做完後,最後的元素會是最大的數。
    3)針對所有的元素重複以上的步驟,除了最後一個。
    4)持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
  2. 優化策略:設立一個 flag,當在一趟序列遍歷中元素沒有發生交換,則證明該序列已經有序。
  3. 動圖演示:
    在這裏插入圖片描述
  4. 適用場景:
    少量數據
  5. 代碼實現:
public class BubbleSort {

    public int[] sort(int[] arr){
        for (int i = 1; i < arr.length; i++) {
            //標記,若爲true,就表明此次循環沒有進行交換,表明已經排好序,就跳出循環
            boolean flag = true;

            for (int j = 0; j < arr.length-i; j++) {
                if (arr[j] > arr[j+1]){
                    int tmp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = tmp;
                    //交換了,就設爲false
                    flag =false;
                }
            }
            if(flag) break;
        }
        return arr;
    }
}

選擇排序

  1. 算法步驟:
    1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
    2)再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。
    3)重複第二步,直到所有元素均排序完畢。
  2. 動圖演示
    在這裏插入圖片描述
  3. 適用場景:
    少量數據
  4. 代碼實現:
public class SelectSort{
    public int[] sort(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;
                }
            }
            //將找到的最小值和i位置所在的值進行交換
            if (i != min){
                int tmp = arr[i];
                arr[i] = arr[min];
                arr[min] = tmp;
            }
        }
        return arr;
    }
}

插入排序

  1. 算法步驟:
    1)將第一待排序序列第一個元素看做一個有序序列,把第二個元素到最後一個元素當成是未排序序列。
    2)從頭到尾依次掃描未排序序列,將掃描到的每個元素插入有序序列的適當位置。(如果待插入的元素與有序序列中的某個元素相等,則將待插入元素插入到相等元素的後面。)
  2. 動圖演示:
    在這裏插入圖片描述
  3. 適用場景:
    小規模、基本有序的時候十分有效。在對幾乎已經排好序的數據操作時,效率高,即可以達到線性排序的效率。
  4. 代碼實現:
public class InsertSort{
    public int[] sort(int[] arr){
        //0爲已排好序的,從1開始往有序序列裏面插入
        for (int i = 1; i < arr.length; i++) {
            //要插入的數據
            int tmp = arr[i];
            //j是要插入的數據位置,它和j前已排好序的序列j-1 ~ 0比較,
            int j = i;
            while (j>0 && tmp < arr[j-1]){
                //找到就將數據往後移一位
                arr[j] = arr[j-1];
                j--;
            }
            //將數據插入
            if (j != i){
                arr[j] = tmp;
            }
        }
        return arr;
    }
}

希爾排序

  1. 算法步驟:
    1)選擇一個增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
    2)按增量序列個數 k,對序列進行 k 趟排序;
    3)每趟排序,根據對應的增量 ti,將待排序列分割成若干長度爲 m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲 1 時,整個序列作爲一個表來處理,表長度即爲整個序列的長度
  2. 動圖演示:
    在這裏插入圖片描述
  3. 適用場景:
    對插入排序的一種改進,中等大小規模表現良好,對規模非常大的數據排序不是最優選擇。
  4. 代碼實現
public class ShellSort{
    public int[] sort(int[] arr){
        //間隔計算
        int gap = 1;
        while(gap <= arr.length / 3){
            gap = gap * 3 + 1;
        }
        while (gap > 0){
        //相當於間隔爲gap的插入排序,如果gap爲1就和插入排序一樣
            for (int i = gap; i < arr.length; i++) {
                int tmp = arr[i];
                int j = i;
                while (j >= gap && arr[j-gap] > tmp){
                    arr[j] = arr[j-gap];
                    j -= gap;
                }
                arr[j] = tmp;
            }
            //減小間隔
            gap = gap / 3;
        }
        return arr;
    }
}

堆排序

  1. 算法步驟
    1)創建初始堆;(大頂堆和小頂堆)
    2)把堆頂和堆尾互換;
    3)把堆的尺寸縮小 1,並重新調整堆;
    4)重複步驟 2,直到堆的尺寸爲 1。
  2. 動圖演示:在這裏插入圖片描述
  3. 適用場景:
    數據量大,適用面較廣,可以適用比快排更大量的數,也適用於多核並行處理。堆排序所需的輔助空間少於快速排序,並且不會出現快速排序可能出現的最壞情況。
  4. 代碼實現:
public class HeapSort{

    public int[] sort(int[] arr){
        int len = arr.length;
        //先建大頂堆,用堆調整的方法建立
        buildMaxHeap(arr, len);

        for (int i = len -1 ; i > 0; i--) {
            //交換堆頂和堆尾
            swap(arr, 0 ,i);
            len --; //堆大小減1
            //調整堆
            adjustHeap(arr, 0, len);
        }
        return arr;
    }
    //建大頂堆
    private void buildMaxHeap(int[] arr, int len){
        for (int i = len/2; i >= 0 ; i--) {
            adjustHeap(arr, i, len);
        }
    }
    //調整堆
    private void adjustHeap(int[] arr, int index, int len){
        int left = 2 * index + 1;   //左子節點索引
        int right =  left + 1;  //右子節點索引
        int largest = index;        //最大節點索引,默認值是當前節點(父節點)
        //判斷左子節點比父節點的大小,如果有節點,肯定必有左子節點
        if (left < len && arr[left] > arr[largest]) {
            largest=left;
        }
        //先判斷是否有右子節點,再判斷左右節點,哪個較大。
        if (right < len && arr[right] > arr[largest]){
            largest = right;
        }
        //當前位置的索引不是最大索引就交換,然後再重新調整,如果相等就不動。
        if (largest != index){
            swap(arr, index, largest);
            adjustHeap(arr, largest, len);
        }
    }
    //交換數據
    private void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

歸併排序

  1. 算法步驟:
    1)申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列;
    2)設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置;
    3)比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置;
    4)重複步驟 3 直到某一指針達到序列尾;
    5)將另一序列剩下的所有元素直接複製到合併序列尾
  2. 動圖演示:
    在這裏插入圖片描述
  3. 適用場景:
    數據量大,且要求穩定的時候,可以考慮歸併排序。
  4. 代碼實現:
    這裏運用了Arrays.copyOfRange(T[ ] original,int from,int to)方法,將一個原始的數組original,從下標from開始複製,複製到上標to,生成一個新的數組。注意這裏包括下標from,不包括上標to。
public class MergeSort{

    public int[] sort(int[] arr){
        if (arr.length < 2){
            return arr;
        }
        int middle = arr.length / 2;
        //將當前數組分爲兩部分(分治)
        int[] left = Arrays.copyOfRange(arr, 0, middle);
        int[] right = Arrays.copyOfRange(arr, middle, arr.length);
        //遞歸
        return mergeSort(sort(left), sort(right));
    }

    private int[] mergeSort(int[] left, int[] right){
        //合併數組
        int[] result = new int[left.length + right.length];

        //判斷左數組和右數組 中第一個數值的大小,將小的放入數組,並把小的那個數組的該項去掉
        int i = 0;
        while (left.length > 0 && right.length > 0){
            if (left[0] <= right[0]){
                result[i++] = left[0];
                left = Arrays.copyOfRange(left,1, left.length);
            }else{
                result[i++] = right[0];
                right = Arrays.copyOfRange(right,1, right.length);
            }
        }
        //左邊數組有剩餘,加入數組
        while (left.length > 0){
            result[i++] = left[0];
            left = Arrays.copyOfRange(left, 1, left.length);
        }
        //右邊數組有剩餘,加入數組
        while (right.length > 0){
            result[i++] = right[0];
            right = Arrays.copyOfRange(right, 1, right.length);
        }
        return result;
    }
}

快速排序

  1. 算法步驟:
    1)首先取數組的第一個元素作爲基準元素pivot。
    2)重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分區退出之後,該基準就處於數列的中間位置。這個稱爲分區(partition)操作;
    3)遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序;

  2. 動圖演示:
    在這裏插入圖片描述

  3. 適用場景:
    數據量大,快速排序是目前基於比較的排序中被認爲是最好的方法,當待排序的關鍵字是隨機分佈時,快速排序的平均時間最短;

  4. 代碼實現:

public class QuickSort{
    public int[] sort(int[] arr){
        return quickSort(arr, 0, arr.length-1);
    }

    private int[] quickSort(int[] arr, int left, int right){
        if (left < right){
            int partition = partition(arr, left, right);
            //分治
            quickSort(arr, left, partition - 1);
            quickSort(arr, partition + 1, right);
        }
        return arr;
    }

    private int partition(int[] arr, int left, int right){
        int pivot = left;
        int index = pivot + 1;  //始終爲第一個大於pivot值的位置
        for (int i = index; i <= right; i++) {
            if (arr[i] < arr[pivot]){
                swap(arr, i, index);
                index++;
            }
        }
        //將pivot位置的值和最後一個小於pivot的值交換
        swap(arr, pivot, index-1);
        //交換後 index-1 就是 pivot 的位置
        return index-1;
    }

    private void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

計數排序

  1. 算法步驟:
    1)根據待排序集合中最大元素和最小元素的差值範圍,申請額外空間;
    2)遍歷待排序集合,將每一個元素出現的次數記錄到元素值對應的額外空間內;
    3)對額外空間內數據進行計算,得出每一個元素的正確位置;
    4)將待排序集合每一個元素移動到計算得出的正確位置上。
  2. 動圖演示:
    在這裏插入圖片描述
  3. 適用場景:
    每個桶只存儲單一鍵值;它的優勢在於在對一定範圍內的整數排序時,它的複雜度爲Ο(n+k)(其中k是整數的範圍),快於任何比較排序算法。當然這是一種犧牲空間換取時間的做法。
  4. 代碼實現:
public class CountingSort{

    public int[] sort(int[] arr){
        int maxValue = getMaxValue(arr);
        return countingSort(arr, maxValue);
    }

    private int[] countingSort(int[] arr, int maxValue){
        int bucketLen = maxValue + 1;
        int[] bucket = new int[bucketLen];

        //按照數組中的值(對應桶中的key),將桶中對應值的加1
        // 一個爲1,兩個就爲2,...,依次增加,數組中value相同的情況
        for (int value : arr){
            bucket[value]++;
        }

        int sortedIndex = 0;
        for (int i = 0; i < bucketLen; i++) {
            //依次把桶中有數值的,將key取出來
            while (bucket[i] > 0){
                arr[sortedIndex++] = i;
                //可能有相同的值
                bucket[i]--;
            }
        }
        return arr;
    }

    //取最大值,確定桶的範圍
    private int getMaxValue(int[] arr){
        int maxValue = arr[0];
        for(int value : arr){
            if (maxValue < value){
                maxValue = value;
            }
        }
        return maxValue;
    }

}

桶排序

  1. 算法步驟:
    1)首先規定好桶的數量
    2)其次計算好每個桶的數據範圍 (max-min+1)/bucketCount
    3)把數據放在對應的桶裏,桶內是排好序的
    4)遍歷每個桶,得到最後排好序的序列
  2. 動圖演示:
    在這裏插入圖片描述
  3. 適用場景:
    數據量大,求中位數。每個桶存儲一定範圍的數值,桶排序是計數排序的升級版。
  4. 代碼實現:
public class BucketSort{

    public int[] sort(int[] arr){
        //7爲桶數,自行設置
        return bucketSort(arr,7);
    }

    private int[] bucketSort(int[] arr, int bucketLen){
        if (arr.length == 0){
            return arr;
        }
        //求數組的最大值和最小值
        int minValue = arr[0];
        int maxValue = arr[0];
        for (int value: arr) {
            if (value < minValue){
                minValue = value;
            }
            if (value > maxValue){
                maxValue = value;
            }
        }
        //每個桶的大小
        int bucketCount = (maxValue - minValue) / bucketLen + 1;
        int[][] buckets = new int[bucketCount][0];

        //利用映射函數將數據分配到各個桶中
        for (int i = 0; i < arr.length; i++) {
            int index = (arr[i] - minValue) / bucketLen;
            //擴容桶,並添加數據
            buckets[index] = arrAppend(buckets[index], arr[i]);
        }

        int arrIndex = 0;
        for (int[] bucket : buckets) {
            //取有數據的桶,進行排序
            if(bucket.length > 0) {
                //對每個桶進行排序,這裏用的插入排序,數據量小用插入,數據量大用快速
                InsertSort insertSort = new InsertSort();
                bucket = insertSort.sort(bucket);
                for (int value : bucket) {
                    arr[arrIndex++] = value;
                }
            }

        }
        return arr;
    }

    //擴容保存數據
    private int[] arrAppend(int[] arr, int value){
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value; //新數組最後一位添加數據
        return arr;
    }
}

基數排序

  1. 算法步驟:
    1)將所有待比較數值統一爲同樣的數位長度,數位較短的數前面補零。
    2)從最低位開始,依次進行一次排序。
    3)這樣從最低位排序一直到最高位排序完成以後, 數列就變成一個有序序列。
    基數排序的方式可以採用 LSD(Least significant digital)或 MSD(Most significant digital),LSD 的排序方式由鍵值的最右邊開始,而 MSD 則相反,由鍵值的最左邊開始。
  2. 動圖演示:
    在這裏插入圖片描述
  3. 適用場景:
    基數排序是一種非比較型整數排序算法,其原理是將整數按位數切割成不同的數字,然後按每個位數分別比較。由於整數也可以表達字符串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是隻能使用於整數。
  4. 代碼實現:
public class RadixSort{

    public int[] sort(int[] arr) {
        int maxDigit = getMaxDigit(arr);
        return radixSort(arr, maxDigit);
    }

    //獲取最高位數
    private int getMaxDigit(int[] arr) {
        int maxValue = getMaxValue(arr);
        return getNumLenght(maxValue);
    }

    //獲取最大值
    private int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }
    //獲取最高位,個位爲1,十位爲2,百位爲3,...
    private int getNumLenght(long num) {
        if (num == 0) {
            return 1;
        }
        int lenght = 0;
        for (long temp = num; temp != 0; temp /= 10) {
            lenght++;
        }
        return lenght;
    }

    private int[] radixSort(int[] arr, int maxDigit) {
        int mod = 10;
        int dev = 1;

        for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
            // 考慮負數的情況,這裏擴展一倍隊列數,其中 [0-9]對應負數,[10-19]對應正數 (bucket + 10)
            int[][] buckets = new int[mod * 2][0];

            for (int j = 0; j < arr.length; j++) {
                //+mod 正好把正數和負數區分出來,[0-9]對應負數,[10-19]對應正數
                int index = ((arr[j] % mod) / dev) + mod;
                buckets[index] = arrayAppend(buckets[index], arr[j]);
            }
            //按照該位的順序,把元素都放入到數組中。
            int pos = 0;
            for (int[] bucket : buckets) {
                for (int value : bucket) {
                    arr[pos++] = value;
                }
            }
        }
        return arr;
    }

    //擴容保存數據
    private int[] arrayAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;  //新數組最後一位添加數據
        return arr;
    }
}

代碼下載連接:本文的可運行代碼
參考連接:
https://www.toutiao.com/i6704815171367338508/
https://www.jianshu.com/u/47a4e21999fc

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