算法學(xué)習(xí)之不那么簡(jiǎn)單的排序(1)

也不知道與簡(jiǎn)單排序?qū)?yīng)的應(yīng)該叫什么, 就叫不那么簡(jiǎn)單的排序好了.

本篇博客主要學(xué)習(xí)了希爾排序、歸并排序and快速排序把沼。

注: 這一篇和上一篇簡(jiǎn)單排序都算是學(xué)習(xí)白話算法系列的學(xué)習(xí)筆記吧

希爾排序

希爾排序是基于插入排序而來(lái), 插入排序的最好時(shí)間復(fù)雜度是O(n), 當(dāng)數(shù)組基本有序時(shí), 效率是很高的. 而希爾排序, 設(shè)定一個(gè)增量, 按增量將數(shù)組分組.

例如數(shù)組{1,2,3,4}, 增量是2, 那么數(shù)組就可以分為{1,3}, {2,4}兩組, 增量是1那就是1,2,3,4四組.

分組之后在組內(nèi)進(jìn)行插入排序, 再縮小增量重新分組再次排序, 直到增量是1(等同于正常的插入排序), 再插入排序一次, 排序完成.

void shellSort(int arr[], int n){
    
    for (int gap = n/2; gap>0; gap/=2) {
        for (int i = gap; i<n; i++) {
            if (arr[i] < arr[i - gap]) {
                int temp = arr[i];
                int j;
                // 思路與插入排序相同, 用臨時(shí)變量保存要插入的數(shù), 向數(shù)組前面查找插入的位置, 一邊查找, 一邊將前面較大的數(shù)字后移
                // 臨時(shí)變量不小于前面的某數(shù)時(shí), 說(shuō)明找到了正確的位置, 只要放在那個(gè)數(shù)后面就可以了
                for (j = i-gap; j>=0 && temp<arr[j]; j-=gap) {
                    arr[j+gap] = arr[j];
                }
                arr[j+gap] = temp;
            }
        }
    }
}

歸并排序

歸并二字就是遞歸&合并

歸并排序的關(guān)鍵在于合并有序數(shù)組, 合并兩個(gè)有序數(shù)組的方式是先比較兩數(shù)組的第一個(gè)元素, 更小的取出放入新數(shù)組, 再依次向后比較, 直到某個(gè)數(shù)組的元素取光, 把另一個(gè)數(shù)組的元素依次放入新數(shù)組既可.

//先來(lái)演示合并數(shù)組
void mergeArray(int a[], int m, int b[], int n){
    int c[m+n];
    
    int i, j, k;
    //必須初始化, 否則會(huì)有殘值
    i = j = k = 0;
    
    // 此處不能用for循環(huán), 除非只寫第二個(gè)表達(dá)式, 否則ijk哪個(gè)做自增都不合適
    // 其中k看似合適, 但for循環(huán)最后會(huì)執(zhí)行一次第三個(gè)表達(dá)式, k會(huì)+1
    while (i < m && j < n) {
        if (a[i] < b[j]) {
            c[k++] = a[i++];
        }else{
            c[k++] = b[j++];
        }
    }
    
    while (i < m) {
        c[k++] = a[i++];
    }
    
    while (j < n) {
        c[k++] = b[j++];
    }
    
    printfArray(c, m+n);
}

下面開始擼正式的歸并排序

// 合并有序序列
void mergearray(int arr[], int first, int last, int mid, int temp[]){
    int tempIndex = 0;
    
    int firstSequenceIndex = first;
    int secondSequeceIndex = mid + 1;
    
    // 因?yàn)檫@里用的是數(shù)組角標(biāo), 而不是長(zhǎng)度, 所以用<= 而不是<
    while (firstSequenceIndex <= mid && secondSequeceIndex <= last) {
        // 取較小值放入臨時(shí)數(shù)組
        if (arr[firstSequenceIndex] < arr[secondSequeceIndex] ) {
            temp[tempIndex++] = arr[firstSequenceIndex++];
        }else{
            temp[tempIndex++] = arr[secondSequeceIndex++];
        }
    }
    // 如果前一個(gè)序列還有值, 依次放入臨時(shí)數(shù)組
    while (firstSequenceIndex <= mid) {
        temp[tempIndex++] = arr[firstSequenceIndex++];
    }
    // 如果后一個(gè)序列還有值, 依次放入臨時(shí)數(shù)組
    while (secondSequeceIndex <= last) {
        temp[tempIndex++] = arr[secondSequeceIndex++];
    }
    // 將排好序的部分賦值給原數(shù)組
    for (int i = 0; i < tempIndex; i++) {
        arr[first++] = temp[i];
    }
    
}

// 搞清歸并排序, 主要搞清以下兩點(diǎn)
// 1. 遞歸到只有一個(gè)數(shù)時(shí), 遞歸函數(shù)開始出棧, 一個(gè)數(shù)肯定是有序序列
// 2. 合并兩個(gè)有序序列, 可以形成新的有序序列
void mergeSort(int arr[], int first, int last, int temp[]){
    if(first < last){
        // 將數(shù)組分成兩部分
        int mid = (first + last)/2;
        // 前一半排序
        mergeSort(arr, first, mid, temp);
        // 后一半排序
        mergeSort(arr, mid+1, last, temp);
        // 合并有序序列
        mergearray(arr, first, last, mid, temp);
    }
}

快速排序

快速排序是時(shí)間復(fù)雜度O(logN*N)的排序算法中比較出名的, 面試算法常常會(huì)問(wèn), 而手寫出來(lái)是很有難度的事情. 這里非常感謝白話經(jīng)典算法系列的作者, 講解通俗易懂.

快速排序的基本思想一句話概括就是<font color='red'>挖坑填數(shù)+分治法</font>, 下面詳細(xì)描述:

  1. 先取左邊第一個(gè)數(shù)作為基準(zhǔn)數(shù)
  2. 與基準(zhǔn)數(shù)比較, 比基準(zhǔn)數(shù)大的換到右邊, 小的換到左邊
  3. 左右兩邊分成兩個(gè)部分, 再進(jìn)行一次前兩步的操作. 重復(fù)對(duì)左右兩邊拆分, 進(jìn)行前兩步操作, 直到只剩一個(gè)數(shù).

這樣說(shuō)還是太抽象, 舉個(gè)栗子吧

數(shù)組a = {3, 1, 4, 2, 0}

  1. 取a[0]作為基準(zhǔn)數(shù), 使用新變量baseNumber存儲(chǔ)
  2. 從右向左比較, 比基準(zhǔn)數(shù)小的放在基準(zhǔn)數(shù)的位置上, 數(shù)組變成{<font color='red'>0</font>, 1, 4, 2, <font color='red'>0</font>}, 此時(shí)出現(xiàn)一個(gè)坑a[4]
  3. 從左往右比較, 比基準(zhǔn)數(shù)大的填入上一個(gè)坑a[4], 數(shù)組變成{0, 1, <font color='red'>4</font>, 2, <font color='red'>4</font>}, 此時(shí)的新坑是a[2]
  4. 再?gòu)挠蚁蜃蟊容^, 比基準(zhǔn)數(shù)小的填入上一個(gè)坑a[2], 數(shù)組變成{0, 1, <font color='red'>2</font>, <font color='red'>2</font>, 4}, 此時(shí)的坑是a[3]
  5. 再?gòu)淖笙蛴冶容^時(shí), 發(fā)現(xiàn)左右相遇了, 將baseNumber賦值給a[3], 數(shù)組變成{0, 1, 2, 3, 4}

因?yàn)閿?shù)組元素較少, 這樣就排序完成了, 但足夠大家了解挖坑填數(shù)的思路了.

有一點(diǎn)需要說(shuō)明, 為什么左右相遇了就可以把baseNumber賦值給那個(gè)元素? 因?yàn)樽笥覂蛇呄嘤鰰r(shí), 所有數(shù)字都已經(jīng)比較了一遍, 已經(jīng)做到"比基準(zhǔn)數(shù)大的都在右邊, 比基準(zhǔn)數(shù)小的都在左邊".

根據(jù)上面的分析, 可以很容易寫出挖坑填數(shù)的代碼:

void changeArray(int arr[], int left, int right){
    int i = left;
    int j = right;
    
    // 使用變量存儲(chǔ)最左邊的數(shù)做基準(zhǔn)數(shù)
    // 基準(zhǔn)數(shù)也可不使用最左邊的, 中間和最后一個(gè)當(dāng)然都可以
    int baseNumber = arr[left];
    
    // 當(dāng)i=j時(shí)意味著數(shù)列中所有數(shù)都與基準(zhǔn)數(shù)比較過(guò)了, 故結(jié)束比較
    while (i < j) {
        
        // 從右往左比較, 找到比基準(zhǔn)數(shù)小的數(shù)的下標(biāo)
        while (arr[j] > baseNumber && i < j) {
            j--;
        }
        arr[i] = arr[j];
        
        // 從左往右比較, 找到比基準(zhǔn)數(shù)大的數(shù)的下標(biāo)
        while (arr[i] < baseNumber && i < j) {
            i++;
        }
        arr[j] = arr[i];
    }
    // 將基準(zhǔn)數(shù)賦值給a[i](也可以是a[j], 此時(shí)i=j)
    arr[i] = baseNumber;
    
}

最后baseNumber賦值,arr[i] = baseNumber,可能會(huì)有人對(duì)這句疑惑, 為何可以直接賦值, 不會(huì)少一個(gè)數(shù)嗎?

答案是不會(huì), 從上面的代碼看出, 即便while (arr[i] < baseNumber && i < j)這個(gè)循環(huán)沒(méi)有走, arr[i]的值也會(huì)賦值給arr[j], 這樣arr[i]的值必定有兩個(gè), 當(dāng)然可以直接賦值.

接下來(lái)徹底完成遞歸調(diào)用:

void quickSort(int arr[], int left, int right){
    // 遞歸的結(jié)束條件, left=right, 也就是只剩一個(gè)數(shù)的時(shí)候
    if (left < right) {
        
        int i = left;
        int j = right;
        int baseNumber = arr[left];
        
        while (i < j) {  
            while (arr[j] > baseNumber && i < j) {
                j--;
            }
            arr[i] = arr[j];
            
            while (arr[i] < baseNumber && i < j) {
                i++;
            }
            arr[j] = arr[i];
        }
        
        arr[i] = baseNumber;
        
        // 遞歸調(diào)用
        quickSort(arr, left, i - 1);
        quickSort(arr, i + 1, right);
    } 
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市吁伺,隨后出現(xiàn)的幾起案子饮睬,更是在濱河造成了極大的恐慌,老刑警劉巖篮奄,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捆愁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡窟却,警方通過(guò)查閱死者的電腦和手機(jī)昼丑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)夸赫,“玉大人菩帝,你說(shuō)我怎么就攤上這事〔缤龋” “怎么了呼奢?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)切平。 經(jīng)常有香客問(wèn)我握础,道長(zhǎng),這世上最難降的妖魔是什么揭绑? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任弓候,我火速辦了婚禮郎哭,結(jié)果婚禮上他匪,老公的妹妹穿的比我還像新娘。我一直安慰自己夸研,他們只是感情好邦蜜,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著亥至,像睡著了一般悼沈。 火紅的嫁衣襯著肌膚如雪贱迟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天絮供,我揣著相機(jī)與錄音衣吠,去河邊找鬼。 笑死壤靶,一個(gè)胖子當(dāng)著我的面吹牛缚俏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贮乳,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼忧换,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了向拆?” 一聲冷哼從身側(cè)響起亚茬,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浓恳,沒(méi)想到半個(gè)月后刹缝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颈将,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年赞草,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吆鹤。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡厨疙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疑务,到底是詐尸還是另有隱情沾凄,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布知允,位于F島的核電站撒蟀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏温鸽。R本人自食惡果不足惜保屯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一涤垫、第九天 我趴在偏房一處隱蔽的房頂上張望姑尺。 院中可真熱鬧蝠猬,春花似錦、人聲如沸榆芦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)驻右。三九已至什黑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堪夭,已是汗流浹背兑凿。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茵瘾,地道東北人礼华。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像拗秘,于是被迫代替她去往敵國(guó)和親圣絮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

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