程序員,你應該知道的基礎排序算法

冒泡排序(Bubble Sort)

原理

冒泡排序只會操做相鄰的兩個數據。每次冒泡操做都會對相鄰的兩個元素進行比較,看是否知足大小關係要求。若是不知足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置,重複 n 次,就完成了 n 個數據的排序工做。java

圖解

圖片來源網絡,侵權即刪

性能

  • 時間複雜度:O(n²)
  • 最好時間複雜度:O(n)
  • 最差時間複雜度:O(n²)
  • 平均時間複雜度:O(n²)
  • 空間複雜度:O(1)

代碼

public static void bubbleSort(int[] a, int n) {
        if (n <= 1) return;

        for (int i = 0; i < n; ++i) {
            // 若是沒有數據交換,則提早退出,提早退出冒泡循環的標誌位
            boolean flag = false;
            
            for (int j = 0; j < n - i - 1; ++j) {
                if (a[j] > a[j+1]) { // 交換
                    int tmp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = tmp;
                    flag = true;  // 表示有數據交換
                }
            }
            if (!flag) break;  // 沒有數據交換,提早退出
        }
    }

插入排序(Insertion Sort)

原理

插入排序就像咱們玩棋牌同樣,每次摸的牌咱們都會插入到合適的位置,保證咱們的牌是有序的,在這個過程當中咱們有一個比大小的操做,插入排序沒咱們手動方便,插入排序除了比大小以外還須要元素移動。例如:須要將一個數據 a 插入到已排序區間時,須要拿 a 與已排序區間的元素依次比較大小,找到合適的插入位置。找到插入點以後,咱們還須要將插入點以後的元素順序日後移動一位,這樣才能騰出位置給元素 a 插入。面試

圖解

圖片來源網絡,侵權即刪

性能

  • 時間複雜度:O(n²)
  • 最好時間複雜度:O(n)
  • 最差時間複雜度:O(n²)
  • 平均時間複雜度:O(n²)
  • 空間複雜度:O(1)

代碼

// 插入排序,a 表示數組,n 表示數組大小
    public static void insertionSort(int[] a, int n) {
        if (n <= 1) return;

        for (int i = 1; i < n; ++i) {
            int value = a[i];
            int j = i - 1;
            // 查找插入的位置
            for (; j >= 0; --j) {
                if (a[j] > value) {
                    a[j + 1] = a[j];  // 數據移動
                } else {
                    break;
                }
            }
            a[j + 1] = value; // 插入數據
        }
    }

選擇排序(Selection Sort)

原理

選擇排序的思想很簡單,從數組的下標i=0開始,在i+1後面找出最小的一個元素與i對應的值比大小,若是i+1的值小於i的值,則將這兩個數進行交換,而後i++後面重這種操做,直到整個數組排好序。算法

圖解

圖片來源網絡,侵權即刪

性能

  • 時間複雜度:O(n²)
  • 最好時間複雜度:O(n²)
  • 最差時間複雜度:O(n²)
  • 平均時間複雜度:O(n²)
  • 空間複雜度:O(1)

代碼

public static void sort(int arr[],int n){
    for( int i = 0;i < n ; i++ ){
        int min = i;//最小元素的下標
        for(int j = i + 1;j < n; j++ ){
            if(arr[j] < arr[min]){
                min = j;//找最小值
            }
        }
        //交換位置
        int temp = arr[i];
        arr[i] = arr[min];
        arr[min] = temp;
    }
}

歸併排序(Merge Sort)

原理

歸併排序採用的是分而治之的思想,咱們將要排序的數組從中間分紅兩半,分別對先後兩個數組進行排序,而後將接口合併,這樣整個數據就有序了。編程

歸併排序通常咱們採用遞歸的方式,將數組進行拆分到不可分爲止,而後對不可拆分的數組進行排序,左右兩半數組分別從第一個開始逐一比較大小,放入到新的臨時數組中,當左右兩半的值同樣時,將左邊的值先放入到數組中,最後判斷左右兩邊是否有剩餘,將剩餘的數組加入到臨時數組中,通過上面的操做後,整個數組都有序了,最後須要將臨時數組的值拷貝回原數組中,整個歸併排序就完成了。數組

圖解

咱們以數組40, 2, 11, 5, 15, 6, 90, 10來演示歸併排序的過程
微信

性能

  • 時間複雜度:O(nlogn)
  • 最好時間複雜度:O(nlogn)
  • 最差時間複雜度:O(nlogn)
  • 平均時間複雜度:O(nlogn)
  • 空間複雜度:O(n)

代碼

/**
     * 遞歸 將數組分紅兩半,分別對先後兩部分排序
     *
     * @param nums     數組
     * @param leftPtr  左半邊開始下標
     * @param rightPtr 右半邊結束下標
     */
    public static void merge_sort(int[] nums, int leftPtr, int rightPtr) {
        if (leftPtr >= rightPtr) return;
        // 將數組分紅兩半
        int mid = leftPtr + (rightPtr - leftPtr) / 2;

        // 左半邊排序
        merge_sort(nums, leftPtr, mid);

        // 右半邊排序
        merge_sort(nums, mid + 1, rightPtr);

        merge(nums, leftPtr, mid + 1, rightPtr);
    }

    /**
     * 將先後兩半排好序的數組進行合併
     *
     * @param nums
     * @param leftPtr    左半邊開始下標值
     * @param rightPtr   右半邊開始下標值
     * @param rightBound 左半邊結束值
     */
    public static void merge(int[] nums, int leftPtr, int rightPtr, int rightBound) {
        // 新開闢臨時排序數組
        int[] sortNums = new int[rightBound - leftPtr + 1];
        // 求出中間值
        int mid = rightPtr - 1;
        // 前半部分數組起始下標
        int i = leftPtr;
        // 後半部分起始下標
        int j = rightPtr;

        // 臨時排序數組的起始下標
        int k = 0;

        // 左右兩邊分別逐一比較,將小的存入到臨時數組
        while (i <= mid && j <= rightBound) {
            sortNums[k++] = nums[i] <= nums[j] ? nums[i++] : nums[j++];
        }
        // 判斷左半邊時候有剩下
        while (i <= mid) sortNums[k++] = nums[i++];
        // 判斷右半邊時候有剩下
        while (j <= rightBound) sortNums[k++] = nums[j++];

        // 將數組拷貝回nums
        for (int m = 0; m < sortNums.length; m++) nums[leftPtr + m] = sortNums[m];
    }

快速排序(Quick Sort)

原理

快速排序也稱作快排,快排跟歸併排序同樣也是利分治思想。快排分爲單軸快排和雙軸快排,由於雙軸排序效率比單抽排序效率高,因此這篇主要講的是雙軸排序。快速排序每次從數組中隨機選擇一個元素做爲pivot(通常狀況下,選擇數組的最後一個元素),而後從數組的第一個元素和分區值前一個元素開始進行查找,從第一個元素開始查找比分區值大的第一個數A,從分區值前面的一個數往前開始查找,找到第一個比分區值小的數B,將AB交換位置,直到左邊開始查找的下標大於右邊開始查找的下標中止查找,而後將分區值與第一個大於分區值的數交換位置,這樣分區值左邊的數就所有小於分區值,右邊的數所有大於分區值。繼續遍歷左右兩邊的數組,直到整個數組排好序。網絡

圖解

咱們以數組40, 2, 11, 5, 15, 6, 90, 10來演示快速排序的過程

選擇數組最右邊的值做爲分區值,即黃顏色的數組10,創建兩個下標索引,一個指向數組的第一個元素left,一個指向分區值的前一位數組的元素right,即圖中用紅色箭頭標出來的地方。從左邊開始找出第一個比分區值大的元素,從右邊找出第一個比分區值小的元素,將這兩個元素進行交換而且將座標日後移,即圖中的第二步和第三步,按照上面的規則繼續查找,直到left > right 爲止。將分區值與left所指的元素進行交換,這樣分區值左邊的數都是比分區值小的,右邊的數都是被分區值大的。按照上面的規則繼續遍歷左右兩邊的數組,最後整個數組就排好序了。框架

性能

  • 時間複雜度:O(nlogn)
  • 最好時間複雜度:O(nlogn)
  • 最差時間複雜度:O(n²)
  • 平均時間複雜度:O(nlogn)
  • 空間複雜度:O(logn)

代碼

// 遞歸
    public static void sort(int[] nums, int leftBound, int rightBound) {
        if (leftBound >= rightBound) return;
        // 分區值的下標位置
        int mid = partition(nums, leftBound, rightBound);
        // 左分區排序
        sort(nums, leftBound, mid - 1);
        // 右分區排序
        sort(nums, mid, rightBound);
    }

    // 分區
    public static int partition(int[] nums, int leftBound, int rightBound) {

        // 分區點的值
        int piovt = nums[rightBound];

        // 左邊下標
        int left = leftBound;
        //右邊起始下標
        int right = rightBound - 1;

        while (left <= right) {
            // 找到第一個大於分區值的
            while (left <= right && nums[left] <= piovt) left++;

            // 找到第一個小於分區值的
            while (left <= right && nums[right] > piovt) right--;

            // 將左右兩邊的值進行交換
            if (left < right) swap(nums, left, right);
        }
        // 將left的值與分區值交換位置
        swap(nums, left, rightBound);

        return left;
    }

    /**
     * 數據交換
     *
     * @param nums
     * @param i
     * @param k
     */
    public static void swap(int[] nums, int i, int k) {
        int temp = nums[i];
        nums[i] = nums[k];
        nums[k] = temp;

    }

桶排序(Bucket sort)

原理

桶排序,顧名思義,會用到「桶」,核心思想是將要排序的數據按照範圍分到幾個桶裏,每一個桶裏的數據再單獨進行排序。桶內排完序以後,再把每一個桶裏的數據按照順序依次取出,組成的序列就是有序的了。性能

圖解

咱們以數組90,85,63,34,42,12,10來演示桶排序的過程
ui

性能

  • 時間複雜度:O(n)
  • 最好時間複雜度:O(n)
  • 最差時間複雜度:O(nlogn)
  • 平均時間複雜度:O(n)
  • 空間複雜度:O(n)

代碼

public static void sort(int[] nums) {
        //最大值
        int maxValue = nums[0];
        // 最小值
        int minValue = nums[0];

        int length = nums.length;

        /**
         * 求出最大最小值
         */
        for (int i = 1; i < length; i++) {
            if (nums[i] > maxValue) {
                maxValue = nums[i];
            } else if (nums[i] < minValue) {
                minValue = nums[i];
            }
        }
        //最大值和最小值的差,
        int diff = maxValue - minValue;

        //初始化桶列表
        ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();
        for (int i = 0; i < length; i++) {
            bucketList.add(new ArrayList<Integer>());
        }

        //桶之間的數值間距
        float section = (float) diff / (float) (length - 1);

        //數據入桶
        for (int i = 0; i < length; i++) {
            //當前數除以區間得出存放桶的位置 減1後得出桶的下標
            int num = (int) (nums[i] / section) - 1;
            if (num < 0) {
                num = 0;
            }
            bucketList.get(num).add(nums[i]);
        }

        //桶內排序
        for (int i = 0; i < bucketList.size(); i++) {

            //jdk內置的集合排序框架
            Collections.sort(bucketList.get(i));
        }

        //寫入原數組
        int index = 0;
        for (ArrayList<Integer> arrayList : bucketList) {
            for (int value : arrayList) {
                nums[index] = value;
                index++;
            }
        }
    }

計數排序(Counting sort)

原理

計數排序實際上是桶排序的一種特殊狀況,當要排序的 n 個數據,所處的範圍並不大的時候,好比最大值是 k,咱們就能夠把數據劃分紅 k 個桶。每一個桶內的數據值都是相同的,省掉了桶內排序的時間。好比騰訊面試題,不用數據交換,查詢出你的成績排名?考生的滿分是 750 分,最小是 0 分,這個數據的範圍很小,因此咱們能夠分紅 750 個桶,對應分數從 0 分到 750 分。根據考生的成績,咱們將這 50 萬考生劃分到這 750 個桶裏。桶內的數據都是分數相同的考生,因此並不須要再進行排序。咱們只須要依次掃描每一個桶,將桶內的考生依次輸出到一個數組中,就實現了 50 萬考生的排序。

圖解

咱們以數組5,8,9,7,8,4,5,6,9,3,0,2,1來演示計數排序的過程

性能

  • 時間複雜度:O(n)
  • 最好時間複雜度:O(n)
  • 最差時間複雜度:O(n)
  • 平均時間複雜度:O(n)
  • 空間複雜度:O(n)

代碼

/**
     * 計數排序
     *
     * @param nums       待排序數組
     * @param rangeCount 數組範圍個數
     * @param min        最小的個數
     * @return
     */
    public static int[] countingSort(int[] nums, int rangeCount, int min) {
        int[] result = new int[nums.length];

        // 定義計數桶
        int[] count = new int[rangeCount + min];

        // 將數據添加到桶裏
        for (int i = 0; i < nums.length; i++) {
            count[nums[i]]++;
        }

        // 遍歷桶 將數據寫入到返回數組中
        for (int i = min, j = 0; i < count.length; i++) {
            while (count[i]-- > 0) result[j++] = i;
        }
        return result;
    }

基數排序(Radix sort)

原理

基數排序也是桶排序的一種,基數排序對要排序的數據是有要求的,須要能夠分割出獨立的「位」來比較,並且位之間有遞進的關係,若是 a 數據的高位比 b 數據大,那剩下的低位就不用比較了,除此以外,每一位的數據範圍不能太大,要能夠用線性排序算法來排序。例如假設咱們有 10 萬個手機號碼,須要按照從小到大的順序對電話號碼排序,前面的快排、桶排序、計數排序均可以進行快速的排序,可是內存有限,不容許你作這樣的操做,這是就可使用基數排序來解決這個問題。

圖解

咱們以數組2154,5896,356,201,698,412來演示基數排序的過程

性能

  • 時間複雜度:O(n*k)
  • 最好時間複雜度:O(n*k)
  • 最差時間複雜度:O(n*k)
  • 平均時間複雜度:O(n*k)
  • 空間複雜度:O(n+k)

代碼

public static void radixSort(int[] nums){

        // 記錄數組的大小
        int length = nums.length;

        //最大值
        int numMax = nums[0];
        for(int i = 0;i < length;i++){
            if(nums[i] > numMax){
                numMax = nums[i];
            }
        }
        //當前排序位置
        int location = 1;

        //桶列表 一個桶中會有多個不一樣的元素
        ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();

        //初始化一個空桶
        for(int i = 0; i < 10; i++){
            bucketList.add(new ArrayList<Integer>());
        }

        while(true)
        {
            //求出每位數的最小值
            int min = (int)Math.pow(10,(location - 1));
            // 判斷最大值是否小於每位數的最小值,小於就結束
            if(numMax < min){
                break;
            }
            //遍歷數據,將數據寫入桶
            for(int i = 0; i < length; i++)
            {
                //計算餘數 放入相應的桶
                int number = ((nums[i] / min) % 10);
                bucketList.get(number).add(nums[i]);
            }
            //將數從桶中取回,從新組成數組
            int k = 0;
            for (int i=0;i<10;i++){
                int size = bucketList.get(i).size();
                for(int j = 0;j < size;j ++){
                    nums[k++] = bucketList.get(i).get(j);
                }
                // 將桶清空,用於下一次排序
                bucketList.get(i).clear();
            }
            // 位數加一
            location++;
        }
    }

以上就是編程中的基本排序算法,基本排序算法,在不少組件內部使用,若是對基礎排序瞭解的話,對你閱讀源碼仍是有些幫助的,很是感受你看到這裏,但願這篇文章對你有所幫助。

最後

打個小廣告,歡迎掃碼關注微信公衆號:「平頭哥的技術博文」,按期分享Java技術乾貨,讓咱們一塊兒進步。
平頭哥的技術博文公衆號

相關文章
相關標籤/搜索