java數(shù)據(jù)結(jié)構(gòu)與算法 - 排序

冒泡排序

public class Bubble {

/*
1. 比較相鄰的元素。如果前一個元素比后一個元素大倡缠,就交換這兩個元素的位置哨免。
2. 對每一對相鄰元素做同樣的工作,從開始第一對元素到結(jié)尾的最后一對元素昙沦。最終最后位置的元素就是最大值琢唾。
*/
    public static void sort1(Comparable[] arr){
        for (int i = arr.length - 1; i > 0 ;i -- ){ //將較大的數(shù)向后移
            for (int j = 0;j < i ;j ++ ){
                if (arr[j].compareTo(arr[j+1])>0){
                    Comparable temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }

            }
        }
    }

    public static void sort2(Comparable[] arr){ //將較小的數(shù)向前移
        for (int i = 0; i < arr.length - 1; i ++ ){
            for (int j = arr.length - 1 ; j > i ; j --){
                if(arr[j].compareTo(arr[j-1])<0){
                    Comparable temp = arr[j];
                    arr[j] = arr[j-1];
                    arr[j-1] = temp;
                }
            }
        }
    }

    public static void sort3(Comparable[] arr){ // 改進版 帶flag
        for(int i = 0; i < arr.length - 1; i ++){
            boolean flag = false; //是否交換的標志
            for (int j = arr.length - 1; j > i ; j--){
                if(arr[j].compareTo(arr[j-1])<0){
                    Comparable temp = arr[j];
                    arr[j] = arr[j-1];
                    arr[j-1] = temp;
                    flag = true;
                }
            }
            if(flag == false) //若無交換 說明已有序
                return;
        }
    }
}

image.png

選擇排序

image.png
public class Select {
    public static void sort(Comparable[] arr){
        for (int i = 0; i < arr.length - 1 ; i ++){
            int min = i; // 記錄最小值的下標
            for(int j = i + 1; j < arr.length ; j ++){
                if(arr[j].compareTo(arr[min])<0){
                    min = j;
                }     // 記錄當前最小值
            }
            if (min != i){ // 最小值不在第i位 則交換
                Comparable t = arr[i];
                arr[i] = arr [min];
                arr[min] = t;
            }
        }
    }
}

選擇排序的時間復雜度分析:
選擇排序使用了雙層for循環(huán),其中外層循環(huán)完成了數(shù)據(jù)交換盾饮,內(nèi)層循環(huán)完成了數(shù)據(jù)比較采桃,所以我們分別統(tǒng)計數(shù)據(jù)交換次數(shù)和數(shù)據(jù)比較次數(shù):
數(shù)據(jù)比較次數(shù):(N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
數(shù)據(jù)交換次數(shù):N-1
時間復雜度:N2/2-N/2+(N-1)=N2/2+N/2-1;
根據(jù)大O推導法則,保留最高階項丘损,去除常數(shù)因子普办,時間復雜度為O(N^2);

插入排序

排序原理:
1.把所有的元素分為兩組,已經(jīng)排序的和未排序的徘钥;
2.找到未排序的組中的第一個元素衔蹲,向已經(jīng)排序的組中進行插入;
3.倒敘遍歷已經(jīng)排序的元素呈础,依次和待插入的元素進行比較舆驶,直到找到一個元素小于等于待插入元素,那么就把待插入元素放到這個位置猪落,其他的元素向后移動一位贞远;


image.png
public class Insert {
    public static void sort(Comparable[] arr){
        for (int i = 1; i < arr.length ; i ++ ){
            // 當前元素為arr[i] 依次和前面的元素比較 找到一個小于等于arr[i]的元素
            for (int j = i; j > 0 ; j -- ){ // 有序表中 從后往前查找
                if(arr[j-1].compareTo(arr[j])>0){ // 在前面的有序表中找到合適的位置插入
                    Comparable t = arr[j];
                    arr[j] = arr [j-1];
                    arr[j-1] = t;
                }
            }
        }
    }
}

插入排序的時間復雜度分析
插入排序使用了雙層for循環(huán),其中內(nèi)層循環(huán)的循環(huán)體是真正完成排序的代碼笨忌,所以蓝仲,我們分析插入排序的時間復雜度,主要分析一下內(nèi)層循環(huán)體的執(zhí)行次數(shù)即可官疲。
最壞情況袱结,也就是待排序的數(shù)組元素為{12,10,6,5,4,3,2,1},那么:
比較的次數(shù)為:
(N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)(N-1)/2=N^2/2-N/2;
交換的次數(shù)為:
(N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)
(N-1)/2=N^2/2-N/2;
總執(zhí)行次數(shù)為:
(N2/2-N/2)+(N2/2-N/2)=N^2-N;
按照大O推導法則途凫,保留函數(shù)中的最高階項那么最終插入排序的時間復雜度為O(N^2).

希爾排序

排序原理:
1.選定一個增長量h垢夹,按照增長量h作為數(shù)據(jù)分組的依據(jù),對數(shù)據(jù)進行分組维费;
2.對分好組的每一組數(shù)據(jù)完成插入排序果元;
3.減小增長量促王,最小減為1,重復第二步操作而晒。


image.png

增長量 h的確定:增長量h的值每一固定的規(guī)則蝇狼,我們這里采用以下規(guī)則:

int h=1
while(h<5){
  h=2h+1;//3,7
}
//循環(huán)結(jié)束后我們就可以確定h的最大值倡怎;
//h的減小規(guī)則為:
  h=h/2
public class Shell {
    public static void sort(Comparable[] arr){
        int N = arr.length; // 增量h的最大值 增量遞增
        int h = 1;
        while (h < N / 2 ){
            h = h * 2 + 1;
        }
        // 當增量h小于1 排序結(jié)束
        while (h >= 1){
            // 找到待插入的元素arr[i]
            // 把 arr[i]插入到arr[i-2h] arr[i - 3h] ... 序列中
            for (int i = h ; i < N ; i ++){
                //arr[j] 就是待插入元素 依次和arr[j-h] arr[j-2h] .. 比較 若 arr[j]小 那么交換位置 反之 arr[j]大 則插入完成
                for(int j = i ; j >= h ; j-=h ){
                    if(arr[j-h].compareTo(arr[j])>0){
                        Comparable t = arr[j];
                        arr[j] = arr [j-h];
                        arr[j-h] = t;
                    }else{
                        break;
                    }
                }
            }
            h /= 2;
        }
    }
}

歸并排序

排序原理:
1.盡可能的一組數(shù)據(jù)拆分成兩個元素相等的子組迅耘,并對每一個子組繼續(xù)拆分,直到拆分后的每個子組的元素個數(shù)是1為止监署。
2.將相鄰的兩個子組進行合并成一個有序的大組颤专;(有序表的合并)
3.不斷的重復步驟2,直到最終只有一個組為止钠乏。


image.png
public class Merge {
    private static Comparable[] assist; //輔助數(shù)組
    public static void sort(Comparable[] arr){
        assist = new Comparable[arr.length];
        int low = 0;
        int high = arr.length - 1;
        sort(arr,low,high);
    }

    // low 到 high 的元素進行排序
    private static void sort(Comparable[] arr, int low, int high){
        if(high <= low) return;
        int mid = low + (high - low) / 2;
        // 對 low 到 mid 之間的元素進行排序
        sort(arr, low, mid);
        // 對 mid+1 到 high 之間的元素進行排序
        sort(arr,mid+1,high);
        // 對 low - mid , mid - high 這組數(shù)據(jù)進行歸并
        merge(arr, low, mid, high);
    }

    // 對數(shù)組中 low - mid 為一組 mid+1 - high 為一組 對兩組數(shù)組進行歸并
    private static void merge(Comparable[] arr, int low, int mid, int high){
        // low 到 mid 這組數(shù)據(jù)和 mid+1 到 high 這組數(shù)據(jù)歸并到輔助數(shù)組assist對應(yīng)的索引處
        int i = low; //定義一個指針栖秕,指向assist數(shù)組中開始填充數(shù)據(jù)的索引
        int p1 = low; //定義一個指針,指向第一組數(shù)據(jù)的第一個元素
        int p2 = mid + 1; //定義一個指針缓熟,指向第二組數(shù)據(jù)的第一個元素
        //比較左邊小組和右邊小組中的元素大小累魔,哪個小摔笤,就把哪個數(shù)據(jù)填充到assist數(shù)組中
        while (p1 <= mid && p2 <= high) {
            if (arr[p1].compareTo(arr[p2])<0) {
                assist[i++] = arr[p1++];
            } else {
                assist[i++] = arr[p2++];
            }
        }

        //上面的循環(huán)結(jié)束后够滑,如果退出循環(huán)的條件是p1<=mid,則證明左邊小組中的數(shù)據(jù)已經(jīng)歸并完畢吕世,如果退出循環(huán)的條件是p2<=high,則證明右邊小組的數(shù)據(jù)已經(jīng)填充完畢彰触;
        //所以需要把未填充完畢的數(shù)據(jù)繼續(xù)填充到assist中
        // 下面兩個循環(huán),只會執(zhí)行其中的一個
        while(p1<=mid){
            assist[i++]=arr[p1++];
        }
        while(p2<=high){
            assist[i++]=arr[p2++];
        }
        //到現(xiàn)在為止命辖,assist數(shù)組中况毅,從low到high的元素是有序的,再把數(shù)據(jù)拷貝到a數(shù)組中對應(yīng)的索引處
        for (int index=low;index<=high;index++){
            arr[index]=assist[index];
        }
    }
}

假設(shè)元素的個數(shù)為n尔艇,那么使用歸并排序拆分的次數(shù)為log2(n),所以共log2(n)層尔许,那么使用log2(n)替換上面32^3中的3這個層數(shù),最終得出的歸并排序的時間復雜度為:log2(n) 2^(log2(n))=log2(n)*n,根據(jù)大O推導法則终娃,忽略底數(shù)味廊,最終歸并排序的時間復雜度為O(nlogn);
歸并排序的缺點:
需要申請額外的數(shù)組空間,導致空間復雜度提升棠耕,是典型的以空間換時間的操作余佛。

快速排序

排序原理:
1.首先設(shè)定一個分界值,通過該分界值將數(shù)組分成左右兩部分窍荧;
2.將大于或等于分界值的數(shù)據(jù)放到到數(shù)組右邊辉巡,小于分界值的數(shù)據(jù)放到數(shù)組的左邊。此時左邊部分中各元素都小于或等于分界值蕊退,而右邊部分中各元素都大于或等于分界值郊楣;
3.然后憔恳,左邊和右邊的數(shù)據(jù)可以獨立排序。對于左側(cè)的數(shù)組數(shù)據(jù)净蚤,又可以取一個分界值喇嘱,將該部分數(shù)據(jù)分成左右兩部分,同樣在左邊放置較小值塞栅,右邊放置較大值者铜。右側(cè)的數(shù)組數(shù)據(jù)也可以做類似處理。
4.重復上述過程放椰,可以看出作烟,這是一個遞歸定義。通過遞歸將左側(cè)部分排好序后砾医,再遞歸排好右側(cè)部分的順序拿撩。當左側(cè)和右側(cè)兩個部分的數(shù)據(jù)排完序后,整個數(shù)組的排序也就完成了如蚜。


image.png

切分原理:
把一個數(shù)組切分成兩個子數(shù)組的基本思想:
1.找一個基準值压恒,用兩個指針分別指向數(shù)組的頭部和尾部;
2.先從尾部向頭部開始搜索一個比基準值小的元素错邦,搜索到即停止探赫,并記錄指針的位置;
3.再從頭部向尾部開始搜索一個比基準值大的元素撬呢,搜索到即停止伦吠,并記錄指針的位置;
4.交換當前左邊指針位置和右邊指針位置的元素魂拦;
5.重復2,3,4步驟毛仪,直到左邊指針的值大于右邊指針的值停止。

public class Quick {
    public static void sort(Comparable[] a){
        int low = 0;
        int high = a.length - 1;
        sort(a,low,high);
    }
    private static void sort(Comparable[] a,int low, int high){
        if(high<=low) return;
        // low - high 進行劃分
        int partition = partition(a, low , high);
        sort(a,low,partition-1);
        sort(a,partition+1,high);
    }

    private static int partition(Comparable[] a, int low, int high){
        Comparable pivot = a[low];
        while (low < high){
            while(low < high && a[high].compareTo(pivot)>0 ) --high;
            a[low] = a [high];
            while (low < high && a[low].compareTo(pivot)<0 ) ++ low;
            a[high] = a[low];
        }
        a[low] = pivot; // 樞軸元素放到最終位置
        return low;// 存放樞軸的位置
    }
}

快速排序時間復雜度分析:
快速排序的一次切分從兩頭開始交替搜索芯勘,直到left和right重合箱靴,因此,一次切分算法的時間復雜度為O(n),但整個快速排序的時間復雜度和切分的次數(shù)相關(guān)荷愕。
最優(yōu)情況:每一次切分選擇的基準數(shù)字剛好將當前序列等分衡怀。
如果我們把數(shù)組的切分看做是一個樹,那么上圖就是它的最優(yōu)情況的圖示路翻,共切分了 logn次狈癞,所以,最優(yōu)情況下快速排序的時間復雜度為O(nlogn);
最壞情況:每一次切分選擇的基準數(shù)字是當前序列中最大數(shù)或者最小數(shù)茂契,這使得每次切分會有一個子組蝶桶,那么總共就得切分n次,所以掉冶,最壞情況下真竖,快速排序的時間復雜度為O(n^2);

穩(wěn)定性

常見排序算法的穩(wěn)定性:
冒泡排序:
只有當arr[i]>arr[i+1]的時候脐雪,才會交換元素的位置,而相等的時候并不交換位置恢共,所以冒泡排序是一種穩(wěn)定排序算法战秋。
選擇排序:
選擇排序是給每個位置選擇當前元素最小的,例如有數(shù)據(jù){5(1),8 讨韭,5(2)脂信, 2, 9 },第一遍選擇到的最小元素為2透硝,所以5(1)會和2進行交換位置狰闪,此時5(1)到了5(2)后面,破壞了穩(wěn)定性濒生,所以選擇排序是一種不穩(wěn)定的排序算法埋泵。
插入排序:
比較是從有序序列的末尾開始,也就是想要插入的元素和已經(jīng)有序的最大者開始比起罪治,如果比它大則直接插入在其后面丽声,否則一直往前找直到找到它該插入的位置。如果碰見一個和插入元素相等的觉义,那么把要插入的元素放在相等元素的后面雁社。所以,相等元素的前后順序沒有改變谁撼,從原無序序列出去的順序就是排好序后的順序歧胁,所以插入排序是穩(wěn)定的滋饲。
希爾排序:
希爾排序是按照不同步長對元素進行插入排序 ,雖然一次插入排序是穩(wěn)定的厉碟,不會改變相同元素的相對順序,但在不同的插入排序過程中屠缭,相同的元素可能在各自的插入排序中移動箍鼓,最后其穩(wěn)定性就會被打亂,所以希爾排序是不穩(wěn)定的呵曹。
歸并排序:
歸并排序在歸并的過程中款咖,只有arr[i]<arr[i+1]的時候才會交換位置,如果兩個元素相等則不會交換位置奄喂,所以它并不會破壞穩(wěn)定性铐殃,歸并排序是穩(wěn)定的。
快速排序:
快速排序需要一個基準值跨新,在基準值的右側(cè)找一個比基準值小的元素富腊,在基準值的左側(cè)找一個比基準值大的元素,然后交換這兩個元素域帐,此時會破壞穩(wěn)定性赘被,所以快速排序是一種不穩(wěn)定的算法是整。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市民假,隨后出現(xiàn)的幾起案子浮入,更是在濱河造成了極大的恐慌,老刑警劉巖羊异,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件事秀,死亡現(xiàn)場離奇詭異,居然都是意外死亡野舶,警方通過查閱死者的電腦和手機秽晚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筒愚,“玉大人赴蝇,你說我怎么就攤上這事〕膊簦” “怎么了句伶?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長陆淀。 經(jīng)常有香客問我考余,道長,這世上最難降的妖魔是什么轧苫? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任楚堤,我火速辦了婚禮,結(jié)果婚禮上含懊,老公的妹妹穿的比我還像新娘身冬。我一直安慰自己,他們只是感情好岔乔,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布酥筝。 她就那樣靜靜地躺著,像睡著了一般雏门。 火紅的嫁衣襯著肌膚如雪嘿歌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天茁影,我揣著相機與錄音宙帝,去河邊找鬼。 笑死募闲,一個胖子當著我的面吹牛步脓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沪编,長吁一口氣:“原來是場噩夢啊……” “哼呼盆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚁廓,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤访圃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后相嵌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腿时,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年饭宾,在試婚紗的時候發(fā)現(xiàn)自己被綠了批糟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡看铆,死狀恐怖徽鼎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弹惦,我是刑警寧澤否淤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站棠隐,受9級特大地震影響石抡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜助泽,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一啰扛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗡贺,春花似錦隐解、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至岩臣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宵膨,已是汗流浹背架谎。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辟躏,地道東北人谷扣。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親会涎。 傳聞我的和親對象是個殘疾皇子裹匙,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容