1. 程式人生 > 其它 >"簡單"的優化--希爾排序也沒你想象中那麼難

"簡單"的優化--希爾排序也沒你想象中那麼難

最近我們進入了排序演算法專題,上節課聊到了"簡單"插入排序,那在簡單的基礎上,我們可以怎麼做進一步的優化呢,這篇來看看優化版--**希爾排序**!

寫在前邊

大家好,我是melo,一名大二上軟體工程在讀生,經歷了一年的摸滾,現在已經在工作室裡邊準備開發後臺專案啦。
不過這篇文章呢,還是想跟大家聊一聊資料結構與演算法,學校也是大二上才開設了資料結構這門課,希望可以一邊學習資料結構一邊積累後臺專案開發經驗。
最近我們進入了排序演算法專題,上節課聊到了"簡單"插入排序,那在簡單的基礎上,我們可以怎麼做進一步的優化呢,這篇來看看優化版--希爾排序

知識點

概念

希爾排序(Shell Sort)是插入排序的一種,它是針對直接插入排序演算法的改進。
希爾排序又稱縮小增量排序,因 DL.hell 於 1959 年提出而得名。
它通過比較相距一定間隔的元素來進行,各趟比較所用的距離隨著演算法的進行而減小,直到只比較相鄰元素的最後一趟排序為止。

引入

簡單插入排序存在問題

改進

  • 分割待排序記錄的個數,分別進行插入排序

基本思想

  • 希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序演算法排序;隨著增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個陣列恰被分成一組,演算法便終止

精髓

  • 由於開始時每組只有很少整數,所以排序很快。之後每組含有的整數越來越多,但是由於這些數也越來越有序,所以排序速度也很快。

示意圖

按一定增量分組,然後逐漸減小增量

初始化gap為length/2,逐漸減小為gap/2,直到gap不滿足>0的條件

分組後,再對該組進行簡單插入排序

  • 拿圖中的第三步舉例,陣列分成了兩組[3,1,0,9,7],[5,6,8,4,2]
    • 對[3,1,0,9,7]進行簡單插入排序,看成前n-1個為有序陣列,第n個為待插入的元素(找到自己的位置後插入即可)

不夠清晰的話也可以看下邊這張


程式碼實現

力扣912排序陣列 : https://leetcode-cn。com/problems/sort-an-array/submissions/
又是這道題hhh,萬能

思路概覽

首先

  • 我們要先初始化增量gap=length/2,然後不斷縮小gap=gap/2 直到不滿足gap>0

所以我們最外層會需要一個for迴圈來調控這個gap的變化

其次,再往內層走

  • 對於分組後,由於我們是要對分組後的每一組進行簡單插入排序,而插入排序我們預設從待排序陣列的第二位開始,所以我們需要從每一組的第二位開始去遍歷,直到整個陣列的末尾

for迴圈讓i=gap;i<陣列;i++即可

最後,就對該陣列進行插入排序即可

  • 注意不是跟前一個進行比較了,而是跟 j-gap 比較

最初版本(for)

for的話,我是先把j賦值等於i-gap,這樣的話就是跟j去比較,最後也還會去j-=gap
會導致我最後跳出迴圈的時候,得插到j+gap

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* sortArray(int* nums, int numsSize, int* returnSize){
    //逐步縮小增量gap
    for(int gap=numsSize/2;gap>0;gap=gap/2){
        int insertValue = 0;
        int j;
        //從分組後的第一組的第二位開始
        for(int i=gap;i<numsSize;i++){
                //儲存待插入的值
                insertValue=nums[i];
                //因為本身有序,若待插入的數還大於最後一個數,則無須繼續遍歷下去了
            	//注意j>=0的條件,這裡無哨兵了
                for(j=i-gap;j>=0 && insertValue<nums[j];j-=gap){
                    //若待插入的值小於索引值,證明要索引值需要後移,空出j這個位置給插入值
                        nums[j+gap]=nums[j];
                }
                //跳出迴圈後,把這個數插入到指定位置
                nums[j+gap]=insertValue;
        }
    }
    *returnSize=numsSize;
    return nums;
}

改進for

先去判斷是否 j-gap>=0,滿足才進迴圈,才會去j-=gap,所以最後j就是要插入的位置

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* sortArray(int* nums, int numsSize, int* returnSize){
    //逐步縮小增量gap
    for(int gap=numsSize/2;gap>0;gap=gap/2){
        int insertValue = 0;
        //用於插入排序中遍歷待排序的陣列
        int j;
        //從分組後的第一組的第二位開始
        for(int i=gap;i<numsSize;i++){
                //儲存待插入的值
                insertValue=nums[i];
        //因為本身有序,若待插入的數還大於最後一個數,則無須繼續遍歷下去了
                for(j=i;j-gap>=0 && insertValue<nums[j-gap];
                    j-=gap){
                    //若待插入的值小於索引值,證明要索引值需要後移,空出j這個位置給插入值
                        nums[j]=nums[j-gap];
                }
                //跳出迴圈後,把這個數插入到指定位置
                nums[j]=insertValue;
        }
    }
    *returnSize=numsSize;
    return nums;
}

注意

  • 希爾排序沒有辦法用到哨兵了,我們需要注意判斷是否走到頭了

參考

  • 菜鳥教程
  • 尚矽谷資料結構與演算法