排序算法總結——Java版

轉載請注明出處
作者:@ZJXin
說明:本文所寫的排序,默認按從小到大排序。由于本人水平有限,如有不正確的地方歡迎留言指出,謝謝!

排序算法是面試中經常會被問到的問題,甚至會要求手寫算法,本文對一些常用的排序算法做了總結。本文涉及的代碼全部都運行驗證過。(堆排序沒有給出代碼實現)

排序算法分類

1. 直接插入排序

算法思想

每次將無序區的第一個記錄按關鍵字插入到有序區的合適位置。

算法實現步驟

  • 取出無序區第一個元素,并將該值賦值給一個臨時變量
  • 在已經排序的元素序列中從后向前掃描
  • 如果所取元素大于新元素,將該元素向后移動一個位置
  • 重復步驟三,直到找到已排序的元素小于或者等于新元素的位置
  • 將新元素插入到該位置中
  • 重復1~5

算法流程圖

直接插入排序

Java代碼實現

/**
 * 直接插入排序算法
 * 
 * @param nums 待排序數組
 */
public void insertSort(int []nums){

    //數組長度
    int length = nums.length;
    //要插入的數
    int insertNum;

    for(int i=1;i<length;i++){//遍歷數組

        int j = i-1;//已排好序的元素序列的最大下標
        insertNum = nums[i];

        while(j>=0 && nums[j]>insertNum){
            //首先判斷j是否>=0,不然將可能產生數組溢出
            //序列從后往前循環
            //將大于insertNum的數往后挪一格
            nums[j+1] = nums[j--];
        }

        //將需要插入的數放在插入的位置
        nums[j+1] = insertNum;
    }
}

算法分析

  • 時間復雜度
    • 最佳情況:O(n)
    • 最壞情況:O(n^2)
    • 平均時間復雜度:O(n^2)
  • 空間復雜度:O(1)
  • 穩定性:穩定
  • 改進:希爾排序(縮小增量排序)

2. 希爾排序(縮小增量排序)

算法思想

希爾排序是對直接插入排序的一種改進。希爾排序將整個待排序序列按增量dk劃分為m個子序列,不斷縮小增量dk,重復這一過程,直到dk減少到1,對整個序列進行一次直接插入排序,因此,希爾排序也被稱為縮小增量排序

希爾排序與直接插入排序的不同之處在于,直接插入排序每次對相鄰記錄進行比較,記錄只移動一個位置,而希爾排序每次對相隔較遠(即增量)的記錄進行比較,使得記錄移動時跨越多個記錄,實現宏觀上的調整。當增量為1時,此時序列已基本有序,可將前邊的各趟的調整看做最后一趟的預處理。

算法實現步驟

  • 選擇一個增量序列t1、t2、t3...tk(其中tk=1)
  • 按增量序列個數k,對待排序序列進行k趟排序
  • 每趟排序,根據相應的增量dk,進行插入排序

算法流程圖

希爾排序

Java代碼實現

/**
 * 希爾排序(縮小增量排序)
 * 不穩定
 * 
 * @param nums 待排序數組
 */
public void sheelSort(int []nums){
    int dk = nums.length;
    do{
        //縮小增量
        //此處縮小增量可以自己設置,一般縮小當前的一半
        dk = dk/2;
        sheelInsert(nums, dk);//進行一次希爾排序
    }while(dk>0);//當增量為1時停止
}

/**
 * 希爾排序一趟排序
 * 如果把增量設置為1,則是簡單的插入排序
 *
 * @param nums 待排序數組
 * @param dk 增量
 */
public void sheelInsert(int []nums,int dk){
    int length = nums.length;
    int insertNum;//待插入的值

    for(int i=dk;i<length;i++){

        int j=i-dk;
        insertNum = nums[i];

        while(j>=0 && nums[j]>insertNum){
            //同樣的,應該先檢測j是否比0小,否則可能產生數組溢出
            //向后移動dk位
            nums[j+dk] = nums[j];
            j = j-dk;
        }
        nums[j+dk] = insertNum;//把待插入的 值放到正確的位置
    }
}

算法分析

  • 時間復雜度
    • 最佳情況:O(n)
    • 最壞情況:O(n^2)
    • 平均情況:O(nlogn)
  • 空間復雜度:O(1)
  • 穩定性:不穩定

3. 簡單選擇排序

算法思想

每趟在未排序的序列中找到最小的元素,存放到未排序序列的起始位置。

算法實現步驟

  • 首先在未排序序列中找到最小元素,存放到排序序列的起始位置
  • 再從剩余未排序元素中繼續尋找最小元素,然后放到已排序序列的末尾。
  • 重復上述步驟,直到所有元素均排序完畢。

算法流程圖

簡單選擇排序

Java代碼實現

/**
 * 簡單選擇排序
 * 每一個循環后再交換,簡單選擇排序
 * 
 * @param nums 待排序數組
 */
public void selsectSort(int []nums){

    int length = nums.length;//數組長度,將這個值提出來,提高速度

    for(int i=0; i<length; i++){//循環次數

        int key = nums[i];
        int position = i;

        for(int j=i+1;j<length;j++){//選出最小的值和位置

            if(key>nums[j]){
                key = nums[j];
                position = j;
            }
        }
        //交換位置
        nums[position] = nums[i];
        nums[i] = key;
    }
}

算法分析

  • 時間復雜度
    • 最壞、最佳、平均:O(n^2)
  • 空間復雜度:O(1)
  • 穩定性:不穩定

4. 堆排序

算法思想

堆排序是對簡單排序的優化,利用大頂堆所有非葉子結點均不小于其左右孩子結點這一特性來排序。

算法實現步驟

  • 將待排序列建成一個大頂堆
  • 將頂堆結點與隊尾結點交換位置,堆長度減1
  • 調整剩余結點為堆
  • 重復步驟2~3

算法流程圖

堆排序

算法分析

  • 時間復雜度
    • 最壞、最好、平均:O(nlogn)
  • 空間復雜度:O(1)
  • 穩定性:不穩定

5. 冒泡排序

算法思想

冒泡排序每趟排序,對相鄰兩個元素進行比較,如果順序錯誤則交換過來,每趟排序后,都將把未排序序列的最大值放在最后邊。每趟排序,越小的元素會經由交換,慢慢地"浮"到數列頂端,這也是該算法名字的由來。

算法實現步驟

  • 將序列中所有元素相鄰兩兩比較,將最大的放在最后面。
  • 將剩余序列中所有元素相鄰兩兩比較,將最大的放在最后面。
  • 重復第二步,直到只剩下一個數。

算法流程圖

冒泡排序

Java代碼實現

/**
 * 冒泡排序
 * 
 * @param nums 待排序數組
 */
public void bubbleSort(int []nums){

    int length = nums.length;
    int temp;

    for(int i=0; i<length; i++){

        for(int j=0; j<length-i-1; j++){
            if(nums[j]>nums[j+1]){
                //交換相鄰兩個數
                temp = nums[j];
                nums[j] = nums[j+1];
                nums[j+1] = temp;
            }
        }
    }
}

算法分析

  • 時間復雜度:
    最佳、最壞、平均:O(n^2)
  • 空間復雜度:O(1)
  • 穩定性:穩定

算法改進

  • 改進方案一
    設置一標志性變量pos,用于記錄每趟排序中最后一次進行交換的位置。由于pos位置之后的記錄均已交換到位,故在進行下一趟排序時只要掃描到pos位置即可。
/**
 * 冒泡排序改進版本一
 * 通過設置標志位來減少遍歷次數
 * 
 * @param nums
 */
public void betterBubbleSort1(int []nums){

    int length = nums.length;
    int i= length -1;  //初始時,最后位置保持不變  
    while ( i> 0) {   
       int pos= 0; //每趟開始時,無記錄交換  
       for (int j = 0; j< i; j++){  
           if (nums[j]> nums[j+1]) {  
               pos= j; //記錄交換的位置   
               int tmp = nums[j]; 
               nums[j]=nums[j+1];
               nums[j+1]=tmp;  
           }   
       i= pos; //為下一趟排序作準備  
       }
    }   
}
  • 改進方案二
    若某一趟排序中未進行一次交換,則排序結束 。
 /**
   * 冒泡排序改進版本二
   * 
   * @param nums 待排序數組
   */
  public void betterBubbleSort2(int[] nums ) {

       int len = nums .length;
       boolean flag = true;
       while (flag) {
           flag = false;
           for (int i = 0; i < len - 1; i++) {
               if (nums [i] > nums [i + 1]) {
                   int temp = nums [i + 1];
                   nums [i + 1] = nums [i];
                   nums [i] = temp;
                   flag = true;
               }
           }
           len--;
       }
 }

6. 快速排序

算法思想

分治法。通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,然后在分別對這兩部分記錄進行排序。

算法實現步驟

  • 從數列中挑出一個元素,稱為“基準”
  • 重新排序數列,所有比基準小的元素放在基準前邊,所有比基準大的放在基準后邊,該基準最后處于數列中間位置。
  • 遞歸地把小于基準值元素和大于基準值元素的子序列快速排序

算法流程圖

快速排序

Java代碼實現

/**
 * 快速排序
 * 不穩定
 * 
 * @param nums 待排序數組
 * @param left 開始位置
 * @param right 結束位置
 */
public void quickSort(int []nums,int left,int right){

    if(left<right){
        int base = nums[left];//選定的基準
        int low = left;
        int high = right;

        while(low<high){

            while(low<high && nums[high]>base){
                //先從后往前遍歷,直到數值比基準小
                high--;
            }
            //把數值比基準小的放到基準左邊
            nums[low] = nums[high];

            while(low<high && nums[low]<base){
                //從前往后遍歷,直到數值比基準大
                low++;
            }
            //把數值比基準大的放到基準右邊
            nums[high] = nums[low];
        }
        //把基準放到準確位置去
        nums[low] = base;

        //用同樣的方法,遞歸調用基準左邊的數組以及右邊的數組
        quickSort(nums, left, low-1);
        quickSort(nums, low+1, right);
    }
}

算法分析

  • 時間復雜度
    • 最佳情況:O(nlogn)
    • 最壞情況:O(n^2)
    • 平均復雜度:O(nlogn)
  • 空間復雜度:O(nlogn)
  • 穩定性:不穩定

7.歸并排序

算法思想

分治法。歸并排序將待排序列一分為二,并對每個子數組遞歸排序,然后再把這兩個排好序的子數組合并為一個有序的數組。

算法實現步驟

  • 把長度為n的輸入序列分為兩個長度為n/2的子序列
  • 對這兩個子序列分別采用歸并排序
  • 將兩個排序好的子序列合并成一個最終的排序序列

算法流程圖

歸并排序

Java代碼實現

/**
 * 歸并排序
 * 穩定的排序算法
 * 
 * @param nums 待排序數組
 * @param low 待排序數組的起點
 * @param high 待排序數組的終點
 */
public static void mergeSort(int []nums,int low,int high){
    //算出分割點,2-路歸并即數組的中點
    int mid = (high+low)/2;

    if(low<high){
        //遞歸分割
        mergeSort(nums, low, mid);
        mergeSort(nums, mid+1, high);

        //調用歸并方法,將分割的進行歸并
        merge(nums, low, mid, high);
    }   
}
/**
 * 一次2-路歸并
 * 可以想象成將兩個鏈表,按照升序的方法組合成一條鏈表
 * 
 * @param nums 待排序數組
 * @param low 歸并的最低位
 * @param mid 歸并的中間位置
 * @param high 歸并的最高位
 */
public static void merge(int []nums,int low,int mid,int high){

    //新建數組,放置臨時數據
    int []temp = new int[high-low+1];
    int i = low,j=mid+1;
    int k=0;

    //把較小的數先移到新數組中去
    while(i<=mid && j<=high){
        //遍歷兩路數組,直到一路結束為止
        if(nums[i]<nums[j]){
            temp[k++] = nums[i++]; 
        }else{
            temp[k++] = nums[j++];
        }
    }

    //把左邊那路剩余的數放入數組
    while(i<=mid){
        temp[k++] = nums[i++];
    }
    //把右邊那路剩余的數放入數組
    while(j<=high){
        temp[k++] = nums[j++];
    }

    //把新數組的值覆蓋nums數組
    //新數組的長度有可能比nums數組短
    for(int t=0;t<temp.length;t++){
        nums[t+low] = temp[t];
    }
}

算法分析

  • 時間復雜度
    • 最佳、最壞、平均:O(nlogn)
  • 空間復雜度:O(n)
  • 穩定定:穩定
  • 優劣
    • 優點:穩定性,性能不受輸入數據的影響
    • 缺點:需要線性的額外空間

8. 基數排序

算法思想

先將所有關鍵字統一為相同位數,位數較少的數前邊補0.然后從最低位開始依次向高位進行排序,直到按最高位排序完成,關鍵字序列就成為有序序列。基數排序基于分別排序,分別收集,所以是穩定的。適用于很長的數的排序。

算法實現步驟

  • 確定排序趟次,即確定最大的數的位數
  • 從最低位按照分配和收集進行排序,直至最高位。
  • 在每一趟,分配按照相應關鍵字的曲子將記錄加入到r個不同的隊列,收集是從小到大以此將r個隊列收尾相接成數組。

算法流程圖

基數排序

從圖片可以看出,最大的數只有3位,進行3趟排序。先從個位進行排序,第二趟對十位數進行排序,最后對百位數進行排序。

Java代碼實現

/**
 * 基數排序
 * 
 * @param nums
 */
public static void radixSort(int []nums){

    //確定排序趟次
    int time = sortTime(nums);

    //建立10個隊列   
    List<ArrayList> queue = new ArrayList<ArrayList>();
    for (int i = 0; i < 10; i++) {
        ArrayList<Integer> queue1 = new ArrayList<Integer>();
        queue.add(queue1);
    }

    //進行time次分配和收集     
    for (int i = 0; i < time; i++) {
        //分配數組元素;     
        for (int j = 0; j < nums.length; j++) {
            //得到數字的第time+1位數   
            //先取余,再做除法
            int x = nums[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
            ArrayList<Integer> queue2 = queue.get(x);
            queue2.add(nums[j]);
            queue.set(x, queue2);
        }
        //元素計數器     
        int count = 0;
        //收集隊列元素
        for (int k = 0; k < 10; k++) {
            while (queue.get(k).size() > 0) {
                //如果該隊列有分配到元素,對其進行收集
                ArrayList<Integer> queue3 = queue.get(k);
                nums[count] = queue3.get(0);
                queue3.remove(0);
                count++;
            }
        }

        //查看第N趟排序后的結果
        /*
        System.out.print("\n"+"第"+i+"趟排序結果:");
        for(int m=0;m<nums.length;m++){
            System.out.print(nums[m]+" ");
        }
        */
    }
}

/**
 * 計算基數排序的遍歷趟次
 * 
 * @param nums 待排序序列
 * @return 返回一個int類型,表示需要遍歷的趟次
 */
public static int sortTime(int []nums){
    //首先確定排序的趟數
    //通過待排序列的最大數來確定趟數time
    int max = nums[0];
    for (int i = 1; i < nums.length; i++) {
        if (nums[i] > max) {
            max = nums[i];
        }
    }
    int time = 0;
    //判斷位數;     
    while (max > 0) {
        max /= 10;
        time++;
    }
    return time;        
}

算法分析

  • 時間復雜度
    • 最佳、最壞、平均:O(mn)
  • 空間復雜度:O(n)
  • 穩定性:穩定

總結

算法 平均復雜度 最佳時間復雜度 最壞時間復雜度 空間復雜度 穩定性
直接插入排序 O(n2) O(n) O(n^2) O(1) 穩定
希爾排序 O(nlogn) O(n) O(n^2) O(1) 不穩定
選擇排序 O(n^2) O(n^2) O(n^2) O(1) 不穩定
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不穩定
冒泡排序 O(n^2) O(n^2) O(n^2) O(1) 穩定
快速排序 O(nlogn) O(nlogn) n(n^2) O(nlogn) 不穩定
歸并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 穩定
基數排序 O(nm) O(nm) O(nm) O(n) 穩定
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容

  • 簡介:本文主要總結了以下幾個排序算法:冒泡排序、選擇排序、插入排序、希爾排序、歸并排序、快速排序、堆排序、計數排序...
    AlexanderJLiu閱讀 944評論 2 29
  • 概述:排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,737評論 0 15
  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    蟻前閱讀 5,207評論 0 52
  • 概述排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部的...
    Luc_閱讀 2,285評論 0 35
  • 無論在什么場合,高情商的人在,總能說出適宜的話,他擁有理智而感性的處事之道,就像雪夜里的一團火,不僅能照亮道路,還...
    主持人梓惟閱讀 120評論 0 0