日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

最經(jīng)典最常用的排序算法有:冒泡排序、插入排序、選擇排序、歸并排序、快速排序、計數(shù)排序、基數(shù)排序和桶排序。這些排序算法可以按照時間復雜度分為三類:

  • O(n^2)——冒泡、插入、選擇
  • O(nlogn)——快速、歸并
  • O(n)——計數(shù)、基數(shù)、桶

一、衡量排序算法的指標

1.1 算法的執(zhí)行效率

  • 最好、最壞和平均情況下的時間復雜度;
  • 原始數(shù)據(jù)的雜亂程度會導致同一種算法在不同情況下的時間復雜度呈現(xiàn)不同量級的差異,所以在選擇排序算法的時候,需要給出這三個指標,結(jié)合實際業(yè)務情況的原始數(shù)據(jù),來選擇算法。
  • 時間復雜度的系數(shù)、常數(shù)和低階;
  • 不同的排序算法可能會出現(xiàn)算法復雜度處在同一量級的情況,此時就需要根據(jù)時間復雜度的系數(shù)、常數(shù)和低階來決定使用哪一種算法。
  • 算法的比較次數(shù)和移動次數(shù);
  • 比較次數(shù)和移動次數(shù)會分別導致CPU和IO操作,所以也需要納入到選擇排序算法的指標中來。

1.2 算法的內(nèi)存消耗

  • 原地排序,是指空間復雜度為O(1)的排序算法,不需要借助很多外部存儲空間就能完成排序;
  • 非原地排序,需要借助外部存儲空間才能完成排序;

1.3 算法的排序穩(wěn)定性

  • 穩(wěn)定,原始數(shù)據(jù)中相等的元素在排序后它們之間的順序不發(fā)生改變;
  • 非穩(wěn)定,原始數(shù)據(jù)中相等的元素在排序后它們之間的順序有可能發(fā)生改變;

二、O(n^2)的排序算法

2.1 冒泡排序

冒泡排序每次只會比較相鄰的兩個元素,只有在不滿足要求的大小關(guān)系情況下,才會發(fā)生交換行為。一次冒泡迭代會讓至少一個元素移動到它最終應該在的位置上,最多n次冒泡迭代,當某次冒泡迭代沒有發(fā)生元素的交換行為,就可以判定數(shù)列已經(jīng)完成了排序。

比如我們需要對如下數(shù)列進行從小到大的排列:

 

冒泡排序過程示意圖

對應的實現(xiàn)代碼如下:

    public static void main(String[] args) {
        int[] data = {4,5,6,3,2,1};
        int size = 6;
        log.info("原始數(shù)列為:{}",data);
        bubbleSort(data,size);
        log.info("冒泡排序后的結(jié)果為:{}",data);
    }

    public static void bubbleSort(int[] data, int n){
        if(n <= 1){
            log.info("數(shù)組中元素少于2個,無需進行排序!");
            return;
        }

        // 最多需要迭代n次冒泡
        for(int i=0;i<n;i++){
            // 表示當前迭代冒泡中是否發(fā)生了元素交換
            boolean switchFlag = false;
            for(int j=0;j<n-i-1;j++){
                if(data[j] > data[j+1]){
                    // 不滿足從小到大的關(guān)系,需要交換
                    int temp = data[j];
                    data[j] = data[j+1];
                    data[j+1] = temp;
                    switchFlag = true;
                }
            }

            // 當前冒泡迭代中沒有發(fā)生元素交換,說明數(shù)列已經(jīng)有序,完成了排序,可以提前結(jié)束
            if(!switchFlag){
                break;
            }
        }
    }

冒泡排序沒有借助很多額外的存儲空間,因此是原地排序;

冒泡排序只有在前后兩個元素不滿足大小關(guān)系時才發(fā)生交換,因此是穩(wěn)定的排序算法;

最好情況下原始數(shù)列就是滿足要求的大小關(guān)系的,那么只需要迭代一個冒泡就完成了排序,時間復雜度為O(n);最壞情況下原始數(shù)列正好和要求的大小關(guān)系完全相反,那么需要迭代n此冒泡,因此時間復雜度為O(n^2)。

2.2 插入排序

插入排序?qū)⒄麄€數(shù)列劃分為已排序和未排序兩個區(qū)間,初始情況下,已排序區(qū)間只有一個元素,那就是數(shù)列的第一個元素,然后重復取未排序區(qū)間中的元素逐個插入到已排序區(qū)間中,直至未排序區(qū)間元素數(shù)量為0,需要注意的是,將元素插入已排序區(qū)間會引起插入位置后方所有元素的移動。

比如,我們對如下數(shù)列進行插入排序的過程為:

 

插入排序過程示意圖

對應的實現(xiàn)代碼如下:

public static void main(String[] args) {
        int[] data = {4,5,6,3,2,1};
        int size = 6;
        log.info("原始數(shù)列為:{}",data);
        insertionSort(data,size);
        log.info("插入排序后的結(jié)果為:{}",data);
    }

    public static void insertionSort(int data[], int n){
        if(n <= 1){
            log.info("數(shù)組中元素少于2個,無需進行排序!");
            return;
        }

        // 從未排序區(qū)間開始遍歷
        for(int i=1;i<n;i++){
            // 放到臨時空間保存,因為后續(xù)已排序區(qū)間元素的向后移動會覆蓋掉data[i]的值
            int value = data[i];
            int j=i-1;
            for(;j>=0;j--){
                // 已排序區(qū)間從后往前遍歷和當前需要插入的元素進行比較
                if(data[j] > value){
                    // 當前已排序區(qū)間的元素往后移動一位
                    data[j+1] = data[j];
                } else {
                    break;
                }
            }
            // 插入到已排序區(qū)間中小于等于自己元素的后面,保證排序算法的穩(wěn)定性
            data[j+1] = value;
        }
    }

插入排序沒有借助很多額外的存儲空間,因此是原地排序;

插入排序可以在算法中約定,將未排序區(qū)間的元素插入到排序區(qū)間相同元素的后方,因此也是穩(wěn)定的排序算法;

最好情況下原始數(shù)列就是滿足要求的大小關(guān)系的,那么只需要迭代一遍數(shù)列就完成了排序,時間復雜度為O(n);最壞情況下,每將一個元素插入已排序區(qū)間,都會引起所有已排序區(qū)間元素的向后移動,那么時間復雜度就是O(n^2)。

2.3 選擇排序

選擇排序也是將整個數(shù)列分為已排序區(qū)間和未排序區(qū)間,和插入排序的區(qū)別是,每次迭代都是從未排序區(qū)間中尋找到最小值,然后將其和未排序區(qū)間中的第一個元素進行交換,直至未排序區(qū)間元素為空。

 

選擇排序過程示意圖

選擇排序也是原地排序;

選擇排序是不穩(wěn)定的,在交換元素的時候,極有可能改變相同元素的前后順序,比如:

1,2,| 5,4,5,3

此時未排序區(qū)間最小元素為3,會和未排序區(qū)間第一個元素5進行交換:

1,2,3,| 4,5,5

此時,未排序區(qū)間的兩個5實際上已經(jīng)發(fā)生了順序上的變化,在排序完成后,它們也極有可能是維持現(xiàn)在變化了的順序的。

最好情況和最壞情況下的時間復雜度都是O(n2),無論最好還是最壞,每次迭代都要從未排序區(qū)間中找到最小值,每次找最小值的時間復雜度為O(n),所以無論哪種情況,選擇排序的時間復雜度都是O(n2)。

2.4 小結(jié)

冒泡排序和插入排序雖然時間復雜度在同一量級,并且都是穩(wěn)定的、原地排序算法,但是通常來說,插入排序的性能要優(yōu)于冒泡排序。因為插入排序的交換次數(shù)和賦值次數(shù)更少,在大數(shù)據(jù)量數(shù)列排序時,效果尤為明顯。

插入排序還存在優(yōu)化的空間,在大數(shù)據(jù)量和比較無序的情況下使用希爾排序能很好地提升排序性能,希爾排序的核心思想是對數(shù)列按照增量序列進行邏輯分組,對分組的數(shù)列使用插入排序,保證基本有序,然后逐步減小增量序列,迭代進行插入排序。

希爾排序的時間復雜度視增量序列值的不同而不同,最壞情況是O(n^2),并且不是穩(wěn)定的排序算法。

總之,這三種排序算法的時間復雜度都是O(n^2),比較適合小數(shù)據(jù)量的排序場景。

三、O(nlogn)的排序算法

3.1 快速排序

快速排序是由冒泡排序進化而來,能夠迭代更少的次數(shù),更加快速地完成排序,因此得名。其使用了分治思想和遞歸的實現(xiàn)方式,其大致的思路如下:

  • 選取最前面或者最后面的一個元素作為基準元素(我們以最前面一個元素作為基準元素為例),并在數(shù)列的頭部left和尾部right上各設置一個指針;
  • 從right開始將元素和基準元素比較,如果大于等于基準元素,則right-1,如果小于基準元素,那么將right的值賦值到left所在的位置,然后切換到left,left+=1,將此時的left元素和基準元素比較,如果小于等于基準元素,則left+1,如果大于基準元素,那么將left的值賦值到right所在的位置;
  • 對如上過程遞歸迭代,直至left=right,然后將它們指向的元素賦值為基準元素,此時就完成了排序;

排序過程示意圖可以參考:什么是快速排序

快速排序在排序的過程中并不會借助很多的額外存儲空間,因此屬于原地排序;

快速排序可能會改變相同元素的順序,因此是不穩(wěn)定的排序算法;

快速排序的時間復雜度是O(nlogn),前提是每次遞歸選取的基準元素正好可以將數(shù)列分成兩個差不多的子數(shù)列,在最壞情況下,每次選取的基準元素不是最大值就是最小值的話,那么時間復雜度就會退化為O(n^2)。

3.2 歸并排序

歸并排序也是使用了分治思想和遞歸的實現(xiàn)方式,其大致的思路如下:

  • 找到待排序數(shù)列的中間位置,將數(shù)列分解成前后兩個子數(shù)列;
  • 使用同樣的方法迭代分解子數(shù)列,直至所有子數(shù)列都只有一個元素為止;
  • 使用額外的存儲空間從下往上,開始合并相鄰子數(shù)列,直至合并為一個數(shù)列為止,即完成了排序;

分解和合并的大致示意圖如下:

 

歸并排序的分解合并過程

需要注意的是,在合并的過程中,算法需要借助額外的存儲空間來合并相鄰的兩個子數(shù)列,合并的過程如下:

 

歸并排序合并過程示意圖

遞歸的代碼示例如下:

public static void MergeSort(int[] data, int begin, int end){
    if(begin >= end){
        log.info("當前子數(shù)列僅有一個元素,無需再分解");
        return;
    }
    int middle = (begin + end) / 2;
    MergeSort(data,begin,middle);
    MergeSort(data,middle+1,end);
    
    Merge(data,begin,middle,end);
}

歸并排序在合并的時候需要借助很多額外的存儲空間,空間復雜度為O(n),因此它不是原地排序算法;

歸并排序在合并的時候,我們可以通過代碼控制相同的元素保持原始數(shù)列的順序,因此是穩(wěn)定的;

歸并排序在任何情況下的時間復雜度都是O(nlogn);

3.3 小結(jié)

快速排序和歸并排序都是比較復雜的排序算法,它們都是采用了分治思想和遞歸的實現(xiàn)方法,區(qū)別在于:

  • 快速排序是自上而下地進行排序,歸并排序是先分解然后再自下而上地完成排序;
  • 快速排序平均時間復雜度是O(nlogn),但是最壞情況會退化為O(n^2),雖然概率很小,但是歸并排序在任何情況下時間復雜度都是O(nlogn);
  • 快速排序是不穩(wěn)定的,屬于原地排序算法,歸并排序是穩(wěn)定的,屬于非原地排序算法;
  • 因為歸并排序的空間復雜度是O(n),所以相對來說,快速排序更加受歡迎,并被使用地最多;

四、O(n)的排序算法

4.1 桶排序

  • 首先得出待排序數(shù)列的區(qū)間范圍,按照范圍平均分為若干個有序的桶;
  • 遍歷待排序數(shù)列,將這些數(shù)據(jù)分別放到各個桶里面;
  • 單個桶里面的數(shù)據(jù)可以使用快速排序進行排序;
  • 將所有桶中的數(shù)據(jù)按照桶的次序合并起來就得到了最終有序的數(shù)列;

 

桶排序示意圖

桶排序需要借助額外的存儲空間“桶”,因此,不是原地排序算法;

桶排序是否穩(wěn)定取決于單個桶內(nèi)排序使用的算法,如果使用的快速排序,因為快排本身就不是穩(wěn)定的,因此桶排序就不是穩(wěn)定的;

桶排序的空間復雜度為O(n),因為它需要將全部元素都放到桶中;時間復雜度也是O(n),這個是由公式推到而來,假設n個元素被平均放到m個桶里面,那么每個桶中的元素個數(shù)為k=n/m,每個桶中排序的時間復雜度就是快速排序的時間復雜度,為O(klogk),因為有m個桶,因此總的時間復雜度就是O(mklogk),我們把k替換為n/m,那么時間復雜度就可以表示為O(nlog(n/m),當桶的個數(shù)m接近元素個數(shù)n時,log(n/m)就是很小的常數(shù)可以忽略,因此時間復雜度就接近O(n);

桶排序?qū)Υ判驍?shù)列的要求比較高,即待排序數(shù)列必須是容易被劃分為m個桶的,并且每個桶中的元素要能均勻,否則,有的桶中元素很多,有的很少,那么就會退化為O(nlogn)的時間復雜度;

桶排序比較適用于大數(shù)據(jù)量的外部排序場景中(非內(nèi)存排序),比如對10GB的訂單數(shù)據(jù)按照金額進行排序,內(nèi)存一次性加載不了這么大的數(shù)據(jù),可以使用桶排序劃分若干個有次序的相同大小的文件,一次只把一個文件中的數(shù)據(jù)加載到內(nèi)存進行快速排序;但是訂單金額分布不一定是均勻的,有的桶中的數(shù)據(jù)比較多,會導致該桶文件也無法被一次性加載到內(nèi)存中進行排序,此時可以對該文件迭代進行桶排序,直至所有桶文件都可以被加載到內(nèi)存中為止。

4.2 計數(shù)排序

計數(shù)排序是桶排序的一種特殊情形,當待排序數(shù)列的范圍區(qū)間不是很大的時候,我們?yōu)槊恳粋€數(shù)值劃分一個桶,桶之間要有順序,然后遍歷數(shù)列,將元素放到它對應的桶中,最后再將桶按照順序合并起來就得到了有序的數(shù)列;每個桶中的元素都是相同的,因此我們省掉了桶內(nèi)快速排序的時間;

計數(shù)排序需要借助額外的存儲空間“桶”,因此,不是原地排序算法;

計數(shù)排序再將相同的元素放到同一個桶中的時候,可以設置保持它們在原始數(shù)列中順序來保證算法的穩(wěn)定性;

計數(shù)排序的空間復雜度是O(n),時間復雜度為O(n+k),k表示數(shù)據(jù)范圍即有多少各個桶,當數(shù)據(jù)范圍不大,可以考慮為常數(shù),那么時間復雜度就是O(n);

計數(shù)排序只能適用在數(shù)列范圍區(qū)間不大的場景中,比如給50萬考生的分數(shù)進行排名,分數(shù)范圍k為0~600,數(shù)據(jù)元素m為50萬,k遠遠小于m,所以適合適用計數(shù)排序;計數(shù)排序只能給非負整數(shù)進行排序,如果待排序數(shù)列不是非負整數(shù),需要先想辦法把數(shù)列轉(zhuǎn)換為非負整數(shù)后才能使用計數(shù)排序進行排序;

4.3 基數(shù)排序

有時候待排序數(shù)列的范圍區(qū)間非常大,分布也不一定是均勻的,且數(shù)列本身位數(shù)比較多,可以考慮基數(shù)排序;基數(shù)排序是從最低位開始排序,逐步遍歷到最高位,等到最高位完成排序后,整個數(shù)列就是有序的。

 

有一個要求,對每一位的排序算法一定要使用穩(wěn)定的排序算法,不然高位的排序會弄亂低位已經(jīng)排好的順序,排序算法就不對了。

基數(shù)排序在每一位上會使用到線性排序算法,比如計數(shù)排序,需要使用額外的存儲空間,因此不是原地排序算法;

基數(shù)排序每一位的排序算法都是采用的穩(wěn)定的算法,因此整體上也是穩(wěn)定的;

基數(shù)排序的空間復雜度為O(1),時間復雜度為O(kn),當k(元素的位數(shù))不大的時候,并且每一位的排序采用的是線性排序算法(比如計數(shù)排序,這就要求每一位上的范圍區(qū)間不是很大),那時間復雜度就是O(n);如果每一位排序采用的不是線性排序算法,那么時間復雜度就很難保證是O(n)了。

基數(shù)排序的適用場景有給手機號、身份證號、銀行卡號排序,這些場景下,數(shù)列的范圍區(qū)間非常大,并且分布不一定均勻,不適合使用桶排序和計數(shù)排序,但是卻非常適合基數(shù)排序。

有的待排序數(shù)列不是等長的,比如單詞表序列的排序,可以把每一個單詞元素通過補0的方式補充到一樣長,再通過基數(shù)排序進行排序。

4.4 小結(jié)

這三種排序算法的時間復雜度是線性的,因此也被成為線性排序算法,雖然它們在時間復雜度上面很占優(yōu)勢,但是對待排序數(shù)列的要求比較高,只有待排序數(shù)列符合它們各自的特點時,使用它們進行排序才能得到O(n)的效率,因此,實際使用中也不是太多。

五、排序算法總結(jié)

如下是八種基本排序算法的總結(jié):

 

八大排序算法總結(jié)

以上是比較基本的排序算法,還有其它的排序算法會在以后總結(jié)。

當你拿到一個數(shù)列考慮使用哪一種排序算法的時候:

  • 優(yōu)先考慮是否可以使用線性排序算法;
  • 如果數(shù)據(jù)不符合線性排序算法的特征,再看下數(shù)據(jù)量是否比較大,不大的話就選用O(n^2)的算法即可,比較簡單;
  • 如果數(shù)據(jù)量比較大,還是優(yōu)先考慮使用O(nlogn)的排序算法;
  • 其中因為歸并算法空間復雜度較高,因此快速排序會被更多地被考慮使用。
  • 快速排序的缺點在于最壞情況下時間復雜度會到O(n^2),這個取決于你每次迭代選取基準元素的方式和數(shù)列的特點決定的,通常可以對選取基準元素的方式進行優(yōu)化來盡量避免這個問題。
  • 比如原先是選取第一個或者最后一個元素,可以改進為隨機選取或者別的方式,目標是使得基準元素左右兩邊的數(shù)據(jù)元素個數(shù)都差不多,這樣才能維持O(nlogn)的時間復雜度。

分享到:
標簽:算法 排序
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定