十大排序算法(java實現)

十大經常使用排序算法(java實現)

  • 【前言】最近在從新研究算法,此篇博文供本身複習使用也爲方便廣大程序員同窗!此文代碼均爲本身實現,經過對比經典解法校驗,如有錯請讀者及時提出!

- 【對比分析圖】首先,咱們先來對比分析一下這十大排序算法的特色:
這裏寫圖片描述
java

這裏寫圖片描述

(一).冒泡排序(優化)程序員

【題目】對於一個int數組,請編寫一個冒泡排序算法,對數組元素排序。
給定一個int數組A及數組的大小n,請返回排序後的數組。 web

  • 冒泡排序,顧名思義,從下往上遍歷,每次遍歷往上固定一個最小值
  • 加一個標誌位,當某一趟冒泡排序沒有元素交換時,則冒泡結束,元素已經有序,能夠有效的減小冒泡次數。
import java.util.*;

public class BubbleSort {
    public int[] bubbleSort(int[] A, int n) {
        //冒泡排序:從後往前(從下往上)就像冒泡同樣
        //用flag做爲標記,標記數組是否已經排序完成
        boolean flag = true;
        //固定左邊的數字
        for(int i=0; i<n-1&flag; i++){
            flag = false;
            //從後面(下面)往前(上)遍歷
            for(int j=n-2;j>=i;j--){

                if(A[j]>A[j+1]){
                    swap(A,j,j+1);
                    flag = true;
                }
            }
        }

        return A;

    }
    //數組是按引用傳遞,在函數中改變數組起做用
    private void swap(int[] A,int i,int j){
        int temp = A[i];
        A[i] = A[j];
        A[j] = temp;
    }
}

(二).簡單選擇排序算法

【題目】 對於一個int數組,請編寫一個選擇排序算法,對數組元素排序。
給定一個int數組A及數組的大小n,請返回排序後的數組。shell

  • 1.【初始升序】:交換0次,時間複雜度爲o(n); 【初始降序】:交換n-1次,時間複雜度爲o(n^2)。
  • 【特色】:交換移動數據次數少,比較次數多。
import java.util.*;
/** **/
public class SelectionSort {
    public int[] selectionSort(int[] A, int n) {
       //簡單選擇排序算法,排序結果爲遞增數組
        //記錄最小下標值
        int min=0;
        //固定左邊的數字
        for(int i=0; i<A.length-1;i++){
            min = i;
            //找到下標i開始後面的最小值
            for(int j=i+1;j<A.length;j++){

                 if(A[min]>A[j]){
                     min = j;
                 }
            }
           //確保穩定排序,數值相等就不用交換
            if(i!=min){
                swap(A,i,min);
            }
        }

        return A;

    }

    //數組是按引用傳遞,在函數中改變數組起做用
    private void swap(int[] A,int i,int j){
        int temp = A[i];
        A[i] = A[j];
        A[j] = temp;
    }

}

(三).直接插入排序數組

【題目】對於一個int數組,請編寫一個插入排序算法,對數組元素排序。
給定一個int數組A及數組的大小n,請返回排序後的數組。ide

import java.util.*;

public class InsertionSort {
    public int[] insertionSort(int[] A, int n) {
      //用模擬插入撲克牌的思想
        //插入的撲克牌
        int i,j,temp;
        //已經插入一張,繼續插入
        for(i=1;i<n;i++){

            temp = A[i];
            //把i前面全部大於要插入的牌的牌日後移一位,空出一位給新的牌
            for(j=i;j>0&&A[j-1]>temp;j--){
                A[j] = A[j-1];
            }
            //把空出來的一位填滿插入的牌
            A[j] = temp;

        }
        return A;


    }
}

(四).希爾排序svg

【題目】對於一個int數組,請編寫一個希爾排序算法,對數組元素排序。
給定一個int數組A及數組的大小n,請返回排序後的數組。保證元素小於等於2000。函數

  • 基本思想:算法先將要排序的一組數按某個增量d(n/2,n爲要排序數的個數)分紅若干組,每組中記錄的下標相差d.對每組中所有元素進行直接插入排序,而後再用一個較小的增量(d/2)對它進行分組,在每組中再進行直接插入排序。當增量減到1時,進行直接插入排序後,排序完成。性能

  • 希爾排序法(縮小增量法) 屬於插入類排序,是將整個無序列分割成若干小的子序列分別進行插入排序的方法。

import java.util.*;

public class ShellSort {
    public int[] shellSort(int[] A, int n) {
        //要插入的紙牌
        int temp,j,i;
        //設定增量D,增量D/2逐漸減少
        for(int D = n/2;D>=1;D=D/2){

            //從下標d開始,對d組進行插入排序
            for(j=D;j<n;j++){

                temp = A[j];
                for(i=j;i>=D&&A[i-D]>temp;i-=D){
                    A[i]=A[i-D];
                }

                A[i]=temp;
            }

        }

        return A;
    }
}

(五).堆排序

【題目】對於一個int數組,請編寫一個堆排序算法,對數組元素排序。
給定一個int數組A及數組的大小n,請返回排序後的數組。

  • 【堆】 1.堆是徹底二叉樹 2.大頂堆:每一個結點的值都大於或等於其左右孩子結點的值,稱爲大頂堆。3.小頂堆:每一個結點的值都大於或等於其左右孩子結點的值,稱爲小頂堆。
  • 【徹底二叉樹性質5】:若是i>1,則雙親是結點[i/2]。也就是說下標i與2i和2i+1是雙親子女關係。 當排序對象爲數組時,下標從0開始,因此下標 i 與下標 2i+1和2i+2是雙親子女關係。
import java.util.*;

public class HeapSort {
    public int[] heapSort(int[] A, int n) {
        //堆排序算法

        int i;
        //先把A[]數組構建成一個大頂堆。
        //從徹底二叉樹的最下層最右邊的非終端結點開始構建。
        for(i=n/2-1;i>=0;i--){
            HeapAdjust(A,i,n);
        }

        //開始遍歷
        for(i=n-1;i>0;i--){
            swap(A,0,i);
            //每交換一次獲得一個最大值而後丟棄
            HeapAdjust(A,0,i);
        }
        return A;

    }
    //A[i]表明的是下標爲i的根結點
    private void HeapAdjust(int[] A,int i,int n){
        //【注意】這裏下標從0開始
        int temp;
        //存儲根結點
        temp = A[i];
        //沿根結點的左右孩子中較大的往下遍歷,因爲徹底二叉樹特性 i的左子節點2i+1 i的右子節點2i+2
        for(int j=2*i+1;j<n;j=j*2+1){

            if(j<n-1&&A[j]<A[j+1]){
                ++j;
            }

            if(temp>=A[j]){
                break;
            }
            //將子節點賦值給根結點
            A[i] = A[j];
            //將子節點下標賦給i
            i = j;
        }
        //將存儲的根結點的值賦給子節點
        A[i] = temp;

    }
    private void swap(int[] A,int i,int j){
        int temp = A[i];
        A[i]=A[j];
        A[j] = temp;

    }
}

(六).歸併排序算法

【題目】對於一個int數組,請編寫一個歸併排序算法,對數組元素排序。
給定一個int數組A及數組的大小n,請返回排序後的數組。

  • 先用遞歸方法,默認排序方法爲2路歸併排序。看下圖應該能當即理解:
    這裏寫圖片描述
  • 先使每一個子序列有序,再將兩個已經排序的序列合併成一個序列的操做。若將兩個有序表合併成一個有序表,稱爲二路歸併。
import java.util.*;

public class MergeSort {
    public int[] mergeSort(int[] A, int n) {
       //歸併排序,遞歸作法,分而治之

        mSort(A,0,n-1);
        return A;
    }

    private void mSort(int[] A,int left,int right){
        //分而治之,遞歸經常使用的思想,跳出遞歸的條件
        if(left>=right){
            return;
        }
        //中點
        int mid = (left+right)/2;
        //有點相似後序遍歷!
        mSort(A,left,mid);
        mSort(A,mid+1,right);

        merge(A,left,mid,right);



    }

    //將左右倆組的按序子序列排列成按序序列
    private void merge(int[] A,int left,int mid,int rightEnd){
        //充當tem數組的下標
        int record = left;
        //最後複製數組時使用
        int record2 = left;
        //右子序列的開始下標
        int m =mid+1;
        int[] tem = new int[A.length];

        //只要left>mid或是m>rightEnd,就跳出循環
        while(left<=mid&&m<=rightEnd){

            if(A[left]<=A[m]){

                tem[record++]=A[left++];
            }else{
                tem[record++]=A[m++];
            }

        }
        while(left<=mid){
            tem[record++]=A[left++];
        }
        while(m<=rightEnd){
            tem[record++]=A[m++];
        }
        //複製數組
        for( ;record2<=rightEnd;record2++){
            A[record2] = tem[record2];
        }

    }

}

(七).快速排序算法

【題目】對於一個int數組,請編寫一個快速排序算法,對數組元素排序。
給定一個int數組A及數組的大小n,請返回排序後的數組。

  • 【基本思想】:快速排序(Quicksort)是對冒泡排序的一種改進,使用分治法(Divide and conquer)策略來把一個序列(list)分爲兩個子序列(sub-lists)。

  • 【步驟爲】

  • 從數列中挑出一個元素,稱爲」樞軸」(pivot)。
  • 從新排序數列,全部元素比樞軸值小的擺放在基準前面,全部元素比樞軸值大的擺在樞軸的後面(相同的數能夠到任一邊)。在這個分區結束以後,該樞軸就處於數列的中間位置。這個稱爲分區(partition)操做。
  • 遞歸地(recursive)把小於樞軸值元素的子數列和大於樞軸值元素的子數列排序。
import java.util.*;

public class QuickSort {
    public int[] quickSort(int[] A, int n) {
        //快速排序

        qSort(A,0,n-1);

        return A;

    }
    public void qSort(int[] A,int left,int right){

         //樞軸
        int pivot;
        if(left<right){

            pivot = partition(A,left,right);

            qSort(A,left,pivot-1);
            qSort(A,pivot+1,right);

        }      

    }

    //優化選取一個樞軸,想盡辦法把它放到一個位置,使它左邊的值都比它小,右邊的值都比它大
    public int partition(int[] A,int left,int right){

        //優化選取樞軸,採用三數取中的方法
        int pivotKey = median3(A,left,right);
        //從表的倆邊交替向中間掃描
        //樞軸用pivotKey給備份了
        while(left<right){
           while(left<right&&A[right]>=pivotKey){
               right--;
           }
            //用替換方式,由於樞軸給備份了,多出一個存儲空間
            A[left]=A[right];
           while(left<right&&A[left]<=pivotKey){
               left++;
           }
           A[right]=A[left];

        }

        //把樞軸放到它真正的地方
        A[left]=pivotKey;
        return left;
    }
    //三數取中
    public int median3(int[] A,int left,int right){

        int mid=(right-left)/2;
        if(A[left]>A[right]){
            swap(A,left,right);
        }
        if(A[mid]>A[left]){
            swap(A,mid,left);
        }
        if(A[mid]>A[right]){
            swap(A,mid,right);
        }

        return A[left];
    }

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



}
  • 【快排效率】
  • 時間性能取決於遞歸的深度,能夠用遞歸樹來描述遞歸算法的執行狀況!最優狀況下,partition每次劃分均勻,排序n個數值,則遞歸樹的深度爲[logn]+1,第一次partition須要對整個數組掃描一遍,作n次比較,第二次對一半掃描。因此最優時間複雜度 Ο(n log n) ,最差時間複雜度 Ο(n^2) ,平均時間複雜度Ο(n log n) 。

  • 遞歸致使棧空間的使用,最好空間複雜度爲Ο(log n),最差空間複雜度Ο(n)。


(八).桶排序算法
【桶排序的步驟】

  • 設置一個定量的數組看成空桶子。
  • 尋訪序列,而且把項目一個一個放到對應的桶子去。
  • 對每一個不是空的桶子進行排序。
  • 從不是空的桶子裏把項目再放回原來的序列中。

(九).計數排序算法(實際上就是桶排序算法)

【特色】
1. 提早必須是已知待排序的關鍵字爲整型且範圍已知。
2. 時間複雜度爲O(n+k),n指的是桶的個數,k指的是待排序數組的長度,不是基於比較的排序算法,所以效率很是之高。
3. 穩定性好,這個是計數排序很是重要的特性,能夠用在後面介紹的基數排序中。
4. 但須要一些輔助數組,如C[0..k],所以待排序的關鍵字範圍0~k不宜過大。

import java.util.*;

public class CountingSort {
    public int[] countingSort(int[] A, int n) {
        if(A==null ||n<2){
            return A;
        }
        //找出桶的範圍,即經過要排序的數組的最大最小值來肯定桶範圍
        int min=A[0];
        int max=A[0];
        for(int i=0;i<n;i++){
            min=Math.min(A[i],min);
            max=Math.max(A[i],max);
        }
        //肯定桶數組,桶的下標即爲需排序數組的值,桶的值爲序排序數同一組值出現的次數
        int[] arr = new int[max-min+1];
        //往桶裏分配元素
        for(int i=0;i<n;i++){
            arr[A[i]-min]++;
        }

        //從桶中取出元素
        int index=0;
        for(int i=0;i<arr.length;i++){
            while(arr[i]-->0){
                A[index++]=i+min;
            }
        }

        return A;
    }
}

(十).基數排序算法(基於桶排序)

【原理】基數排序(Radix sort)是一種非比較型整數排序算法,其原理是將整數按位數切割成不一樣的數字,而後按每一個位數分別比較。因爲整數也能夠表達字符串(好比名字或日期)和特定格式的浮點數,因此基數排序也不是隻能使用於整數。

將全部待比較數值(正整數)統一爲一樣的數位長度,數位較短的數前面補零。而後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成之後,數列就變成一個有序序列。

【題目】對於一個int數組,請編寫一個基數排序算法,對數組元素排序。
給定一個int數組A及數組的大小n,請返回排序後的數組。保證元素均小於等於2000。

import java.util.*;
import java.lang.Math;
public class RadixSort {
    public int[] radixSort(int[] A, int n) {
        //基於桶排序的基數排序

        //肯定排序的趟數,即排序數組中最大值爲809時,趟數爲3
         int max=A[0];
        for(int i=0;i<n;i++){
            if(A[i]>max){
                max= A[i];
            }
        }
        //算出max的位數
        int time=0;
        while(max>0){
            max/=10;
            time++;
        }
        //【桶】初始化十個鏈表做爲桶,用戶分配時暫存
        ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>();
        for(int i=0;i<10;i++){
            ArrayList<Integer> Item = new ArrayList<Integer>();
            list.add(Item);
        }

        //進行time次分配和收集
        for(int i=0;i<time;i++){

            //分配元素,按照次序優先,從個位數開始
            for(int j=0;j<n;j++){
                int index = A[j]%(int)Math.pow(10,i+1)/(int)Math.pow(10,i);
                list.get(index).add(A[j]);
            }
            //收集元素,一個一個桶地收集
            int count=0;
            //10個桶
            for(int k=0;k<10;k++){
                //每一個桶收集
                if(list.get(k).size()>0){

                    for(int a: list.get(k)){
                        A[count]=a;
                        count++;   
                    }
                  //清除數據,以便下次收集
                    list.get(k).clear();
                }
            }
        }
        return A; 
    }
}
  • 【基數排序效率】
  • 基數排序的時間複雜度是O(k·n),其中n是排序元素個數,k是數字位數。注意這不是說這個時間複雜度必定優於O(n·log(n)),k的大小取決於數字位的選擇和待排序數據所屬數據類型的全集的大小;k決定了進行多少輪處理,而n是每輪處理的操做數目。

  • 基數排序基本操做的代價較小,k通常不大於logn,因此基數排序通常要快過基於比較的排序,好比快速排序。

  • 最差空間複雜度是O(k·n)