1. 程式人生 > >資料結構與演算法之經典排序

資料結構與演算法之經典排序

1 經典演算法對比

這裡寫圖片描述

2 衡量演算法的優劣指標

2.1 三大指標

(1)時間複雜度:主要是分析關鍵字的比較次數和記錄的移動次數。
(2)空間複雜度:分析排序演算法中需要多少輔助記憶體
(3)穩定性:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj之前,而在排序後的序列中,ri仍在rj之前,則稱這種排序演算法是穩定的;否則稱為不穩定的。

2.2 指標的意義

(1)時間複雜度

  • 找出演算法中的基本語句:演算法中執行次數最多的那條語句就是基本語句,通常是最內層迴圈的迴圈體。
  • 計算基本語句的執行次數的數量級:只需計算基本語句執行次數的數量級,這就意味著只要保證基本語句執行次數的函式中的最高次冪正確即可,可以忽略所有低次冪和最高次冪的係數。這樣能夠簡化演算法分析,並且使注意力集中在最重要的一點上:增長率。

(2)空間複雜度

(3)穩定性的意義

  • 如果只是簡單的進行數字的排序,那麼穩定性將毫無意義。
  • 如果排序的內容僅僅是一個複雜物件的某一個數字屬性,那麼穩定性依舊將毫無意義
  • 如果要排序的內容是一個複雜物件的多個數字屬性,但是其原本的初始順序毫無意義,那麼穩定性依舊將毫無意義。
  • 除非要排序的內容是一個複雜物件的多個數字屬性,且其原本的初始順序存在意義,那麼我們需要在二次排序的基礎上保持原有排序的意義,才需要使用到穩定性的演算法,例如要排序的內容是一組原本按照價格高低排序的物件,如今需要按照銷量高低排序,使用穩定性演算法,可以使得想同銷量的物件依舊保持著價格高低的排序展現,只有銷量不同的才會重新排序。(當然,如果需求不需要保持初始的排序意義,那麼使用穩定性演算法依舊將毫無意義)。

2 參考連結

3 演算法分類解析

3.1 直接插入排序

3.1.1 核心思想

  核心思想:對n個數據要進行 n-1 趟比較,每趟比較 a[i] 與 a[i-1]~~a[0],即是a[i] 與 i 前面的資料逐個比較 ,目的就是將一個待排序的資料,插入到前面已經排好序的有序序列中去,直到插完所有元素為止。

3.1.2 演示圖

這裡寫圖片描述
這裡寫圖片描述

3.1.3 原始碼

 /**
     * 直接插入排序
     */
    private static void insertSort(int[] arr) {
        if (arr == null
|| arr.length < 2) { return; } for (int i = 1; i < arr.length; i++) { for (int j = i - 1; j >= 0; j--) { //j=i-1開始,j--,直至j=-1 或 arr[j + 1] >= arr[j] if (arr[j + 1] <= arr[j]) { //arr[j+1] 與 j 以及前面的資料逐個比較, 將小的數插入到前一位 swap(arr, j, j + 1); } } } } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } public static void main0() { int[] a = {49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1}; Log.i("TAG", "排序之前:"); for (int anA : a) { Log.i("TAG", anA + " "); } Log.i("TAG", "排序之後:"); insertSort(a); for (int anA : a) { Log.i("TAG", anA + " "); } }

3.1.4 時間複雜度計算

1)最壞情況
9, 8, 7, 6, 5, 4, 3, 2, 1(逆序)
//所以時間複雜度是:O(N^2)

(2)最好情況
1, 2, 3, 4, 5, 6, 7, 8, 9(正序)
//所以時間複雜度是:O(N)

3.2 二分法插入排序(折半插入排序,是對直接插入排序的改進)

3.2.1 核心思想

  具體操作為:在將一個新元素temp插入已排好序的陣列的過程中,尋找插入點時, 將待插入區域的首元素設定為a[low],末元素設定為a[high],將temp與a[mid]比較,其中mid=(low+high)/2相比較;
  (1)如果比參考元素小,則選擇a[low]到a[m-1]為新的插入區域(即high=m-1);否則選擇a[m+1]到a[high]為新的插入區域(即low=m+1);
  (2)如此直至low<=high不成立,即將此位置之後所有元素後移一位,並將新元素插入a[low]。

3.2.2 演示圖

這裡寫圖片描述

3.2.3 原始碼

package com.dn.sort;
public class BinaryInsertSort {
    private static int[] binaryInsertSort(int[] a) {
        for (int i = 0; i < a.length; i++) {
            int temp = a[i];//待插入元素
            int low = 0;
            int high = i - 1;
            int mid = 0;
            // 確定要插入的位置
            while (low <= high) {
                // 找出low和high中間的索引  
                mid = (low + high) / 2;
                if (temp < a[mid]) {
                    // 如果值比中間值小,限制在小於mid的那一半搜尋,讓high左移到"中間下標-1"
                    high = mid - 1;
                } else {
                    // 如果值比中間值大,限制在大於mid的那一半搜尋,讓low右移到"中間下標+1"
                    low = mid + 1;
                }
            }

            //將low到i處的所有元素後移一位  
            for (int j = i - 1; j >= low; j--) {
                a[j + 1] = a[j];
            }
            //元素位置變化時
            if (low != i) {
                // 元素位置變化時插入新元素temp(因為插入都在mid的右邊mid+1,即是low)
                a[low] = temp;
            }
        }
        return a;
    }

    public static void main(String[] args) {
        int[] a = { 49, 38, 65, 97, 176, 213, 227, 49, 78, 34, 12, 164, 11, 18,
                1 };
        System.out.println("排序之前:");
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }

        // 二分插入排序
        a = binaryInsertSort(a);
        System.out.println();
        System.out.println("排序之後:");
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
    }
}

3.3 希爾排序(縮小增量排序)

3.3.1 核心思想

  核心思想:演算法先將要排序的一組數按某個增量d(n/2,n為要排序數的個數)分成若干組,每組中記錄的下標相差d.對每組中全部元素進行直接插入排序,然後再用一個較小的增量(d/2)對它進行分組,在每組中再進行直接插入排序。當增量減到1時,進行直接插入排序後,排序完成。

3.3.2 演示圖

這裡寫圖片描述

3.3.3 原始碼

package com.dn.sort;
//不穩定
public class HeerSort {
    private static int[] heerSort(int[] a) {
        System.out.println();
        int d = a.length / 2;//預設增量
        while (true) {
            for (int i = 0; i < d; i++) {
                for (int j = i; j + d < a.length; j += d) {//i開始,d為增量分組比較(eg:d=3; i=0; j=0、3,3、6,6、9,9、12,12、15)
                    int temp;
                    if (a[j] > a[j + d]) {//互換位置
                        temp = a[j];
                        a[j] = a[j + d];
                        a[j + d] = temp;
                    }
                }
            }
            if (d == 1) {
                break;
            }
            d--;//增量-1繼續比較
        }
        return a;
    }

    /**
     * 增強希爾排序&氣泡排序
     */
    private static int[] heerBubbleSort(int[] a) {
        System.out.println();
        int d = a.length / 2;
        while (true) {
            d = d/2;
            for (int i = 0; i < d; i++) {
                for (int j = i; j + d < a.length; j += d) {//i開始,d為增量分組比較並組內排序(eg:d=3; i=0; j=0、3、6、9、12、15)
                    for (int n = i; n + d < a.length; n += d) {//氣泡排序(組內排序)
                        int temp;
                        if (a[n] > a[n + d]) {//互換位置
                            temp = a[n];
                            a[n] = a[n + d];
                            a[n + d] = temp;
                        }
                    }
                }
            }
            if (d == 1) {
                break;
            }
            d--;//增量-1繼續比較
        }
        return a;
    }

    public static void main(String[] args) {
        int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1, 33, 85,29 };
        System.out.println("排序之前:");
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }

        a = heerBubbleSort(a);
        System.out.println();
        System.out.println("排序之後:");
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
    }
}

3.4 直接選擇排序

3.4.1 核心思想

  核心思想:在待排序的資料元素中,選出最小(或最大)的一個數與第一個位置的數交換;然後在剩下的數當中再找最小(或最大)的與第二個位置的數交換,直到所有元素排完為止,簡單選擇排序是不穩定排序。

3.4.2 演示圖

這裡寫圖片描述
這裡寫圖片描述

3.4.3 原始碼

/**
     * 選擇排序
     * 
     * @param arr
     */
    public static void selectSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        for (int i = 0; i < arr.length; i++) {
            int minIndex = i;
            for (int j = i + 1; j < arr.length; j++) {
                //找到最小數的下標
                minIndex = arr[j] < arr[minIndex] ? j : minIndex;
            }
            swap(arr, i, minIndex);
        }
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main2() {
        int[] array = new int[]{57,68,59,52};

        selectSort(array);

        Log.i("TAG", "選擇排序:");
        for (int num : array) {
            Log.i("TAG", num + " ");
        }
    }

3.5 堆排序

3.5.1 定義

  堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱為大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱為小頂堆。如下圖:
這裡寫圖片描述
這裡寫圖片描述

大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]  

小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]  

3.5.2 核心思想

  堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。堆排序的關鍵在於:①建堆(大頂堆或小頂堆);②拿堆的根節點和最後一個節點交換。堆排序的核心思想:
  (1)將無需序列構建成一個堆,根據升序降序需求選擇大頂堆或小頂堆;
  (2)將堆頂元素與末尾元素交換,將最大元素”沉”到陣列末端;
  (3)重新調整結構,使其滿足堆定義,然後繼續交換堆頂元素與當前末尾元素,反覆執行調整+交換步驟,直到整個序列有序。

3.5.3 儲存模式:

這裡寫圖片描述

3.5.4 演示圖

(1)構造初始堆。將給定無序序列構造成一個大頂堆(一般升序採用大頂堆,降序採用小頂堆)
這裡寫圖片描述
(2)index = 2;值為8,不符合情況
(3)index = 1;值為6,符合情況 —>largest=index=4
  從最後一個非葉子結點開始(葉結點自然不用調整,第一個非葉子結點 arr.length/2-1=5/2-1=1,也就是下面的6結點),從左至右,從下至上進行調整。
這裡寫圖片描述
(4)index = 4;值為6,不符合情況
(5)index = 0;值為4,符合情況 —>largest=index=1
  找到第二個非葉節點4,由於[4,9,8]中9元素最大,4和9交換:
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
(6)index = 1;值為4,符合情況
  交換導致了子根[4,5,6]結構混亂,繼續調整,[4,5,6]中6最大,交換4和6:
這裡寫圖片描述
(7)就將一個無需序列構造成了一個大頂堆
(8)將堆頂元素與末尾元素進行交換,使末尾元素最大。然後繼續調整堆,再將堆頂元素與末尾元素交換,得到第二大元素,如此反覆進行交換、重建、交換。
(9)將堆頂元素9和末尾元素4進行交換
這裡寫圖片描述
(10)重新調整結構,使其繼續滿足堆定義
這裡寫圖片描述
(11)再將堆頂元素8與末尾元素5進行交換,得到第二大元素8
這裡寫圖片描述
(12)後續過程,繼續進行調整,交換,如此反覆進行,最終使得整個序列有序
這裡寫圖片描述

3.5.5 參考連結

3.5.5 原始碼

package com.dn.sort;
public class HeapSort { 
    public void heapSort(int[] array) {
        if (array == null || array.length <= 1) {
            return;
        }
        // 建立最大堆
        buildMaxHeap(array);
        // 排序
        for (int i = array.length - 1; i >= 1; i--) {
            // 最大的在0位置,那麼開始沉降,這樣每交換一次最大的值就丟到最後了
            exchangeElements(array, 0, i);
            // 調整大堆
            maxHeap(array, i, 0);//i--,不遍歷最後的值
        }
    }

    /**
     * 建立最大堆
     * @param array
     */
    private void buildMaxHeap(int[] array) {
        if (array == null || array.length <= 1) {
            return;
        }
        int half = (array.length - 1) / 2;//遍歷一半(即可得到另一半,最深一層),假設有5個
        for (int i = half; i >= 0; i--) {//從中間序號8開始
            //調整大堆,只需遍歷i=2、1、0
            maxHeap(array, array.length, i);
        }
    }

    /**
     * 調整大堆️
     * @param array 堆陣列
     * @param length 表示用於構造大堆的陣列長度元素數量
     * @param index 從哪位置開始
     */
    private void maxHeap(int[] array, int length, int index) {
        int left = index * 2 + 1;//左節點
        int right = index * 2 + 2;//右節點
        int largest = index;//目標序號
        //left < length安全範圍內
        if (left < length && array[left] > array[index]) {
            largest = left;//左節點大於根節點,將左序號需要賦值為目標序號
        }
        if (right < length && array[right] > array[largest]) {
            largest = right;//右節點大於根節點或左節點中一個,將右序號需要賦值為目標序號
        }
        if (index != largest) {//largest位置元素不是最大值
            //資料交換
            exchangeElements(array, index, largest);
            //繼續調整大堆
            maxHeap(array, length, largest);//largest==變化的值
        }
    }

    /**
     * 資料交換
     */
    public void exchangeElements(int[] array, int index1, int index2) {
        int temp = array[index1];
        array[index1] = array[index2];
        array[index2] = temp;
    }

    /**
     * 列印
     */
    public void printArray(int[] array) {
        System.out.print("{");
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]);
            if (i < array.length - 1) {
                System.out.print(", ");
            }
        }
        System.out.println("}");
    }

    public static void main(String[] args) {
        HeapSort heapSort = new HeapSort();
        int[] array = { 19, 8, 27, 6, 35, 14, 3, 12, 1, 0, 9, 10, 7 };

        System.out.println("Before heap:");
        heapSort.printArray(array);

        heapSort.heapSort(array);

        System.out.println("After heap sort:");
        heapSort.printArray(array);
    }
}

3.6 氣泡排序

3.6.1 核心思想

  基本思想:進行 n-1 趟比較並交換,對相鄰的元素進行兩兩比較,大小不相等則進行交換,每一趟會將最小或最大的元素“冒”到末端,最終達到完全有序。

3.6.2 演示圖

這裡寫圖片描述

3.6.3 原始碼

 /**
     * 氣泡排序
     *
     * @param arr
     */
    public static void bubbleSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        for (int end = arr.length - 1; end > 0; end--) {//N-1趟排序
            for (int i = 0; i < end; i++) {//N-1次關鍵字的比較
                if (arr[i] > arr[i + 1]) {
                    swap(arr, i, i + 1);
                }
            }
        }
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main3() {
        int arr[] = {9, 3, 1, 4, 2, 7, 8, 6, 5};
        Log.i("TAG", "排序前:");
        for (int data : arr) {
            Log.i("TAG", data + " ");
        }

        bubbleSort(arr);

        Log.i("TAG", "排序後:");
        for (int data : arr) {
            Log.i("TAG", data + " ");
        }
    }

3.6.4 時間複雜度計算

1)最壞情況
//第1趟,將9交換到arr.length-1的位置                  --->N-1次比較 (0~~arr.length-1)
//第2趟,將3交換到arr.length-2的位置                  --->N-1
//第3趟,將9交換到arr.length-3的位置                  --->N-2
......
//第N-1趟,將a交換到arr.length-N的位置                --->1次比較 (0~~1)

//計算公式是:(N-1+1)*(N-1)/2=N*(N-1)/2=(N^2+N)/2 
//所以時間複雜度是:O(N^2)

(2)最好情況
1, 2, 3, 4, 5, 6, 7, 8, 9(已經排好序)
//所以時間複雜度是:O(N)

3.7 快速排序

3.7.1 核心思想

  快速排序死對氣泡排序的一種改進。
  基本思想是:選擇一個基準元素,通常選擇第一個元素或者最後一個元素,通過一趟掃描,將待排序列分成兩部分,一部分比基準元素小,一部分大於等於基準元素。此時基準元素在其排好序後的正確位置,然後再用同樣的方法遞迴地排序劃分的兩部分。

3.7.2 演示圖

這裡寫圖片描述=

3.7.3 原始碼

package com.dn.sort;

public class quickSort {
    public static int getMiddle(int[] list, int low, int high) {
        int temp = list[low]; // 陣列的第一個作為中軸
        while (low < high) {
            while (low < high && list[high] >= temp) {
                high--;
            }
            list[low] = list[high]; // 比中軸小的記錄移到低端

            while (low < high && list[low] <= temp) {
                low++;
            }
            list[high] = list[low]; // 比中軸大的記錄移到高階
        }
        list[low] = temp; // 中軸記錄到
        return low; // 返回中軸的位置
    }

    public static void quickSort(int[] list, int low, int high) {
        if (low < high) {
            int middle = getMiddle(list, low, high); // 將list陣列進行一分為二
            quickSort(list, low, middle - 1); // 對低欄位進行遞迴排序
            quickSort(list, middle + 1, high); // 對高欄位進行遞迴排序
        }
    }

    public static void main(String[] args) {
        int a[] = { 49, 99, 56, 67, 18, 23, 34, 15, 35, 25, 73, 51 };
        if (a.length > 0) { // 檢視陣列是否為空
            quickSort(a, 0, a.length - 1);
        }
        for (int i = 0; i < a.length; i++)
            System.out.print(a[i] + "  ");
    }
}

3.8 歸併排序

3.8.1 核心思想

  基本思想是:歸併排序(MERGE-SORT)是利用歸併的思想實現的排序方法,該演算法採用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題然後遞迴求解,而治(conquer)的階段則將分的階段得到的各答案”修補”在一起,即分而治之)。
  細化來說,歸併排序現將長度為n的無序序列看成是n個長度為1的有序子序列,首先做兩兩合併,得到n/2個長度為2的有序子序列,再做兩兩合併…不斷重複這個過程,最終可以得到一個長度為n的有序序列。

3.8.2 演示圖

這裡寫圖片描述
這裡寫圖片描述

3.8.3 演示步驟

//第1輪:[10,4] --> [4,10] ; [6,3] --> [3,6]
//得到:4      10       3       6
//位置:0      1        2       3
//標註:left middle rightStart right
//第2輪
//left=0,rightStart=2,4>3   --->a,temp=0,tempArray[0]=3
//left=0,rightStart=3,4<6   --->b,temp=1,tempArray[1]=4
//left=1,rightStart=3,10>6  --->a,temp=2,tempArray[2]=6
//left=1,rightStart=4,      --->c,temp=3,tempArray[3]=10

3.8.4 原始碼

package com.dn.sort;
public class MergeSort {
    public void mergeSort(int[] a, int left, int right) {
        if (left < right) {
            int middle = (left + right) / 2; //找出中間索引
            mergeSort(a, left, middle); //對左邊陣列進行遞迴
            mergeSort(a, middle + 1, right); //對右邊陣列進行遞迴
            merge(a, left, middle, right); //合併
        }
    }

    private void merge(int[] a, int left, int middle, int right) {
        //快取陣列
        int[] tempArray = new int[a.length];
        //右起第一個索引
        int rightStart = middle + 1;
        //記錄左邊第0個索引
        int leftPos = left;
        //temp記錄快取陣列的索引
        int temp = left;
                                                           //0      1        2       3
        //比較兩個小陣列相應下標位置的陣列大小,小的先放進快取陣列//left middle rightStart right
        while (left <= middle && rightStart <= right) {    //4      10       3       6
            if (a[left] <= a[rightStart]) {                //4>3,a ---> 4<6,b ---> 10>6,a ---> c
                tempArray[temp++] = a[left++];//b          //3      4      6      10
            } else {
                tempArray[temp++] = a[rightStart++];//a
            }
        }
        //如果左邊還有資料需要拷貝,把左邊陣列剩下的拷貝到快取陣列
        while (left <= middle) {
            tempArray[temp++] = a[left++];//c
        }
        //如果右邊還有資料......
        while (rightStart <= right) {
            tempArray[temp++] = a[rightStart++];
        }
        //將快取陣列中的內容複製回原陣列
        while (leftPos <= right) {
            a[leftPos] = tempArray[leftPos++];
        }
    }

    public static void main(String[] args){
        MergeSort mergeSort = new MergeSort();
        int [] a = new int[]{10,4,6,3,8,2,5,7};      
        mergeSort.mergeSort(a, 0, a.length-1);
        for(int n:a){
            System.out.print(" "+n);
        }
    }
}

3.9 基數排序

3.9.1 核心思想

  基本思想是:將所有待比較數值(正整數)統一為同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後,數列就變成一個有序序列。

3.9.2 演示圖

這裡寫圖片描述
(1)個位—把個位的數值排序好:
這裡寫圖片描述
(2)十位—把十位的數值排序好:
這裡寫圖片描述
(3)百位—把百位的數值排序好:
這裡寫圖片描述

3.9.3 原始碼

package com.dn.sort;
public class BasicSort {
    public void basicSort(int[] array) {
        int max = 0;// 獲取最大值
        int digit = 10;//0-9
        int times = 0;// 獲取最大值位數

        for (int num : array) {
            if (max < num) {
                max = num;
            }
        }
        while (max > 0) {
            max = max / 10;
            times++;
        }

        //建立10個集合(0-9)
        List<ArrayList> baseList = new ArrayList<ArrayList>();// 多維陣列
        for (int i = 0; i < digit; i++) {
            ArrayList list1 = new ArrayList<>();
            baseList.add(list1);
        }

        //進行times次分配和收集;
        for (int i = 0; i < times; i++) {
            //分配陣列元素
            for (int j = 0; j < array.length; j++) {
                // 獲取對應的位的值(pow是平方,i為0是個位,i為1是10位,i為2是百位)
                int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);//取餘、取整
                ArrayList list2 = baseList.get(x);
                list2.add(array[j]);// 把元素新增進對應下標陣列
            }

            int count = 0;//元素計數器;
            //收集佇列元素;
            for (int j = 0; j < digit; j++) {//0-9
                while (baseList.get(j).size() > 0) {
                    ArrayList<Integer> list3 = baseList.get(j);// 拿到每一個集合
                    array[count] = list3.get(0);//獲取集合第一個,每次都是新的資料
                    list3.remove(0);//刪除分配給集合的資料
                    count++;
                }
            }
        }
    }

    public static void main(String[] args) {
        BasicSort basicSort = new BasicSort();
        int[] a = { 136, 2, 6, 8, 9, 2, 8, 11, 23, 56, 34, 90, 89, 29, 145,209, 320, 78, 3 };
        basicSort.basicSort(a);
        for (int n : a) {
            System.out.print(" " + n);
        }
    }
}

3.10 桶排序

3.10.1 背景

  給阿里 2 萬多名員工按年齡排序應該選擇哪個演算法?阿里員工特徵,2萬人,規模較小;處於18-99,尤其是23-40佔了大部分,使用桶排序適合。

3.10.2 核心思想

  基本思想是:桶排序(Bucket Sort)的原理很簡單,它是將陣列分到有限數量的桶子裡。
  過程:假設待排序的陣列a中共有N個整數,並且已知陣列a中資料的範圍[0, MAX)。在桶排序時,建立容量為MAX的桶陣列r,並將桶陣列元素都初始化為0;將容量為MAX的桶陣列中的每一個單元都看作一個”桶”。在排序時,逐個遍歷陣列a,將陣列a的值,作為”桶陣列r”的下標。當a中資料被讀取時,就將桶的值加1。例如,讀取到陣列a[3]=5,則將r[5]的值+1。
  特徵:(1)桶排序是穩定的; (2)桶排序是常見排序演算法中最快的一種,大多數情況下比快排和歸併排序還要快 (3)桶排序非常快但是也非常消耗空間,典型的以空間換時間,基本上是最耗記憶體的一種排序演算法。

3.10.3 演示圖

  bucketSort(a, n, max)是作用是對陣列a進行桶排序,n是陣列a的長度,max是陣列中最大元素所屬的範圍[0,max)。假設a={8,2,3,4,3,6,6,3,9}, max=10。此時,將陣列a的所有資料都放到需要為0-9的桶中。如下圖:
這裡寫圖片描述

3.10.4 原始碼

public class BucketSort {
    /*
     * 桶排序
     * 引數說明:
     *     a -- 待排序陣列
     *     max -- 陣列a中最大值的範圍
     */
    public static void bucketSort(int[] a, int max) {
        int[] buckets;

        if (a==null || max<1)
            return ;

        // 建立一個容量為max的陣列buckets,並且將buckets中的所有資料都初始化為0。
        buckets = new int[max];

        // 1. 計數
        for(int i = 0; i < a.length; i++) 
            buckets[a[i]]++; 

        // 2. 排序
        for (int i = 0, j = 0; i < max; i++) {
            while( (buckets[i]--) >0 ) {
                a[j++] = i;
            }
        }

        buckets = null;
    }

    public static void main(String[] args) {
        int i;
        int a[] = {8,2,3,4,3,6,6,3,9};

        System.out.printf("before sort:");
        for (i=0; i<a.length; i++)
            System.out.printf("%d ", a[i]);

        bucketSort(a, 10); // 桶排序

        System.out.printf("after  sort:");
        for (i=0; i<a.length; i++)
            System.out.printf("%d ", a[i]);
    }
}
before sort:8 2 3 4 3 6 6 3 9 
after  sort:2 3 3 3 4 6 6 8 9 

3.10.5 參考連結

桶排序

4 總結

4.1 穩定性

(1)穩定:氣泡排序、插入排序、歸併排序和基數排序。
(2)不穩定:選擇排序、快速排序、希爾排序、堆排序。
(3)參考連結:排序演算法的穩定性及其意義

4.2 平均時間複雜度

(1)O(n