排序算法

排序算法

1.什么叫排序?

     排序前:3,1,6,9,8,9
     排序后:1,3,5,6,8,9

排序無處不在

汽車銷量排行.png
游戲裝備排行.png

十大排序算法:

十大排序算法.png

2 算法介紹

什么是排序算法的穩定性?

回答:如果相等的2個元素,在排序前后的相對位置保持不變,那么這是穩定的排序算法
舉例:
  • 排序前:5,1,3(a), 4,7,3(b)

  • 穩定的排序: 1,3(a),3(b),4,5,7

  • 不穩定的排序: 1,3(b),3(a),4,5,7

在對自定義對象進行排序時,穩定性會影響最終的排序效果

什么是原地算法(In-place Algorithm)?

回答:不依賴額外的資源或者依賴少數的額外資源,僅依靠輸出覆蓋輸入,空間復雜度為0(1)的可以認為是原地算法

ps.(代碼實現用到的方法定義,本文統一以升序為例子)
cmp(index0,index1)
cmp方法傳入二個數組索引比較兩個索引所指元素的大小
如果index0索引的元素大于index1索引的元素則返回 一個大于0 的整數
反之則返回小于0的整數 ,相等返回0。

swp(index0,index1)
swp方法傳入二個數組索引交換索引所指元素的位置

2.1冒泡排序

  • 冒泡排序也叫起泡排序

  • 執行流程

    ① 從頭開始比較相鄰每一對元素,如果第1個比第2個大,就交換它們的位置,執行完一輪后,最末尾那個元素就是最大的元素

    ② 忽略①中曾經找到的最大元素,重復執行步驟1,知道全部元素有序

代碼實現:
for (int end = array.length - 1; end > 0 ; end --){
       for (int begin = 1; begin <= end ; begin ++){
              if (cmp(begin, begin - 1) < 0) {
                   swap(begin, begin - 1)
              }
        }
} 
冒泡排序優化①

在冒泡排序的過程中會出現這么一種情況在某一輪的排序過程中根本就沒有調用swap方法說明此時的數組已經是完全有序了但是按照之前的做法我們的程序還是要執行下去直到所有輪次結束,這樣后面的代碼都是在做無用功大大的浪費了性能,所以我們可以在檢測到數組已經提前有序的情況下及時的結束方法提高效率。

代碼實現:
for (int end = array.length - 1; end > 0 ; end --){
       bool sorted = true;
       for (int begin = 1; begin <= end ; begin ++){
              if (cmp(begin, begin - 1) < 0) {
                   swap(begin, begin - 1)
                   sorted = false;
              }
        }
        if (sorted) break
} 
  • 從代碼中可以看出 如果swap 在一輪排序中始終沒有調用,那么sorted變量則為true
    發現sorted為true時,我們直接結束循環。

  • 在實際應用中很少會出現①中提前有序的情況,所以第一種優化的能用到的情況不會太大。

冒泡排序優化②

如果序列尾部已經局部有序,我們可以記錄最后一次交換的位置,減少比較次數

什么意思?
舉個例子:有無序列表 a = [2,1,8,9,7,10,11,12,13,14]

  • 通過觀察可以發現a的尾部從10元素開始后面都是有序的,如果通過我們之前的代碼排序在交換了9 和 7 的位置之后 我們還是會去判斷10之后的元素的大小關系但是它們已經是有序的了。所以后面我們幾輪的排序都會做一些無用的判斷。這個時候我們可以記錄下我們最后一次交換元素的位置 7 的位置 此時 a = [1,2,8,7,9,10,11,12,13,14]

  • 當我們下一輪排序時到7 的位置我們就直接結束排序因為后面的元素已經是有序的了。這樣可以優化我們判斷次數。

代碼實現:
for (int end = array.length - 1; end > 0 ; end --){
       int sortedIndex = 1;
       for (int begin = 1; begin <= end ; begin ++){
              if (cmp(begin, begin - 1) < 0) {
                   swap(begin, begin - 1)
                   sortedIndex = begin;
              }
        }
        end = sortedIndex
} 
  • 設置 sortedIndex 的初始值為1可以完美的兼容整個序列提前有序的情況
  • sortedIndex 的值會不斷被begin的值覆蓋 單一輪排序完成時 sortedIndex的值必然是最后一次交換元素的位置

2.2 選擇排序

  • 執行流程

    ① 遍歷一邊序列找出最大的那個元素,然后與末尾的元素交換位置

    ② 忽略①中找到的最大元素,重復執行步驟 ①

    代碼實現:
     for (int end = array.length - 1; end > 0 ; end --){
         int maxIndex = 0;
         for (int begin = 1; begin <= end ; begin ++){
                if (cmp(max, begin ) <= 0) {
                     max = begin;
                }
          }
          swap(max,end);
     } 
    
  • 選擇排序的交換次數遠遠少于冒泡排序,平均性能優于冒泡排序

  • 最好,最壞,平均時間復雜度:O(n2)

2.3 插入排序

  • 執行流程
    ① 在執行過程中,插入排序會將序列分為2部分,頭部是已經排好序的,尾部是待排序的
    ② 從頭開始掃描每一個元素,每當掃描到一個元素,就將它插入到頭部合適的位置,使得頭部的數據依次保持有序
代碼實現:
for (int begin = 1; begin < array.length; begin++) {
     int cur = begin;
    while (cur > 0 && amp(cur, cur -1) < 0) {
             swap(cur, cur - 1);
             cur--
    }
}
  • 插入排序的時間復雜度與逆序對的數量成正比關系

  • 逆序對的數量越多,插入排序的時間復雜度越高
    (ps.什么是逆序對? 舉個例子:數組<2,3,8,6,1>的逆序對為: <2,1><3,1><8,1><8,6><6,1>,一共5個逆序對)

  • 最壞,平均時間復雜度::O(n2)

  • 最好時間復雜度: O(n)

  • 空間復雜度:O(1)

  • 屬于穩定排序

插入排序優化1
  • 思路將【交換】轉為【挪移】
    ① 先將待插入的元素備份
    ②頭部有序數據中比待插入元素大的,都朝尾部方向挪動一個位置
    ③將待插入元素放入最終合適的位置
代碼實現:
for (int begin = 1 ; begin < array.length; begin++){
    int cur = begin;
    T value = array[cur];//備份元素
    while (cur > 0 && cmp(v, array[cur - 1]) < 0) {
              array[cur] = array[cur - 1];
              cur --;
    }
    array[cur] = v;
}
插入排序優化2
  • 思路在優化1的基礎上 優化確定位置的過程優化1是一個一個比較來確定,所以可以通過二分法直接求出要插入的位置。從而減少比較次數
代碼實現:
for (int i = 1; i < array.length; i ++ ){
      //找出插入位置
      int index = -1;
      int begin = 0;
      int end = I;
      while (begin < end) {
           int mid = (begin + end) >> 1;
           if (cmp(i ,mid) < 0 ){
               end = mid; 
           }else {
               begin = mid + 1;
           }
      }
      index = begin;
      //備份元素
      T value = array[I];
       for (int j = i; j > index; j --) {
             array[j] = array[j - 1];
      }
      array[index] = value;
}

2.4 歸并排序

  • 執行流程

    ① 不斷地將當前序列平均分割成2個子序列,直到不能分割(序列中只剩一個元素)

    ② 不斷地將2個子序列合并成一個有序序列直到最終只剩下1個有序序列

代碼實現

// 準備一段臨時的數組空間,在合并操作中使用
leftArray = (T[ ]) new Object[array.lengtn >> 1];
sort(0,array.length);

private void sort(int begin, int end) {
     // 至少需要有2個元素
    if (end - begin < 2) return;
    int mid = (begin + end) >> 1;
    sort(begin,mid);
    sort(mid,end);
    merge(begin,mid,end);
}

private void merge(int begin, int mid ,int end) {
   int li = 0;
   int le = mid - begin; // 左邊數組(基于leftArray)
   int ri = mid;
   int re = end;//右邊數組(基于array)
   int ai = begin;//array 的索引
   for (int I = li; i < le; i ++){//拷貝左邊數組到leftArray
         leftArray[I] = array[begin + I];
    }
    while (li < le) {
           if (ri < re && amp(array[ri]),leftArray[li] < 0) {
                array[ai ++] = array[ri ++];// 拷貝右邊數組到array
           } else {
                array[ai ++] = leftArray[li ++];//拷貝左邊數組到array
           }
    }//cmp位置改為 <= 會失去穩定性
}
歸并排序 - 復雜度分析
  • 歸并排序花費的時間

T(n) = 2 * T(n/2) + 0(n)
T(1) = O(1)
T(n)/n = T(n/2)/(n/2) + O(1)

令S(n) = T(n)/n
S(1) = O(1)
S(n) = S(n/2) + O(1) = S(n/4) + 0(2) = S(n/8) + 0(3) = S(n/2k) + O(k) = S(1) + O(logn) = O(logn)
T(n) = n* S(n) = 0(nlogn)

  • 由于歸并排序總是平均分割子序列,所以最好,最壞,平均時間復雜度都是O(nlogn)
常見的遞推式與復雜度
常見的遞推式與復雜度

2.5 快速排序

  • 執行流程

① 從序列中選擇一個軸點元素,一般每次選擇0位置的元素為軸點元素

② 利用軸點將序列分割成2個子序列,將小于軸點元素的元素放在軸點前面(左側)
將大于軸點元素的元素放在軸點的后面(右側)

③ 對子序列進行①②操作知道不能再分割(子序列中只剩下1個元素)

代碼實現

private void sort(int begin, int end) {
     //至少要用2個元素
     if (end - begin < 2) return;
    int middle = privotIndex(begin,end);
    sort(begin,middle);
    sort(middle + 1, end);
}

private int privotIndex(int begin,  int end) {
      
    T privot = array[begin];
    end --;//end指向最后一個元素
    while (begin < end) {
        
          while (begin < end) {
               if (cmp(privot, array[end]) < 0) {
                       end --;
                } else {
                      array[begin ++] = array[end];
                      break;
                 }
          }
           while (begin < end) {
               if (cmp(privot, array[begin]) > 0) {
                      begin ++;
                } else {
                      array[end --] = array[begin];
                      break;
                 }
          }
    } 

    array[begin] = pivot;
    return begin;
}

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

推薦閱讀更多精彩內容

  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    蟻前閱讀 5,211評論 0 52
  • 概述 因為健忘,加上對各種排序算法理解不深刻,過段時間面對排序就蒙了。所以決定對我們常見的這幾種排序算法進行統一總...
    清風之心閱讀 711評論 0 1
  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    zwb_jianshu閱讀 1,187評論 0 0
  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    printf200閱讀 779評論 0 0
  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    閑云清煙閱讀 763評論 0 6