1. 程式人生 > >Java開發中常見排序演算法彙總

Java開發中常見排序演算法彙總

在這裡插入圖片描述
Java程式設計中的排序演算法可以分為兩大類:
1、非線性時間比較類排序:通過比較來決定元素間的相對次序,由於其時間複雜度不能突破O(nlogn),因此稱為非線性時間比較類排序。
2、線性時間非比較類排序:不通過比較來決定元素間的相對次序,它可以突破基於比較排序的時間下界,以線性時間執行,因此稱為線性時間非比較類排序。
在這裡插入圖片描述
相關概念
穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面。
不穩定:如果a原本在b的前面,而a=b,排序之後 a 可能會出現在 b 的後面。
時間複雜度:對排序資料的總的操作次數。反映當n變化時,操作次數呈現什麼規律。
空間複雜度:是指演算法在計算機內執行時所需儲存空間的度量,它也是資料規模n的函式。
一、插入排序
1.1 直接插入排序
插入排序的基本操作就是將一個數據插入到已經排好序的有序資料中,從而得到一個新的、個數加一的有序資料,演算法適用於少量資料的排序,時間複雜度為O(n^2)。是穩定的排序方法。
直接插入排序的演算法思路:
1、設定監視哨temp,將待插入記錄的值賦值給temp;
2、設定開始查詢的位置j;
3、在陣列arr中進行搜尋,搜尋中將第j個記錄後移,直至temp≥arr[j]為止;
4、將temp插入arr[j+1]的位置上。
/

  • 直接插入排序
    */
    *public void insertSort(int[] arr) {
    //外層迴圈確定待比較數值
    //必須i=1,因為開始從第二個數與第一個數進行比較
    for (int i = 1; i < arr.length; i++) {
    //待比較數值
    int temp = arr[i];
    int j = i - 1;
    //內層迴圈為待比較數值確定其最終位置
    //待比較數值比前一位置小,應插往前插一位
    for (; j >= 0 && arr[j] > temp; j–) {
    //將大於temp的值整體後移一個單位
    arr[j + 1] = arr[j];
    }
    //待比較數值比前一位置大,最終位置無誤
    arr[j + 1] = temp;
    }
    }

1.2 希爾排序
  希*爾排序(Shell’s Sort)是插入排序的一種,又稱“縮小增量排序”(Diminishing Increment Sort),是直接插入排序演算法的一種更高效的改進版本。希爾排序是非穩定排序演算法。該方法因D.L.Shell於1959年提出而得名。
希爾排序的演算法思路:
把陣列按下標的一定增量分組;
1、對每組使用直接插入排序演算法排序;
2、隨著增量逐漸減少,每組包含的值越來越多,當增量減至1時,整個檔案被分成一組,演算法便終止。

/**

  • 希爾排序
    /
    public void shellSort(int[] arr) {
    int d = arr.length;
    while (d >= 1) {
    d = d / 2;
    for (int x = 0; x < d; x++) {
    //按下標的一定增量分組然後進行插入排序
    for (int i = x + d; i < arr.length; i = i + d) {
    int temp = arr[i];
    int j;
    for (j = i - d; j >= 0 && arr[j] > temp; j = j - d) {
    //移動下標
    arr[j + d] = arr[j];
    }
    arr[j + d] = temp;
    }
    }
    }
    }
    二、交換排序
    2.1 氣泡排序
      在一組資料中,相鄰元素依次比較大小,最大的放後面,最小的冒上來。
    氣泡排序演算法的演算法思路:
    1、比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
    2、對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
    3、針對所有的元素重複以上的步驟,除了最後一個。
    4、持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
    /
    *
    ** 氣泡排序
    /
    public void bubbleSort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
    for (int j = 0; j < arr.length - i - 1; j++) {
    if (arr[j] > arr[j + 1]) {
    // temp 臨時儲存 arr[j] 的值
    int temp = arr[j];
    //交換 arr[j] 和 arr[j+1] 的值
    arr[j] = arr[j + 1];
    arr[j + 1] = temp;
    }
    }
    }
    }
    2.2 快速排序
      快速排序(Quicksort)是對氣泡排序的一種改進。
      通過一次排序將陣列分成兩個子陣列,其中一個數字的值都比另外一個數字的值小,然後再對這兩子陣列分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。
    快速排序的演算法思路:(分治法)
    1、先從數列中取出一個數作為中間值middle;
    2、將比這個數小的數全部放在它的左邊,大於或等於它的數全部放在它的右邊;
    3、對左右兩個小數列重複第二步,直至各區間只有1個數。
    /
  • 快速排序

** @param arr 待排序陣列
/
public void quickSort(int[] arr) {
//檢視陣列是否為空
if (arr.length > 0) {
sort(arr, 0, arr.length - 1);
}
}/
*

  • @param arr 待排序陣列

  • @param low 開始位置

  • @param high 結束位置
    /
    private void sort(int[] arr, int low, int high) {
    if (low < high) {
    int mid = getMiddle(arr, low, high); //將numbers陣列進行一分為二
    sort(arr, low, mid - 1); //對低欄位表進行遞迴排序
    sort(arr, mid + 1, high); //對高欄位表進行遞迴排序
    }
    }
    /

  • 查詢出中軸(預設是最低位low)的在arr陣列排序後所在位置

  • @param arr 待排序陣列

  • @param low 開始位置

  • @param high 結束位置

  • @return 中軸所在位置
    /
    private int getMiddle(int[] arr, int low, int high) {
    int temp = arr[low]; //陣列的第一個作為中軸
    while (low < high) {
    while (low < high && arr[high] >= temp) {
    high–;
    }
    arr[low] = arr[high];//比中軸小的記錄移到低端
    while (low < high && arr[low] < temp) {
    low++;
    }
    arr[high] = arr[low]; //比中軸大的記錄移到高階
    }
    arr[low] = temp; //中軸記錄到尾
    return low; // 返回中軸的位置
    }
    三、選擇排序
    3.1 簡單選擇排序
       選擇排序(Selection-sort)是一種簡單直觀的排序演算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
    簡單選擇排序的演算法思路:
    1、首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
    2、再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾
    3、以此類推,直到所有元素均排序完畢。
    /

  • 簡單選擇排序
    **/
    public void selectSort(int[] arr) {
    int minIndex = 0;
    int temp;
    for (int i = 0; i < arr.length - 1; i++) {
    minIndex = i;
    for (int j = i + 1; j < arr.length; j++) {
    // 找到當前迴圈最小值索引
    if (arr[j] < arr[minIndex]) {
    minIndex = j;
    }
    }
    temp = arr[i];
    // 交換當前迴圈起點值和最小值索引位置的值
    arr[i] = arr[minIndex];
    arr[minIndex] = temp;
    }
    }
    3.2 堆排序
      堆排序(英語:Heap Sort)是指利用堆這種資料結構所設計的一種排序演算法。堆是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。在堆的資料結構中,堆中的最大值總是位於根節點(在優先佇列中使用堆的話堆中的最小值位於根節點)。
    堆排序的演算法思路:
    1、最大堆調整(Max Heapify):將堆的末端子節點作調整,某個節點的值最多和其父節點的值一樣大;
    2、建立最大堆(Build Max Heap):將堆中的所有資料重新排序,堆中的最大元素存放在根節點中;
    3、堆排序(HeapSort):移除位在第一個資料的根節點,並做最大堆調整的遞迴運算,

  • 堆排序
    /
    public
    void heapSort(int[] arr) {
    buildMaxHeap(arr);

    //進行n-1次迴圈,完成排序
    for (int i = arr.length - 1; i > 0; i–) {
    //最後一個元素和第一個元素進行交換
    int temp = arr[i];
    arr[i] = arr[0];
    arr[0] = temp;
    // 篩選 R[0] 結點,得到i-1個結點的堆 將arr中前i-1個記錄重新調整為大頂堆
    heapAdjust(arr, 0, i);
    }
    }
    /**

  • 構建大頂堆

  • 將陣列中最大的值放在根節點
    /
    private void buildMaxHeap(int[] arr) {
    for (int i = arr.length / 2; i >= 0; i–) {
    heapAdjust(arr, i, arr.length - 1);
    }
    }/
    *

  • 堆調整

  • 將陣列中最大的值放在根節點

  • @param arr 待排序陣列

  • @param parent 父節點索引

  • @param length 陣列長度
    /
    private void heapAdjust(int[] arr, int parent, int length) {
    int temp = arr[parent]; //temp儲存當前父節點
    int child = 2 * parent + 1; //獲取左子節點 while (child < length) {
    // 如果有右子結點,並且右子結點的值大於左子結點的值,則選取右子結點的值
    if (child + 1 < length && arr[child] < arr[child + 1]) {
    child++;
    }
    // 如果父結點的值已經大於子結點的值,則直接結束
    if (temp >= arr[child]) {
    break;
    }
    // 把子結點的值賦給父結點
    arr[parent] = arr[child];
    // 選取子結點的左子結點,繼續向下篩選
    parent = child;
    child = 2 * child + 1;
    }
    arr[parent] = temp;
    }
    四、歸併排序
    4.1 二路歸併排序
      歸併排序(mergeSort)是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。
      若將兩個有序表合併成一個有序表,稱為二路歸併。例如:將2個有序數組合並。比較2個數組的第一個數,誰小就先取誰,取了後就在對應陣列中刪除這個數。然後再進行比較,如果有陣列為空,那直接將另一個數組的數依次取出即可。
    二路歸併排序的演算法思路:
    1、將陣列分成A,B 兩個陣列,如果這2個數組都是有序的,那麼就可以很方便的將這2個數組進行排序。
    2、讓這2個數組有序,可以將A,B組各自再分成2個數組。依次類推,當分出來的陣列只有1個數據時,可以認為陣列已經達到了有序。
    3、然後再合併相鄰的2個數組。這樣通過先遞迴的分解陣列,再合併陣列就完成了歸併排序。
    /

    ** 二路歸併排序
    /
    public void mergeSort(int[] arr) {
    int[] temp = new int[arr.length]; //臨時陣列
    sort(arr, temp, 0, arr.length - 1);
    }/
    *

  • @param arr 待排序陣列

  • @param left 開始位置

  • @param right 結束位置
    /
    private void sort(int[] arr, int[] temp, int left, int right) {
    if (left >= right) {
    return;
    }
    int mid = left + (right - left) / 2;
    sort(arr, temp, left, mid);
    sort(arr, temp, mid + 1, right);
    merge(arr, temp, left, mid, right);
    }/
    *

  • 將兩個有序表歸併成一個有序表

  • @param arr 待排序陣列

  • @param temp 臨時陣列

  • @param leftStart 左邊開始下標

  • @param leftEnd 左邊結束下標(mid)

  • @param rightEnd 右邊結束下標
    /
    private static void merge(int[] arr, int[] temp, int leftStart, int leftEnd, int rightEnd) {
    int rightStart = leftEnd + 1;
    int tempIndex = leftStart; // 從左邊開始算
    int len = rightEnd - leftStart + 1; // 元素個數
    while (leftStart <= leftEnd && rightStart <= rightEnd) {
    if (arr[leftStart] <= arr[rightStart]) {
    temp[tempIndex++] = arr[leftStart++];
    } else {
    temp[tempIndex++] = arr[rightStart++];
    }
    }
    // 左邊如果有剩餘 將左邊剩餘的歸併
    while (leftStart <= leftEnd) {
    temp[tempIndex++] = arr[leftStart++];
    }
    // 右邊如果有剩餘 將右邊剩餘的歸併
    while (rightStart <= rightEnd) {
    temp[tempIndex++] = arr[rightStart++];
    }
    // 從臨時陣列拷貝到原陣列
    for (int i = 0; i < len; i++) {
    arr[rightEnd] = temp[rightEnd];
    rightEnd–;
    }
    }
    五、計數排序
      計數排序(Counting sort)不是基於比較的排序演算法,其核心在於將輸入的資料值轉化為鍵儲存在額外開闢的陣列空間中。作為一種線性時間複雜度的排序,計數排序要求輸入的資料必須是有確定範圍的整數。
    計數排序的演算法思路:
    1、求出待排序陣列的最大值 max 和最小值 min。
    2、例項化輔助計數陣列temp,temp陣列中每個下標對應arr中的一個元素,temp用來記錄每個元素出現的次數。
    3、計算 arr 中每個元素在temp中的位置 position = arr[i] - min。
    4、根據 temp 陣列求得排序後的陣列。
    /
    *

  • 計數排序
    /
    public void countSort(int[] arr) {
    if (arr == null || arr.length == 0) {
    return;
    } int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE; //找出陣列中的最大最小值
    for (int i = 0; i < arr.length; i++) {
    max = Math.max(max, arr[i]);
    min = Math.min(min, arr[i]);
    } int[] temp = new int[max]; //找出每個數字出現的次數
    for (int i = 0; i < arr.length; i++) {
    //每個元素在temp中的位置 position = arr[i] - min
    int position = arr[i] - min;
    temp[position]++;
    } int index = 0;
    for (int i = 0; i < temp.length; i++) {
    //temp[i] 大於0 表示有重複元素
    while (temp[i]-- > 0) {
    arr[index++] = i + min;
    }
    }
    }
    六、桶排序
    桶排序 (Bucket sort)的工作原理是將陣列分到有限數量的桶裡。每個桶再分別排序(有可能再使用別的排序演算法或是以遞迴方式繼續使用桶排序進行排序)。當要被排序的陣列內的數值是均勻分配的時候,桶排序使用線性時間(Θ(n))。但桶排序並不是比較排序,他不受到 O(n log n) 下限的影響,桶排序可用於最大最小值相差較大的資料情況。
    桶排序的演算法思路:
    1、找出待排序陣列中的最大值max和最小值min;
    2、我們使用動態陣列ArrayList 作為桶,桶裡放的元素也用 ArrayList 儲存。桶的數量為 (max-min) / arr.length + 1;
    3、遍歷陣列 arr,計算每個元素 arr[i] 放的桶;
    4、每個桶各自排序;
    5、遍歷桶陣列,把排序好的元素放進輸出陣列。
    /
    *

  • 桶排序

  • @param arr 待排序陣列
    /
    public static void bucketSort(int[] arr) {
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for (int i = 0; i < arr.length; i++) {
    max = Math.max(max, arr[i]);
    min = Math.min(min, arr[i]);
    } //桶數
    int bucketNum = (max - min) / arr.length + 1;
    ArrayList<ArrayList> bucketArr = new ArrayList<>(bucketNum);
    for (int i = 0; i < bucketNum; i++) {
    bucketArr.add(new ArrayList<>());
    } //將每個元素放入桶
    for (int i = 0; i < arr.length; i++) {
    int num = (arr[i] - min) / arr.length;
    bucketArr.get(num).add(arr[i]);
    } //對每個桶進行排序
    for (int i = 0; i < bucketNum; i++) {
    Collections.sort(bucketArr.get(i));
    } int position = 0;
    //合併桶
    for (int i = 0; i < bucketNum; i++) {
    for (int j = 0; j < bucketArr.get(i).size(); j++) {
    arr[position++] = bucketArr.get(i).get(j);
    }
    }
    }
    七、基數排序
       基數排序(radix sort)是桶排序的擴充套件,基本思想是將整數按位數切割成不同的數字,然後按每個位數分別比較。
       基數排序法是屬於穩定性的排序,其時間複雜度為O (nlog®m),其中r為所採取的基數,而m為堆數,在某些時候,基數排序法的效率高於其它的穩定性排序法。
    基數排序的演算法思路:
    1、取得陣列中的最大數,並取得位數;
    2、arr為原始陣列,從最低位開始取每個位組成radix陣列;
    3、對radix進行計數排序(利用計數排序適用於小範圍數的特點)。
    /
    *

  • 基數排序

  • @param arr 待排序陣列
    /
    public void radixSort(int[] arr) {
    int max = getMax(arr); // 陣列arr中的最大值 for (int exp = 1; max / exp > 0; exp = 10) {
    //從個位開始,對陣列arr按"exp指數"進行排序
    countSort(arr, exp);
    //bucketSort(arr, exp);
    }
    }
    /

  • 獲取陣列中最大值
    /
    private int getMax(int[] arr) {
    int max = arr[0];
    for (int i = 1; i < arr.length; i++) {
    if (arr[i] > max) {
    max = arr[i];
    }
    }
    return max;
    }/
    *

  • 對陣列按照"某個位數"進行排序(計數排序)

  • 例如:

  • 1、當exp=1 表示按照"個位"對陣列進行排序

  • 2、當exp=10 表示按照"十位"對陣列進行排序

  • @param arr 待排序陣列

  • @param exp 指數 對陣列arr按照該指數進行排序
    */
    *private void countSort(int[] arr, int exp) {
    int[] temp = new int[arr.length]; // 儲存"被排序資料"的臨時陣列
    int[] buckets = new int[10]; // 將資料出現的次數儲存在buckets[]中
    for (int i = 0; i < arr.length; i++) {
    buckets[(arr[i] / exp) % 10]++;
    } // 計算資料在temp[]中的位置 0 1 2 2 3 --> 0 1 3 5 8
    for (int i = 1; i < 10; i++) {
    buckets[i] += buckets[i - 1];
    } // 將資料儲存到臨時陣列temp[]中
    for (int i = arr.length - 1; i >= 0; i–) {
    temp[buckets[(arr[i] / exp) % 10] - 1] = arr[i];
    buckets[(arr[i] / exp) % 10]–;
    } // 將排序好的資料賦值給arr[]
    for (int i = 0; i < arr.length; i++) {
    arr[i] = temp[i];
    }
    }

  • 桶排序
    /
    private void bucketSort(int[] arr, int exp) {
    int[][] buckets = new int[10][arr.length]; //這是二維陣列組成的桶
    int[] counter = new int[10]; //此陣列用來記錄0-9每個桶中的數字個數,計數器
    for (int i = 0; i < arr.length; i++) {
    int index = (arr[i] / exp) % 10; //得出相應位置(如個位、十位)上的數字
    buckets[index][counter[index]] = arr[i]; //取出來放到桶裡
    counter[index]++; //相應的計數器加1
    } int position = 0;
    //合併桶
    for (int i = 0; i < 10; i++) {
    for (int j = 0; j < counter[i]; j++) {
    arr[position++] = buckets[i][j];
    }
    }
    }
    文章來自:https://www.itjmd.com/news/show-5305.html